# 05.객체 지향 설계 5원칙 - SOLID

  • 응집도는 높이고 결합도는 낮추라는 고전 원칙을 객체 지향의 관점에서 재정립.

    • 결합도 : 모듈(클래스) 간의 상호 의존 정도로서 결합도가 낮으면 모듈 간의 상호 의존성이 줄어들어 객체의 재사용이나 수정, 유지보수가 용이하다.
    • 응집도 : 하나의 모듈 내부에 존재하는 구성 요소들의 기능적 관련성 응집도가 높은 모듈은 하나의 책임에 집중하고 독립성이 높아져 재사용이나 기능의 수정, 유지보수가 용이하다.
  • OOD(Object Oriented Design) 의 5원칙 : SOLID

    • SRP(Single Responsibility Principle) : 단일 책임 원칙
    • OCP(Open Closed Principal) : 개방 폐쇄 원칙
    • LSP(Liskov Substitution Principle) : 리스코프 치환 원칙
    • ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
    • DIP(Dependency Inversion Principle) : 의존 역전 원칙
  • SOLID는 객체 지향 4대특성을 발판으로 하고 디자인 패턴의 뼈대이며 스프링 프레임워크의 근간.

# SRP - 단일 책임 원칙

  • 어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.

  • 객체지향의 4대 특성중 모델링을 담당하는 추상화와 관련이 깊다.

  • 애플리케이션의 경계를 정하고 추상화를 통해 클래스들을 선별하고 속성와 메서드를 설게할때 반드시 단일 책임 원칙을 고려하자.

  • 메서드가 단일 책임 원칙을 지키지 않은 경우

class 강아지 {
    final static Boolean 수컷 = true;
    final static Boolean 암컷 = false;
    Boolean 성별;

    void 소변보다() {
        if(this.성별 == 수컷) {
            // 한쪽 다리를 들고 소변을 본다.
        } else {
            // 뒷다리 두개를 굽혀 앉은 자세로 소변을 본다.
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 단일 책임 원칙을 적용해 개선한 코드
abstract class 강아지{
    abstract void 소변보다()
}

class 수컷강아지 extends 강아지 {
    void 소변보다() {
        // 한쪽 다리를 들고 소변을 본다.
    }
}

class 암컷강아지 extends 강아지 {
    void 소변보다() {
        // 뒷다리 두 개로 앉은 자세로 소변을 본다.
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# OCP - 개방 폐쇄 원칙

  • 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려있어야 하지만 변경에 대해서는 닫혀 있어야 한다. = 자신의 확장에는 열려있고, 주변의 변화에 대해서는 닫혀 있어야 한다.
  • jdbc 인터페이스라고 하는 완충장치를 사용하는 클라이언트는 데이터베이스가 오라클에서 MySQL로 바뀌더라도 connection을 설정하는 부분 외에는 따로 수정할 필요가 없다. OCP의 예 : JDBC

# LSP - 리스코프 치환 원칙

  • 하위 클래스의 인스턴스는 상위형 객체 참조 변수에 대입해 상위 클래스의 인스턴스 역할을 하는데 문제가 없어야 한다.
  • 서브타입은 언제나 자신의 기반타입(base type)으로 교체할 수 있어야 한다.
  • 객체지향에서의 상속대로 구현된 프로그램이라면 이미 리스코프 치환 원칙을 잘 지키고 있다고 할 수 있다.
    • 하위클래스 is a kind of 상위클래스 - 하위 분류는 상위 분류의 한 종류다
    • 구현클래스 is able to 인터페이스 - 구현 분류는 인터페이스할 수 있어야 한다.
  • 아버지 춘향이 = new 딸() : 딸에게 아빠의 역할을 맡기고 있다. 이상함
  • 동물 뽀로로 = new 펭귄() : 펭귄 한마리가 태어나 뽀로로라 이름을 짓고 동물의 행위(메서드)를 하게 함. 리스코프 치환 원칙 만족.
  • 하위형에서 선행 조건은 강화될 수 없다
  • 하위형에서 후행 조건은 약화될 수 없다
  • 하위형에서 상위형의 불변 조건은 반드시 유지돼야 한다

# ISP - 인터페이스 분리 원칙

  • 클라이언트는 자신이 사용되지 않는 메서드에 의존 관계를 맺으면 안된다
  • 다양한 책임을 가진 남자 클래스
    다양한 책임을 가진 남자 클래스
  • 단일 책임 원칙을 적용해 남자 클래스를 단일 책임 원칙을 가진 여러 클래스로 분리
    단일 책임 원칙을 적용해 남자 클래스를 단일 책임 원칙을 가진 여러 클래스로 분리
  • 인터페이스 분할 원칙을 적용한 남자 클래스
    인터페이스 분할 원칙을 적용한 남자 클래스
  • 결론적으로 단일책임원칙(SRP)와 인터페이스분할원칙(ISP)은 같은 문제에 대한 두가지 다른 해결책이라 볼 수 있다.
  • 프로젝트 요구사항과 설계자의 취향에 따라 단일 책임 원칙이나 인터페이스 분할 원칙 중 하나를 선택해서 설계 할 수 있다. 하지만 특별한 경우가 아니라면 단일 책임 원칙을 적용하는 것이 더 좋은 해결책이라고 할 수 있다.
  • 인터페이스 최소주의원칙 : 인터페이스를 통해 메서드를 외부에 제공할때는 최고한의 메서드만 제공하라. ex. 그림에서 남자친구 인터페이스에 사격하기() 메서드를 제공할 필요도 없고 제공해서도 안된다. 상위 클래스는 풍성할 수록 좋고, 인터페이스는 작을수록 좋기 때문. 리스코프 치환 원칙에 따라 하위 객체는 상위 객체인 척 할 수 있다.
  • 빈약한 상위클래스 vs 풍성한 하위클래스
  • 빈약한 상위 클래스를 이용한 경우 여기저기 형변환이 일어나면서 상속의 혜택을 제대로 받지 못함.
  • 인터페이스 최소주의 : 인터페이스는 그 역할에 충실한 최소한의 기능만 제공. 인터페이스는 "~할 수 있는(is able to)이라는 기준으로 만드는것이 정석."

# DIP - 의존 역전 원칙

  • 자동차와 스노우 타이어 사이의 의존 원칙
  • 의존 역전 원칙 적용 전
    의존 역전 원칙 적용 전(자주 변경되는 구체 클래스에 의존)
  • 의존 역전 원칙 적용 후
    의존 역전 원칙 적용 후
  • 자동차가 구체적인 타이어들 (스노우타이어, 일반 타이어, 광폭타이어) 이 아닌 추상화된 타이어 인터페이스에만 의존하게 함으로써 타이어가 변경돼도 자동차는 이제 그 영향을 받지 않는 형태로 구성된다.
  • 개방폐쇄원칙(OCP)를 설명할 때 나온 설명. 이렇게 하나의 해결책을 찾으면 그 안에 여러 설계 원칙이 녹아있는 경우가 많다.
  • 기존에는 스노우 타이어가 그 무엇에도 의존하지 않는 클래스였는데, 의존 역전 원칙 적용 후 추상적인것인 타이어 인터페이스에 의존하게 됐다. 바로 의존의 방향이 역전된 것이다. 그리고 자동차는 자신보다 변하기 쉬운 스노우타이어에 의존하던 관계를 중간에 추상화된 타이어 인터페이스를 추가해 두고 의존관계를 역전시키고 있다.
  • 의존 역전 원칙 : 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는 것이 의존 역전 원칙이다. 자신보다 변하기 쉬운 것에 의존하지 마라
  • 상위클래스 일수록, 인터페이스 일수록, 추상클래스 일 수록 변하지 않을 가능성이 높기에 하위 클래스나 구체 클래스가 아닌 상위클래스, 인터페이스, 추상클래스를 통해 의존하라는 것이 바로 의존 역전 원칙이다.
  • 사례 : 개방폐쇄원칙(OCP)에서 설명했던 JDBC도 해당.

# 정리 - 객체지향 세계와 SOLID

# SoC (Separation Of Concerns, 관심사의 분리).

관심이 같은 것 끼리는 하나의 객체 안으로 또는 친한 객체로 모으고, 관심이 다른 것은 가능한 따로 떨어져 서로 영향을 주지 않도록 분리. 하나의 속성, 하나의 메서드, 하나의 클래스, 하나의 모듈, 하나의 패키지에는 하나의 관심사만 들어있어야 한다. SOC를 적용하게 되면 자연스럽게 SRP, ISP, OCP에 도달하게 된다. 스프링 또한 SOC를 통해 SOLID를 극한까지 적용하고 있다.

  • SRP(단일 책임 원칙) : 어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.
  • OCP(개방 폐쇄 원칙) : 자신의 확장성에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 한다.
  • LSP(리스코프 치환 원칙) : 서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.
  • ISP(인터페이스 분리 원칙) : 클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다.
  • DIP(의존 역전 원칙) : 자신보다 변하기 쉬운 것에 의존하지 마라.

추천하는 책

  • head first design pattern
  • 토비의 스프링3.1
  • 도메인 주도 설계란 무엇인가?(인사이트)
  • 도메인 주도 설계(위키북스)