콘텐츠로 이동

디자인 원칙

애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다.

  • 바뀌는 부분을 따로 캡슐화시키면 나중에 바뀌지 않는 부분에는 영향을 주지 않은 채로 그 부분만 고치거나 확장할 수 있다.
  • 즉, 코드를 변경하는 과정에서 의도하지 않은 일이 일어나는 것을 줄이면서 시스템의 유연성을 향상시킬 수 있습니다.
  • 관련 디자인 패턴 : 스트래티지 패턴

구현이 아닌 인터페이스 맞춰서 프로그래밍 한다.

  • 실행시에 쓰이는 객체가 코드에 의해서 고정되지 않도록, 어떤 상위 형식에 맞춰서 프로그래밍함으로써 다형성을 활용해야 합니다.
  • 관련 디자인 패턴 : 스트래티지 패턴

상속보다는 구성을 활용한다.

  • 구성(composition)을 이용하여 시스템을 만들면 유연성을 크게 향상시킬 수 있습니다.
  • 관련 디자인 패턴 : 스트래티지 패턴

서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

  • 느슨하게 결합하는 디자인을 사용하면 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있습니다. 객체 사이의 상호의존성을 최소화시킬 수 있기 때문이죠.
  • 관련 디자인 패턴 : 옵저버 패턴

OCP(Open-Closed Principle)

  • 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.
  • 기존 코드는 건드리지 않은 채로 확장을 통해 새로운 행동을 간단히 추가할 수 있다면 매우 유연하고 튼튼한 디자인을 만들 수 있습니다.
  • 관련 디자인 패턴 : 데코레이터 패턴

의존성 뒤집기 원칙(Dependency Inversion Principle)

  • 추상화된 것에 의존하도록 만들어라. 구상 클래스에 의존하지 않도록 한다.
  • 가이드 라인
  • 어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 않는다.
  • 구상 클래스에서 유도된 클래스를 만들지 않는다.
  • 베이스 클래스에 이미 구현되어 있던 메소드를 오버라이드하지 않는다. => 이미 구현되어 있는 메소드를 오버라이드한다는 것은 애초부터 베이스 클래스가 제대로 추상화된 것이 아니었다고 볼 수 있습니다.
  • 관련 디자인 패턴 : 팩토리 패턴

최소 지식 원칙(= 데메테르의 법칙(Law of Demeter))

  • 객체 사이의 상호작용은 될 수 있으면 아주 가까운 "친구" 사이에서만 허용하는 것이 좋다.
  • 시스템을 디자인 할 때, 어떤 객체든 그 객체와 상호작용을 하는 클래스의 개수에 주의해야 한다.
  • 이 원칙을 잘 따르면 여러 클래스들이 복잡하게 얽혀서 시스템의 한 부분을 변경했을 때 다른 부분까지 줄줄이 고쳐야 되는 상황을 미리 방지할 수 있습니다.
  • 가이드라인
  • 어떻게 하면 여러 객체하고 인연을 맺는 것을 피할 수 있을까요? 어떤 메소드에서든지 다음 네 종류의 객체의 메소드만을 호출하면 됩니다.
    • 객체 자체
    • 메소드에 매개변수로 전달된 객체
    • 그 메소드에서 생성하거나 인스턴스를 만든 객체
    • 그 객체에 속하는 구성요소(인스턴스 변수에 의해 참조되는 객체)
  • 단점
  • 이 원칙을 잘 따르면 의존성을 줄일 수 있지만, 다른 구성요소에 대한 메소드 호출을 처리하기 위해 래퍼 클래스를 더 만들어야 할 수도 있습니다. ==> 복잡도, 개발시간 증가, 성능 저하
  • 관련 디자인 패턴 : 퍼사드 패턴
Q: 어떤 메소드를 호출한 결과로 리턴받은 객체에 있는 메소드를 호출할 때는 어떤 단점이 있을까요?
A: 그렇게 하면 다른 객체의 일부분에 대해서 요청을 하게 되는 것이고, 그러다 보면 직접적으로 알고 지내는 객체의 개수가 늘어나게 됩니다.
   ==> 그런 경우에 최소 지식 원칙을 따르려면 그 객체 쪽에서 대신 요청을 하도록 만들어야 합니다. 그러면 그 객체의 한 구성요소를 알고 지낼 필요도 없어지죠.(친구 수를 줄이는 데도 도움이 되고요)

   // 원칙을 따르지 않은 경우
   // station으로부터 Thermometer라는 객체를 받은 다음, 그 객체의 getTemperature() 메소드를 직접 호출합니다.
   public float getTemp() {
     Thermometer thermometer = station.getThermometer();
     return thermometer.getTemperature();
   }

   // 원칙을 따르는 경우
   public float getTemp() {
     return station.getTemperature(); // 최소 지식 원칙을 적용하여 Station 클래스에 thermometer에 요청을 해주는 메소드를 추가했습니다. 이렇게 하면 의존해야 하는 클래스의 개수를 줄일 수 있죠.
   }
  public class Car {
    Engine engine;
    // ... 기타 인스턴스 변수

    public Car() {
      // 엔진 초기화 등 인스턴스 변수 초기화 처리
    }

    public void start(Key key) {
      Doors doors = new Doors();
      boolean authorized = key.turns(); // 매개변수로 전달된 객체의 메소드 호출 : OK

      if (authorized) {
        engine.start(); // 이 객체의 구성요소의 메소드 호출 : OK
        updateDashboardDisplay(); // 객체 내에 있는 메소드 호출 : OK
        doors.lock(); // 직접 생성하거나 인스턴스를 만든 객체의 메소드 : OK
      }
    }

    public void updateDashboardDisplay() {
      // 디스플레이 갱신
    }
  }

헐리우드 원칙

먼저 연락하지 마세요. 저희가 연락 드리겠습니다.
  • 헐리우드 원칙을 활용하면 '의존성 부패(dependency rot)'를 방지할 수 있습니다.
  • 저수준 구성요소에서 시스템에 접속을 할 수는 있지만, 언제 어떤 식으로 그 구성요소들을 사용할지는 고수준 구성요소에서 결정하게 됩니다.
  • 따라서, 이 원칙은 저수준 구성요소들을 다양하게 사용할 수 있으면서도, 다른 클래스가 저수준 구성요소들에 너무 의존하지 않게 만들어주는 디자인을 구현하기 위한 기법을 제공합니다.
  • 관련 디자인 패턴 : 템플릿 메소드 패턴
의존성 부패?
[고수준 구성요소]--의존--> [저수준 구성요소] --의존--> [고수준..] --의존--> [저수준..] 과 같이 의존성이 복잡하게 꼬여있는 것을
의존성 부패라고 부릅니다. 이렇게 의존성이 부패되면 시스템이 어떤 식으로 디자인된 것인지 거의 아무도 알아볼 수 없게 됩니다.

클래스를 바꾸는 이유는 한 가지 뿐이어야 한다.

  • 어떤 클래스에서 맡고 있는 모든 역할들은 나중에 코드 변화를 불러올 수 있습니다. 역할이 두개 이상 있으면 바뀔 수 있는 부분이 두 가지 이상이 되는 것이죠.

응집도(cohesion)

  • 응집도란 한 클래스 또는 모듈이 특정 목적, 역할을 얼마나 일관되게 지원하는지를 나타내는 척도라고 할 수 있습니다.