비실이의 개발 성장기

리스코브 치환의 원칙(LSP) 에 대해 알아보자 본문

디자인패턴

리스코브 치환의 원칙(LSP) 에 대해 알아보자

DubbingLee 2017. 2. 11. 16:59


리스코브 치환 의 원칙 (Liskov substitution principle, LSP) 이란 ?


-> 객체지향적으로 개발하기 위한 방법론 S.O.L.I.D 중 하나.  1998년 Barbara Liskov (MIT 컴퓨터 사이언스 교수) 가 제안한 원칙으로 

'컴퓨터 프로그램에서 자료형 S가 자료형 T의 하위형이라면 필요한 프로그램의 속성(정확성, 수행하는 업무 등) 의 변경 없이 자료형 T의 객체를 자료형 S의 객체로 치환 할 수 있어야 한다' 는 원칙.




-> 서브 타입(자식 클래스) 은 언제나 자신의 기반타입(부모 클래스) 으로 교체 할 수 있어야 한다.



-> 클래스 인 경우,   하위 클래스 라면 상위 클래스의 한 종류여야 한다. 



-> 인터페이스 인 경우,  구현 클래스는 인터페이스(규약) 를 지켜야한다.



-> 리스코브 치환 원칙 논문 내용 중,


    • 하위형에서 선행조건은 강화될 수 없다.
    • 하위형에서 후행조건은 약화될 수 없다.
    • 하위형에서 상위형의 불변조건은 반드시 유지되어야 한다.

     이를 보면 하위형은 상위형의 규약을 엄격하게 지켜야 함을 알 수 있습니다. 


출처 : https://ko.wikipedia.org/wiki/%EB%A6%AC%EC%8A%A4%EC%BD%94%ED%94%84_%EC%B9%98%ED%99%98_%EC%9B%90%EC%B9%99





-> 예제 (인터페이스 인 경우)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.dubbing.test;
 
/**
 * 
 * @author leesungwoo
 *  동물 interface
 *  
 *  울음소리 내기, 음식 먹기  
 */
public interface Animal {
    void makeSound();
    
    void eatFood();
}
 
cs


Animal 이라는 동물 인터페이스를 생성하고  


기본행동으로  `울음소리 내기`, `음식 먹기` 두 가지 기능을 하도록 정의 했습니다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.dubbing.test;
 
public class Cat implements Animal{
 
    @Override
    public void makeSound() {
        // TODO Auto-generated method stub
        System.out.println("미야옹~~");
    }
 
    @Override
    public void eatFood() {
        // TODO Auto-generated method stub
        System.out.println("냠냠냠~~");
    }
 
}
 
cs


Cat 클래스를 생성하고 Animal 인터페이스를 구현하도록 했습니다. (고양이는 동물이다. - ISA관계)


Animal 인터페이스에서 정의 해 놓은 `울음소리 내기`, `음식 먹기` 기능을 구현했습니다.





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.dubbing.test;
 
public class Dog implements Animal {
 
    @Override
    public void makeSound() {
        // TODO Auto-generated method stub
        System.out.println("멍멍멍 !!");
    }
 
    @Override
    public void eatFood() {
        // TODO Auto-generated method stub
        System.out.println("강아지 밥먹는다~!");
    }
 
}
 
cs


Dog 클래스를 생성하고 Animal 인터페이스를 구현하도록 했습니다. (강아지는 동물이다. - ISA관계)


Animal 인터페이스에서 정의 해 놓은 `울음소리 내기`, `음식 먹기` 기능을 구현했습니다.





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.dubbing.test;
 
/**
 * 
 * @author leesungwoo
 * Dubbing 이네 동물농장 
 * 
 */
public class AnimalHouse {
    public static void main(String[] args) {
        Cat nabi = new Cat();
        
        nabi.makeSound();
        nabi.makeSound();
        nabi.makeSound();
        
        nabi.eatFood();
    }
}
 
cs


AnimalHouse라는 동물농장 클래스를 생성하였습니다.


Cat nabi = new Cat(); 통해 nabi 라는 이름의 고양이를 농장에 데려왔습니다.


고양이가 너무 우네요...


간식을 하나 줬습니다.





...




여기서 Cat nabi = new Cat(); 을


Animal nabi = new Cat(); 변경해도 


Animal은 Cat의 상위타입이기 때문에


기능에 문제가 발생하지 않습니다. (LSP 원칙에 위배되지 않음. 다형성)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.dubbing.test;
 
/**
 * 
 * @author leesungwoo
 * Dubbing 이네 동물농장 
 * 
 */
public class AnimalHouse {
    public static void main(String[] args) {
        Animal nabi = new Cat();
        
        nabi.makeSound();
        nabi.makeSound();
        nabi.makeSound();
        
        nabi.eatFood();
    }
}
 
cs





이는 추후 Animal의 하위타입으로 Cat, Dog 뿐만 아니라


어떠한 동물 클래스가 추가되어도 LSP원칙을 따르기 때문에 


문제가 발생하지 않음을 의미합니다. (인터페이스를 사용함으로써 확장에 열려있음)





...


.. 시간이 지나고 고양이가 아닌 강아지를 키우고 싶어졌습니다. 


이 경우, Animal nabi = new Cat(); 부분을  Animal nabi = new Dog(); 처럼 바꿔도


Dog는 Animal의 하위타입이기 때문에 기능에 문제가 발생하지 않습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.dubbing.test;
 
/**
 * 
 * @author leesungwoo
 * Dubbing 이네 동물농장 
 * 
 */
public class AnimalHouse {
    public static void main(String[] args) {
        Animal nabi = new Dog();
        
        nabi.makeSound();
        nabi.makeSound();
        nabi.makeSound();
        
        nabi.eatFood();
    }
}
 
cs








-> 예제 (LSP 원칙 위반)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Duck {
    protected boolean starve = true;
    public void sing() {
        System.out.println("quack quack");
    }
    public void eat() {
        System.out.println("munch munch");
        this.starve = false;
    }
    public boolean isStarve() {
        return this.starve;
    }
}
 
public class ToyDuck extends Duck {
    @Override
    public void eat() {
        System.out.println("toy duck cannot eat!");
    }
}
cs


위 코드는 ToyDuck 클래스를 설계한 코드이며, LSP 원칙을 위반 하였습니다.



코드를 분석해보겠습니다.



ToyDuck 클래스는 상위클래스 인 Duck을 상속 받았습니다.


장난감 오리는 음식을 먹을 수 없으므로 Duck 클래스의 eat() 기능을 그대로 사용하면 의도에 어긋나게 됩니다.


그래서 eat()을 재정의하여 '장난감 오리는 음식을 먹을 수 없어요.' 가 출력되도록 기능을 변경했습니다. 



하지만, 상위클래스의 eat()에서는 starve 값을 변화해주고 있는데, 


하위클래스에서 재정의한 eat() 에는 starve 값을 바꿔주는 기능을 넣지 않았습니다.



물론 코드를 실행해보면 컴파일 에러가 발생하거나 별다른 에러가 발생하진 않습니다.



하지만, 하위클래스는 상위클래스의 명세를 따르지 않았기에 추후 원하는 데이터를


얻지 못할 가능성이 생기게 됩니다.


 



     





# 잘못된 내용이 있으면 알려주세요!!












'디자인패턴' 카테고리의 다른 글

단일 책임 원칙 (SRP) 이란??  (0) 2017.03.12
Flux 아키텍쳐 란??  (0) 2017.02.12
리스코브 치환의 원칙(LSP) 에 대해 알아보자  (0) 2017.02.11
0 Comments
댓글쓰기 폼