본문 바로가기
디자인 패턴

2-2. SOLID(S.O.L.I.D)

by spaul 2023. 10. 18.

지난 포스팅에 이어 LSP, ISP, DIP에 대해 알아보도록 하겠습니다.

▣ LSP (Liskov Substitution Principle)

Subclasses should be substitutable for their base classes

- Robert C.Martin -

 

LSP는 자식 클래스는 부모 클래스를 대체 할 수  있어야 하며, 부모 클래스 객체 대신 자식 클래스를 사용했을 때 문제없이 프로그램이 동작해야한다는 원칙입니다.

 

정사각형과 직사각형을 생각해볼까요? 정사각형은 직사각형의 일부, 즉 부분집합이라고 생각할 수 있습니다. 즉 아래 그림과 같이 직사각형 클래스를 상속받아 정사각형 클래스를 구현할 수 있습니다.

Java 코드를 통해 직사각형 클래스와 정사각형 클래스를 구현해보겠습니다. 

public class Rectangle {
    private int width;
    private int height;

    public Rectangle(int w, int h) {
        width = w;
        height = h;
    }

    public int getPerimeter() {
        return 2 * (width + height);
    }

    public void setWidth(int w) {
        width = w;
    }

    public void setHeight(int h) {
        height = h;
    }
}

 

public class Square extends Rectangle {
    public Square(int w) {
        super(w,w);
    }

    @Override
    public void setWidth(int w) {
        super.setWidth(w);
        super.setHeight(w);
    }

    @Override
    public void setHeight(int h) {
        super.setWidth(h);
        super.setHeight(h);
    }
}

 

Rectangle 클래스에서 직사각형의 둘레를 구하기 위한 함수인 getPerimeter()를 정의했고, Square 클래스에서 Rectangle 클래스를 상속받았습니다. 정사각형은 폭과 높이가 같은 직사각형이므로 Square 클래스의 setWidth()와 setHeight()에서 각각 폭과 높이를 같게 설정해주고 있습니다.

public class Main {
    public static void main(String[] args) {
        Rectangle r = new Rectangle(3, 5);
        System.out.println(r.getPerimeter());
        Square s = new Square(3);
        System.out.println(s.getPerimeter());
        r = s;
        r.setWidth(3);
        r.setHeight(5);
        System.out.println(r.getPerimeter());
    }
}

 

그런 다음 Main 클래스에서 r과  s 객체를 각각 생성했습니다. LSP에서 자식 클래스는 부모 클래스를 대체할 수 있어야 한다고 했으므로

r = s;
r.setWidth(3);
r.setHeight(5);
System.out.println(r.getPerimeter());

 

위의 코드를 수행했을 때 우리가 기대하는 출력값은 직사각형의 둘레인 16이 나와야 할 것 같습니다. 실행 결과를 볼까요?

 

main 함수 수행 결과

 

이런, 우리가 원했던 결과가 출력되지 않았습니다. 당연한 것이, r이 s를 가리키게 했을 때, r은 s의 오버라이드 된 setWidth()와 setHeight()를 각각 호출할 것이고, 이 메서드들은 폭과 높이를 동일하게 설정하므로 결국 r의 width와 height는 각각 5가 되겠죠.

 

위의 예시처럼 자식클래스가 부모클래스를 완벽하게 대체하지 못하는 경우, LSP를 위반했다고 볼 수 있습니다. LSP를 위반하는 경우 코드를 설계할 때 예상했던 output이 나오지 않을 수 있으므로, 위반하지 않도록 잘 설계하는 것이 중요합니다.

 

▣ ISP (Interface Segregation Principle)

Many client specific interfaces are better than one general purpose interface.


- Robert C.Martin -

 

ISP는 인터페이스 분리 원칙으로, 여러 개 클라이언트에 필요한 기능을 가지고 있는 일반화된 인터페이스보다는 각 클라이언트에 특화되어 있는 인터페이스를 사용해야 한다는 원칙입니다.

 

 

위의 클래스 다이어그램과 같이 Loader 클래스는 ILoader 인터페이스를 구현하며, 다시 CDManager, BookManager, MP3Manager는 ILoader와 연관 관계를 맺고 있습니다. 이 상황에서 만약 Loader의 loadMP3s()의 내용을 변경한다면, ILoader를 사용하는 CDManager, BookManager, MP3Manager 모두에게 영향이 있을겁니다. 다시 말해서, 각각의 Manager들은 CD, Book, MP3 중 하나씩만 담당하고 있는데, Loader에서 loadMP3s()를 변경함으로써 3개의 Manager클래스 모두에게 영향이 갈 수 있다(다시 컴파일을 해야된다던지 등)는 뜻입니다.  

 

클래스 구조를 이런식으로 수정하면, Loader의 loadMP3s()가 수정되었을 때, BookManager와 CDManager에는 영향 없이 MP3Manager에게만 변경사항이 적용될 것이므로 ISP를 만족한다고 볼 수 있을 것입니다.

(그림에 오타가 있는데 private 변변수 loader의 type은 ILoader가 아닌 ICDLoader, IMP3Loader, IBookLoader 등으로 바뀌어야 할 것입니다.)

 

하나의 클래스(혹은 인터페이스)가 하나의 기능만을 수행해야한다는 점에서 ISP 역시 SRP와 비슷한 부분이 있습니다.

 

▣ DIP (Dependency Inversion Principle)

Depend upon Abstractions. Do not depend upon Concretions

- Robert C.Martin -

DIP (의존성 역전 원칙)은 기능을 직접 구현한 구체(구상) 클래스 또는 함수보다는 추상 클래스나 인터페이스를 사용하는 코드를 작성하라는 원칙입니다. 즉, 되도록 구체적인 클래스를 상속하는 구조보다는 인터페이스나 추상클래스를 구현하여 클래스를 사용하라는 원칙입니다. 기능을 직접 구현한 클래스나 메서드는 변경될 가능성이 높기 때문입니다.

 

위와 같은 클래스 다이어그램이 있다고 했을 때 아래와 같이 인터페이스를 통해 구현하도록 하면 DIP를 만족할 수 있을 것입니다.

 

 

별 차이가 없는 것 같은데 굳이 이런식으로 바꾸는 이유는, 예를 들어 BookDataLoaderFromFile 코드를 수정하여 컴파일을 다시 해야한다고 했을 때, 첫 번째 그림은 BookDataLoaderFromFile과 BookManager 클래스 둘 다 다시 컴파일 해야하겠지만, 중간에 인터페이스를 삽입하게 되면 BookDataLoaderFromFile만 다시 컴파일 하면 되므로 컴파일 해야하는 양이 훨씬 줄어들 수 있습니다. 예제로 다루는 작은 프로그램에서 정도야 컴파일을 다시 하는 시간이 무슨 대수냐 싶겠지만, 프로그램이 커질 수록 무시하지 못할 수준이 될 것입니다.

 

DIP를 끝으로 SOLID원칙에 대한 포스팅을 마무리 하겠습니다. 제가 이해하고 알고 있는 부분에 대해선 전부 포스팅했다고 생각하는데, 잘 이해가 되실지 모르겠습니다. 사실 SOLID 자체가 각각의 원칙들끼리 겹치는 내용들도 많고, 이 원칙들을 전부 다 지켜가며 프로그래밍을 할 수 없는 상황도 있기 때문에, "객체지향 프로그래밍을 할 때 이런 원칙들을 생각하면서 해야겠구나"라는 정도로만 이해하고 넘어가셔도 무방할 것 같습니다.

 

혹시 내용에 오류가 있다면 댓글로 알려주세요!

References

[1] 조용주, 고급객체지향프로그래밍

[2] Eric Freeman, Head Frist Design Patterns

 

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

5. Singleton Pattern  (0) 2023.11.13
4. Observer Pattern  (0) 2023.11.07
3. Strategy Pattern  (0) 2023.10.29
2-1. SOLID(S.O.L.I.D.)  (0) 2023.10.16
1. Software Design Pattern  (0) 2023.10.06