SOLID : 좋은 객체지향 설계의 5가지 원칙

반응형

개요

좋은 객체지향 설계의 5가지 원칙

다음 5가지를 의미한다. 앞글자만 따서 SOLID 라 부른다.

  • SRP (Single Reponsibility Principle) 단일 책임 원칙
  • OCP (Open Closed Principle) 개방 폐쇄 원식
  • LSP (Liskov Substitution Principle) 리스코프 치환 원칙
  • ISP (Interface Segregation Principle) 인터페이스 분리 원칙
  • DIP (Dependency Inversion Principle) 의존관계 역전 원칙

 

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

  • 한 클래스는 하나의 책임만 가져야 한다.
  • 한 클래스는 단 한가지의 변경 이유만을 가져야 한다.
    • '책임' 이라는 개념의 정의를 통해 적절한 클래스 크기를 제시한다.
        하나의 책임 이라는 의미는 '변경을 위한 이유'로 정의한다. 
        책임은 상황에 따라 다르다. 클 수도 있고 작을수도 있다.


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

  • 소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에는 열려있어야 하지만 변경에는 닫혀 있어야 한다.
    • 한 군데를 변경한 것이 의존적인 모듈에서 단계적인 변경을 불러일으키지 않도록 해야한다.
  • 어설픈 추상화를 피하는 일은 추상화 자체만큼이나 중요하다.
    • 프로그램에서 자주 변경되는 부분에만 추상화를 적용하도록 해야한다.
  • 스트래티지패턴의 클라이언트는 OCP 를 따른다.
  • 다형성. 인터페이스를 통한 역할과 구현의 분리

 

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

  • 서브타입(subtype) 은 그것의 기반 타입(base type)으로 치환 가능해야 한다.
    • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀수 있어야 한다. 
    • 기반 타입으로 표현된 모듈을 수정 없이도 확장 가능하게 만드는, 서브타입의 치환 가능성을 말한다.
      따라서 기반 타입의 계약사항은 명시적으로 강제되지 않은 경우, 코드에서 분명하고 뚜렷해야 한다.
  • LSP는 OCP를 가능하게 하는 주요 요인 중 하나이다.

 

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

  • 클라이언트가 자신이 사용하지 않는 메소드에 의존하도록 강제되어서는 안된다.
    • 자신이 사용하지않는 메소드에 의존하면 변경에 취약하다. 즉 모든 클라이언트 간의 의도하지 않은 결합을 불러일으킨다.
  • 클라이언트 분리는 인터페이스 분리를 의미한다. 
    • 클라이언트가 자신이 사용하는 인터페이스에 영향을 끼치기 때문이다.
    • 인터페이스가 더 명확해지고, 대체 가능성이 높아진다.
  • 비대한 클래스는 클라이언트들 간에 기이하고 해가 되는 결합도를 유발한다. 한 클라이언트가 이 비대한 클래스에 변경을 가하면, 모든 나머지 클래스가 영향을 받게 된다. 그러므로 클라이언트는 자신이 실제로 호출하는 메소드에만 의존해야 하는데, 그러려면 이 비대한 클래스의 인터페이스를 클라이언트 고유의 인터페이스 여러개로 분해해야 한다. 그러면 호출하지 않는 메소드에 대한 클라이언트의 의존성을 끊고 클라이언트가 서로 독립적으로 되게 만들 수 있다.

 

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

  • 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다. 둘 모두 추상화에 의존해야 한다.
  • 추상화는 구체적인 사항에 의존해서는 안된다. 구체적인 사항은 추상화에 의존해야 한다.
    • "구체화에 의존하면 안된다. 추상화에 의존해야 한다."
    • 구체 클래스에 의존하지 말고 인터페이스에 의존해야 한다.
  • 전통적인 절차 지향 프로그래밍 방식은 정책이 구체적인 것에 의존하는 의존성 구조를 만든다.
    이런 경우 정책은 구체적인 사항의 변경에 따라 같이 변하기 때문에 불행한 일이다. 객체 지향 프로그래밍은 이런 의존성 구조를 역전시켜 구체적인 사항과 정책이 모두 추상화에 의존하고, 대개 그 클라이언트가 서비스 인터페이스를 소유하게 만든다.

 

■ 생각해볼만한 주제

다음과 같은 경우를 생각해보자

예로 회사의 감가상각 비용처리 규정을 정률법으로 하고 있었는데, 세법에 의한 변경사항이 발생하여 정액법으로 변경하게 되었다.

class CostRuleService { 
    CostRuleRepository fixedRate = new FixedRateCostRuleRepository(); // 기존코드
    CostRuleRepository fixedPrice = new FixedPriceCostRuleRepository(); // 변경코드
}

 

CostRuleService 클라이언트가 구현 클래스 (FixedPriceCostRuleRepository) 를 직접 선택하고 있다.

CostRuleService 는 인터페이스 (CostRuleRepository) 에 의존하지만, 구현클래스도 동시에 의존하고 있다.

 

즉, 인터페이스도 의존하고 구체화에도 의존하고 있으므로 DIP 를 위반하고 있다.

변경이 발생함에 따라 구현 객체를 변경하려면 클라이언트 코드를 변경해야한다.

인터페이스를 이용하여 분명 다형성을 사용했지만, 클라이언트도 변경하게되므로 OCP 원칙을 위반하고 있다.

  • 정리하면
    • 객체지향의 핵심은 다형성이다.
    • 다형성 만으로는 쉽게 부품을 갈아 끼우듯이 개발할 수 없다.
    • 다형성 만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경된다.
    • 다형성만으로는 OCP, DIP 를 준수할 수 없다.

 

이 문제를 어떻게 해결해야할까? 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다.

 

반응형