객체지향 설계의 5원칙

좋은 소프트웨어 설계는 응집도를 높이고 결합도를 낮추어야 한다. -> 결합도가 높다는 것은 객체의 변경이 생겼을 때 수정해야 하는 객체가 많다는 것을 의미한다. -> 이러한 설계는 결론적으로 유지보수를 쉽게하기 위한 설계이다.

1. SRP(Single Responsibility Principle) : 단일 책임 원칙

  • 어떤 클래스를 변경하는 이유는 오직 하나뿐이어야 한다. (단일 책임)

    • 예를 들어, 남자라는 객체의 다양한 책임(남편, 아들, 직장직원, 예비군) 이라는 다양한 책임이 있을 때에 객체지향의 설계에서는 나쁜 냄시가 난다고 말한다.

    • 이런 경우 책임을 분리하는 것이 단일 책임 원칙이라고 부른다. (위의 경우 책임에 따라서 4개의 클래스로 나눈다)

  • 단일 책임 원칙이 지켜지지 않은 경우를 살펴보자.

    • 예를 들어서 남자는 반드시 군대를 가야하고, 여자는 반드시 군대를 가지 않는다고 가정하자.

    • 이 때, 사람 클래스에 군번이라는 속성이라면 여자는 필요하지도 않은 속성을 가져야 한다.

    • 이 경우 사람 클래스를 남자 클래스, 여자 클래스로 분리해 각 객체에 맞는 책임을 질 수 있도록 해야한다.

  • 아래와 같이 메서드 내에서 분기에 따라서 결과가 다르다면 그 또한 단일 책임 원칙이 지켜지지 않은 것이다.

class 강아지 { 
    final static Boolean 수컷 = true; 
    final static Boolean 암컷 = false; 
    Boolean 성별; 
    
    void 소변보다() {
        if(this.성별 == 수컷) {
            // 한쪽 다리 들고 소변
        } else {
            // 뒷다리 두 개를 굽혀 앉은 자세로 소변 
        }
    }
}
  • 위와 같은 경우 아래와 같이 상속을 통해 리팩토링 할 수 있다.

  • 단일 책임 원칙은 추상화와 관련이 깊다 -> 추상화는 각 도메인에 맞도록 객체를 설계하는 즉, 단일 책임 원칙이라고 볼 수 있다.

2. OCP(Open Closed Principle) : 개방 폐쇠 원칙

  • 자신의 확장에는 열려있고, 주변의 변화에 대해서는 닫혀 있어야 한다.

    • 개방 : 기능의 확장에는 열려 있어야 한다.

    • 폐쇄 : 기존 코드의 수정 없이 기능 확장이 가능해야 한다.

  • 예시

    • 차종이 바뀐다고 해서, 운전하는 근본적인 방법이 바뀌는 것은 아니다.

    • JDBC 는 JDBC 인터페이스를 통해서, 데이터페이스가 변경되더라도 문제 없이 연결할 수 있다.

    • Java 는 목적파일(.class) 을 통해서 운영체제가 변경되더라도 문제 없이 실행시킬 수 있다.

    • 편의점에서 직원은 물건 정리와 판매가 주 행동이기 때문에, 사람이 바뀐다고 해서 근본적으로 바뀌는 것은 아니다.

    • 배역이 하는 대사와 행동은 배우가 바뀐다고 해서 바뀌지 않는다.

  • 개방 폐쇄 원칙을 지키는 방법은 인터페이스를 통해서 특정 메서드(행동) 을 구현하게 강제하는 것이다.

    • 이렇게 되면 각 클래스들은 책임에 맞는 메서드를 구현하게 된다.

  • 개방 폐쇄 원칙을 무시하면 유연성, 재사용성, 유지보수성의 이점을 얻을 수 없다.

3. LSP(Liskov Substitution Principle) : 리스코프 치환 원칙

  • 서브타입은 언제나 기반타입으로 교체 될 수 있어야 한다.

    • 부모 클래스와 자식 클래스의 행위의 일관성이 있어야 한다! (Java Collection Framework)

  • 올바른 리스코프 치환 원칙

    • 하위 클래스 is a kind of 상위 클래스 - 하위 분류는 상위 분류의 한 종류이다.

    • 구현 클래스 is able to 인터페이스 - 구현 분류는 인터페이스 할 수 있어야 한다.

    • 분류도와 같은 객체 구조는 리스코프 치환 원칙을 따른다.

      • ex, 동물 뽀로로 = new 펭귄();

  • 잘못된 리스코프 치환 원칙

    • 자식 클래스로 부모 클래스의 내용을 상속하는데, 기존 코드에서 보장 하던 조건을 수정하거나 적용시키지 않아서, 기존 부모 클래스를 사용하는 코드에서 예상하지 않은 오류를 발생 시킨다면 문제다.

      • 부모 클래스와 자식 클래스의 행위의 일관성이 사라졌다..

    • 계층도/조직도와 같은 객체 구조는 리스코프 치환 원칙을 위반한다.

      • ex, 아버지 춘향이 = new 딸();

4. ISP(Interface Segregation Principle) : 인터페이스 분리 원칙

  • 다중 책임을 가지는 클래스에 대해서 각각의 책임에 따른 인터페이스를 상속 받게 만들어서 특정 책임에 요구되는 경우, 특정 인터페이스로 캐스팅해 사용하는 방법이다.

    • 클라이언트 입장에서 자신이 사용하지 않는 메서드와 의존 관계를 맺어서는 안된다!

  • 다중 책임을 가지는 클래스에 대한 개선 방법으로 다중 책임 클래스에 대해 해결하는 방법은 2가지가 있다.

    • 단일 책임 원칙

    • 인터페이스 분리 원칙

  • 일반적인 경우 단일 책임 원칙을 지키는 것이 좋을 해결책이다.

    • 코드가 간결하다!

  • 상위 클래스는 풍성할수록 좋다.

    • 빈약한 상위 클래스의 경우, 형변환이 계속적으로 발생해 상속의 장점을 누리지 못한다.. (코드가 더러워진다)

  • 인터페이스는 꼭 필요한 행위만 넣어주는 것이 좋다.

    • 인터페이스는 그 역할에 충실한 최소한만 공개하는 것이 좋다.

    • 그래야만 특정 역할에 맞는 기능만 가질 수 있다.

5. DIP(Dependency Inversion Principle) : 의존 역전 원칙

  • 고차원 모듈(자동차)은 저차원 모듈(스노우 타이어)에 의존하면 안된다. 두 모듈 모두 추상화 된 것에 의존해야 한다.

    • 추상화(인터페이스)에 의존해야 하지 구체화(클래스)된 것에 의존해서는 안된다.

    • 예를 들면 자동차가 스노우타이어에 의존하는 것이 아닌, 추상화된 타이어의 의존하고, 타이어의 인스턴스가 스노우타이어가 되는 설계가 옳은 설계이다.

  • 개방 폐쇄 원칙과 비슷하게 느껴질 것이다.

    • 이렇듯 하나의 해결책을 찾으면, 그 안에 여러 설계 원칙이 녹아 있는 경우가 많다.

Last updated