본문 바로가기
디자인 패턴(Design Pattern)

SOLID 원칙

by Volka 2024. 2. 25.
  • 로버트 마틴이 자기 책에서 5가지 원칙들을 소개했음.
  • 당연히 유연한 사고로 받아들여야지 원칙에 얽매이면 이상해진다.
  • 단일책임 → 개방/폐쇄 → 리스코프 → 인터페이스 분리

Single Responsibility Principle (단일 책임 원칙)

클래스는 한 가지 이유로 변경되어야 한다.

  • 각각의 클래스가 프로그램이 제공하는 기능의 한 부분을 책임지도록 하라.
  • 그 후 이 책임을 완전히 캡슐화하여 클래스 내부에 숨겨라
  • 해당 원칙의 주목적은 복잡성을 줄이는 것이다.

Open/Closed Principle (개방/폐쇄 원칙)

클래스들은 확장에는 열려있어야 하지만 변경에는 닫혀 있어야 한다.

  • 해당 원칙의 주목적은 새로운 기능을 구현할 때 기존 코드가 깨지지 않도록 하는 것이다.
  • Open(개방) 예시
    • 클래스를 확장할 수 있을 때
    • 자식 클래스를 생성할 수 있을 때
    • 기초 행동을 오버라이드 하고 새로운 메서드나 필드를 추가하는 등 원하는 모든 작업을 수행할 수 있을 때
  • Closed(폐쇄) 예시
    • 일부 프로그래밍 언어의 final과 같은 특수한 키워드를 사용하여 클래스의 추가 확장을 제한하는 경우.
    • 동시에 클래스가 다른 클래스에 의해 사용될 준비가 100% 되어 있다면 해당 클래스는 closed(폐쇄) 또는 완료되었다고 할 수 있다.
    • 이 때 사용될 준비가 되었다는 것은 인터페이스가 명확하게 정의되어 있으며 미래에 변경되지 않는다는 뜻.

Liskov Substitution Principle (리스코프 치환 원칙)

클래스를 확장할 때 클라이언트 코드를 손상하지 않고 자식 클래스의 객체들을 부모 클래스의 객체들로 교체(치환) 할 수 있어야 한다.

  • 이는 자식 클래스가 부모 클래스의 행동과 계속 호환되어야 함을 의미한다.
  • 메소드를 오버라이드할 때 기초 행동을 다른 행동들로 완전히 교체하는 대신 확장하라.

리스코프 치환 원칙은 자식 클래스가 과거에 부모 클래스의 객체들과 함께 작동할 수 있었던 코드와 여전히 호환되는지를 예측하는데 도움이 되는 일련의 검사들이라고 생각할 수 있다.

이 개념은 라이브러리와 프레임워크를 개발할 때 매우 중요하다.

왜냐하면 직접 접근하거나 변경할 수 없는 다른 사람들의 코드가 제공한 클래스들을 사용할 것이기 때문.

여러 방식으로 해석이 가능한 다른 디자인 원칙들과는 달리, 리스코프 치환 원칙은 자식 클래스들, 그리고 특히 그들의 메서드들에 대한 일련의 형식적인 요구사항을 갖는다.

리스코프 치환 원칙 요구사항

  1. 자식 클래스의 메서드의 매개변수 타입들은 부모 클래스의 메서드의 매개변수 타입들보다 더 추상적이거나 추상화 수준이 같아야 한다.
    • 예시
      • 좋은 코드 : 매개 변수로 들어오는 타입보다 더 상위 개념으로 추상화가 되어있거나 추상화 수준이 동등 하다면 뭐가 들어오던 전부 대응 가능하다.
        • ex) Super : feed(Cat c) → Sub : feed(Animal c)
      • 나쁜 코드 : 타입을 하위 개념으로 구체화 할 시 해당 타입 밖에 못 받기 때문에 클라이언트 코드와 연결 시 문제가 생긴다.
        • ex) Super : feed(Cat c) → Sub : feed(BangalCat c)
  2. 자식 클래스의 메서드의 반환 유형은 부모 클래스의 메서드의 반환 유형의 하위유형이거나 일치해야 한다.
    • 반환 유형(타입)에 대한 요구 사항들은 매개변수 타입에 대한 요구 사항과 정반대이다.
    • 즉, 반환 타입은 구체화
  3. 자식 클래스의 메서드는 기초 메서드에서 던질거라 예상되지 않는 예외 유형을 던져서는 안된다.
    • 예외 유형들은 기초 메서드가 이미 던질 수 있는 예외 유형들의 하위 유형이거나 그 유형들과 일치하여야 한다.
    • 해당 규칙은 클라이언트 코드의 try-catch 블록들이 기초 메서드가 던질 가능성이 있는 특정한 예외 타입들을 대상으로 한다는 사실에서 비롯된다.
    • 예외처리가 잘 되어있지 않아 예상치 못한 예외는 앱 전체를 충돌시킬 수 있다.
  4. 자식 클래스는 사전 조건들을 강화해서는 안된다.
    • 하나의 예로 수퍼 클래스의 기초 메소드에 특정 매개변수가 있고 자식 클래스가 해당 메서드를 오버라이드하고 특정 조건을 추가하여 예외를 던지는 식의 사전 조건들을 강화하게 된다면 해당 메서드에 잘 작동하던 클라이언트 코드는 해당 자식 클래스의 객체와 작업하기 시작하면 망가진다.
  5. 자식 클래스는 사후 조건들을 약화해서는 안된다.
    • 예를 들어 데이터베이스와 함께 작동하는 메서드가 있는 클래스가 있다고 가정해볼 때 클래스의 메서드는 값을 반환할 때 항상 열려있는 모든 데이터베이스 연결을 닫아야 한다.
      • 하지만 해당 클래스의 자식 클래스를 생성한 후 데이터베이스 연결을 재사용할 수 있도록 해당 연결을 열린 상태로 유지하도록 자식 클래스를 변경했을 경우 클라이언트는 어떤 의도로 그렇게 변화됐는지 전혀 모를 수 있다.
      • 따라서 클라이언트는 메서드들이 모든 연결을 닫을 것으로 예상하기 때문에 메서드를 호출한 직후에 프로그램을 종료하여 시스템을 유령 데이터베이스 연결들로 오염시킬 수 있다.
  6. 부모 클래스의 불변속성들은 반드시 보존되어야 한다.
  7. 자식 클래스는 부모 클래스에 있는 비공개 필드의 값을 변경해서는 안 된다.
    • 자바의 경우 리플렉션 사용하면 접근 가능
    • 파이썬이나 JS는 비공개 멤버들을 전혀 보호하지 않는다.

Interface Segregation Principle (인터페이스 분리 원칙)

클라이언트들은 자신이 사용하지 않는 메서드들에 의존하도록 강요되어서는 안 된다.

  • 클라이언트 클래스가 불필요한 행동을 구현할 필요가 없으므로 인터페이스는 적당히 작게 만들어라.
  • 세분화되고 구체적인 인터페이스들로 나누어 설계하고 인터페이스 구현시킬 클래스에 세부적으로 나눈 인터페이스들을 조합하여 사용하면 된다.
  • 구현 시킨 인터페이스가 크다면 변경 시 영향도가 너무 크다.
  • 다만, 인터페이스가 이미 상당히 구체적이라면 더 이상 나누지 말아라. 인터페이스가 많아질수록 코드는 더 복잡해진다는 사실을 잊지 말고 균형을 유지해라.

Dependency Inversion Principle (의존관계 역전 원칙)

상위 계층 클래스들은 하위 계층 클래스들에 의존해서는 안된다. 둘 다 추상화에 의존해야 한다. 추상화는 세부 정보들에 의존해서는 안되며 세부 정보들이 추상화들에 의존해야 한다.

  • 일반적으로 소프트웨어를 디자인할 때는 클래스를 두 계층으로 분류할 수 있다.
    • 하위 계층 클래스
      • 디스크와의 작업, 네트워크를 통한 데이터 전송, DB 연결 등과 같은 기본 작업을 구현한다.
    • 상위 계층 클래스
      • 하위 계층 클래스들이 무언가를 하도록 지시하는 복잡한 비즈니스 로직을 포함한다.

하위 계층 클래스들을 먼저 디자인 후 상위 계층 클래스를 디자인 하는 경우는 새로운 시스템의 프로토타입을 개발할 때 매우 일반적이다.

이 시점에서는 하위 계층 기능들이 구현되지 않았거나 명확하지 않기 때문에 상위 계층에서 무엇이 가능한지 확신할 수 없기 때문이다.

다만, 이러한 접근 방식을 사용하면 비즈니스 로직 클래스들이 원시(primitive) 하위 계층 클래스들에 의존하게 되는 경향이 있다.

  • 따라서 의존관계 역전 원칙은 이러한 관계의 방향을 바꾸자고 제안한다.
    1. 상위 계층 클래스가 의존하는 하위 계층 작업의 인터페이스를 되도록 비즈니스 용어를 사용해 설명해야 한다.
      1. 예를 들어 비즈니스 로직은 openFile(), readBytes() 와 같은 메서드를 호출하는 대신 openReport(file) 라는 명칭의 메서드를 호출해야 한다. 이러한 인터페이스들도 상위 계층 인터페이스들로 간주 된다.
    2. 구상 하위 계층 클래스들 대신 이러한 인터페이스에 의존하는 상위 계층 클래스들을 만들 수 있다.
      1. 이 의존관계는 원래 의존관계보다 훨씬 더 약할 것이다.
    3. 하위 계층 클래스들이 이러한 인터페이스들을 구현하면 이들은 비즈니스 로직 계층에 의존하게 되어 원래 의존관계의 방향이 역전된다.
      1. 의존관계 역전 원칙은 종종 개방/폐쇄 원칙과 함께 진행된다.
      2. 하위 계층 클래스를 확장하여 기존의 클래스들을 손상하지 않고 다른 비즈니스 로직 클래스들과 함께 사용할 수 있다.

'디자인 패턴(Design Pattern)' 카테고리의 다른 글

GoF 23 디자인 패턴  (0) 2024.02.25
디자인 패턴이란?  (0) 2024.02.25