Data Structure

SOLID - Design Pattern

아르비스 2021. 3. 3. 15:21

SOLID

1. 5가지 원리의 핵심내용

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

1.정의

 

 

 

 

  • 위키피디아 검색내용

    • 객체 지향 프로그래밍에서 단일 책임 원칙(single responsibility principle)이란 모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 함을 일컫는다. 클래스가 제공하는 모든 기능은 이 책임과 주의 깊게 부합해야 한다.

    • 예시 )

      예를 들어서 보고서를 편집하고 출력하는 모듈을 생각해 보자. 이 모듈은 두 가지 이유로 변경될 수 있다. 첫 번째로 보고서의 내용 때문에 변경될 수 있다. 두 번째로 보고서의 형식 때문에 변경될 수 있다. 이 두 가지 변경은 하나는 실질적이고 다른 하나는 꾸미기 위한 매우 다른 원인에 기인한다. 단일 책임 원칙에 의하면 이 문제의 두 측면이 실제로 분리된 두 책임 때문이며, 따라서 분리된 클래스나 모듈로 나누어야 한다. 다른 시기에 다른 이유로 변경되어야 하는 두 가지를 묶는 것은 나쁜 설계일 수 있다.

      한 클래스를 한 관심사에 집중하도록 유지하는 것이 중요한 이유는, 이것이 클래스를 더욱 튼튼하게 만들기 때문이다. 앞서 든 예를 계속 살펴보면, 편집 과정에 변경이 일어나면, 같은 클래스의 일부로 있는 출력 코드가 망가질 위험이 대단히 높다.

  • 정의

    • 작성된 클래스는 하나의 기능만 가지며 클래스가 제공하는 모든 서비스는 그 하나의 책임(변화의 축)을 수행하는 데 집중되어 있어야 한다는 원칙입니다. 이는 어떤 변화에 의해 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 함을 의미합니다.

  • 장점

    1. SRP원리를 적용하면 무엇보다도 책임 영역이 확실해지기 때문에 한 책임의 변경에서 다른 책임의 변경으로의 연쇄작용에서 자유로울 수 있습니다.

    2. 책임을 적절히 분배함으로써 코드의 가독성 향상, 유지보수 용이해 집니다.

    3. 객체지향 원리의 대전제 격인 OCP원리뿐 아니라 다른 원리들을 적용하는 기초가 됩니다.

 

2.적용방법

 

- 위키피디아 검색내용

  • 객체간의 기능 이동

    • 객체 디자인에서 가장 기본이 되는 것 중의 하나는 책임을 어디에 둘지를 결정하는 것이다.

      이런 문제를 Move MethodMove Field를 사용해서 동작을 옮김으로써 간단히 해결할 수 있다.(둘다 사용해야 할 필요가 있다면 먼저 Move Field를 사용하고 그 다음에 Meve Method를 사용한다.)

    • 클래스는 종종 너무 많은 책임으로 비대해진다. 이런 경우에 Extract Class를 사용하여 책임의 일부를 떼어 놓는다.

    • 클래스가 하는 일이 별로 없다면 Inline Class를 사용하여 다른 클래스로 통합시킨다.

    • 실제로는 다른 클래스가 사용되고 있다면 Hide Delegate를 써서 이사실을 숨기는 것이 유용하다.

    • 때로는 위임 크래스를 숨기는 것은 계속해서 소유 클래스의 인터페이스를 변경하는 결과를 초래한다.

      Remove Middle Man를 사용할 필요가 있다.

- 적용방법

  • 여러 원인에 의한 변경 :

    • Extract Class를 통해 혼재된 각 책임을 각각의 개별 클래스로 분할하여 클래스 당 하나의 책임만을 맡도록 하는 것입니다.

    • 책임만 분리하는 것이 아니라 분리된 두 클래스간의 관계의 복잡도를 줄이도록 설계하는 것입니다.

    • 클래스들이 유사하고 비슷한 책임을 중복해서 갖고 있다면 Extract Superclass를 사용할 수 있습니다. 각각의 클래스들의 공유되는 요소를 부모 클래스로 정의하여 부모 클래스에 위임하는 기법입니다.

  • 산탄총 수술 :

    • Move FieldMove Method를 통해 책임을 기존의 어떤 클래스로 모으거나, 이럴만한 클래스가 없다면 새로운 클래스를 만들어 해결할 수 있습니다. 즉 산발적으로 여러 곳에 분포된 책임들을 한 곳에 모으면서 설계를 깨끗하게 합니다. 즉 응집성을 높이는 작업입니다.

     

 


  • Extract Class ?

    • 정의 :두 개의 클래스가 해야 할 일을 하나의 클래스가 하고 있는 경우 새로운 클래스를 만들어서 관련 있는 필드와 메소드를 예전 클래스에서 새로운 클래스로 옮기는 기법.

  • Move Method ?

    • 정의 :메소드가 자신이 정의된 클래스보다 다른 클래스의 기능을 더 많이 사용하고 있다면이 메소드를 가장 많이 사용하고 있는 클래스에 비슷한 몸체를 가진 새로운 메소드를 만들어고, 이전 메소드는 간단한 위임으로 바꾸거나 완전히 제거하는 기법.

  • Move Field ?

    • 정의 :필드가 자신이 정의된 클래스보다 다른 클래스에 의해 더 많이 사용되고 있다면, 타겟 클래스(target class)에 새로운 필드를 만들고 기존 필드를 사용하는 모든 부분을 변경하는 기법.

     

3.적용이슈

 

  • 클래스는 자신의 이름이 나타내는 일을 해야 합니다. 올바른 클래스 이름은 해당 클래스의 책임을 나타낼 수 있는 가장 좋은 방법입니다. 각 클래스는 하나의 개념을 나타내어야 합니다. 사용되지 않는 속성이 결정적 증거입니다. 무조건 책임을 분리한다고 SRP가 적용되는 건 아닙니다. 각 개체 간의 응집력이 있다면 병합이 순 작용의 수단이 되고 결합력이 있다면 분리가 순 작용의 수단이 됩니다.

 

B. OCP (개방폐쇄의 원칙: Open Close Principle)

1.정의

 

 

- 위키피디아

  • '소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.'는 프로그래밍 원칙이다.

  • 개방-폐쇄 원칙의 두 가지 속성

    1. 확장에 대해 열려 있다.

    2. 수정에 대해 닫혀 있다.

  • 개방-폐쇄 원칙과 객체 지향 언어의 관계

    • 개방-폐쇄 원칙은 객체 지향 프로그래밍의 핵심 원칙이라고 할 수 있다. 개방-폐쇄 원칙을 따르지 않는다고 해서 객체 지향 언어(Java, C++ 등)을 구현이 불가능한 것은 아니지만 이 원칙을 무시하고 프로그래밍을 한다면, 객체 지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성 등을 결코 얻을 수 없다. 따라서 객체 지향 프로그래밍 언어에서 개방-폐쇄 원칙은 반드시 지켜야할 기본적인 원칙이다.

 

2.적용방법

 

  1. 변경(확장)될 것과 변하지 않을 것을 엄격히 구분합니다.

  2. 이 두 모듈이 만나는 지점에 인터페이스를 정의합니다.

  3. 구현에 의존하기보다 정의한 인터페이스에 의존하도록 코드를 작성 합니다.

 

3.적용이슈

 

  1. 확장되는 것과 변경되지 않는 모듈을 분리하는 과정에서 크기 조절에 실패하면 오히려 관계가 더 복잡해 질 수 있습니다. 설계자의 좋은 자질 중 하나는 이런 크기 조절과 같은 갈등 상황을 잘 포착하여 (아깝지만) 비장한 결단을 내릴 줄 아는 능력에 있습니다.

  2. 인터페이스는 가능하면 변경되어서는 안 됩니다. 따라서 인터페이스를 정의할 때 여러 경우의 수에 대한 고려와 예측이 필요합니다. 물론 과도한 예측은 불필요한 작업을 만들고, 보통 이 불필요한 작업의 양은 상당히 크기 마련입니다. 따라서 설계자는 적절한 수준의 예측 능력이 필요한데, 설계자에게 필요한 또 하나의 자질은 예지력입니다.

  3. 인터페이스 설계에서 적당한 추상화 레벨을 선택해야 합니다. 우리는 추상화라는 개념에 '구체적이지 않은' 정도의 의미로 약간 느슨한 개념을 갖고 있습니다. 그래디 부치(Grady Booch)에 의하면 ‘추상화란 다른 모든 종류의 객체로부터 식별될 수 있는 객체의 본질적인 특징’이라고 정의하고 있습니다. 즉, 이 '행위'에 대한 본질적인 정의를 통해 인터페이스를 식별해야 합니다.

 

 

C. LSP (리스코브 치환의 원칙: The Liskov Substitution Principle)

1.정의

 

 

- 위키피디아

  • 정의 : 컴퓨터 프로그램에서 자료형

    가 자료형

    하위형이라면 필요한 프로그램의 속성(정확성, 수행하는 업무 등)의 변경 없이 자료형

    의 객체를 자료형

    의 객체로 교체(치환)할 수 있어야 한다는 원칙이다.

  • 예시 :

    를 자료형

    의 객체

    에 대해 증명할 수 있는 속성이라 하자. 그렇다면

    의 하위형이라면

    는 자료형

    의 객체

    에 대해 증명할 수 있어야 한다.

  • 리스코프의 '행동적 하위형'이라는 개념은 '가변 객체의 치환성'이라는 개념을 정의한다. 즉 자료형

    가 자료형

    의 하위형이라면, 프로그램에서 자료형

    의 객체는 프로그램의 속성을 변경하지 않고 자료형

    의 객체로 교체할 수 있다.

- 블로그 발췌

  • 정의 :

    • 이 원칙은 5가지 원칙 중에서 좀처럼 쉽게 이해 되지 않는 원칙의 하나로 LSP라는 이름에서는 도저히 원칙에 대한 내용을 도출 할 수 없는 원칙입니다.

      LSP를 한마디로 한다면, “서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다.”라고 할 수 있습니다. 즉, 서브 타입은 언제나 기반 타입과 호환될 수 있어야 합니다. 달리 말하면 서브 타입은 기반 타입이 약속한 규약(public 인터페이스, 물론 메소드가 던지는 예외까지 포함됩니다.)을 지켜야 합니다.

      상속은 구현상속(extends 관계)이든 인터페이스 상속(implements 관계)이든 궁극적으로는 다형성을 통한 확장성 획득을 목표로 합니다. LSP원리도 역시 서브 클래스가 확장에 대한 인터페이스를 준수해야 함을 의미합니다. 다형성과 확장성을 극대화 하려면 하위 클래스를 사용하는 것보다는 상위의 클래스(인터페이스)를 사용하는 것이 더 좋습니다.

 

2.적용방법

 

  1. 만약 두 개체가 똑 같은 일을 한다면 둘을 하나의 클래스로 표현하고 이들을 구분할 수 있는 필드를 둡니다.

  2. 똑같은 연산을 제공하지만, 이들을 약간씩 다르게 한다면 공통의 인터페이스를 만들고 둘이 이를 구현 합니다. (인터페이스 상속)

  3. 공통된 연산이 없다면 완전 별개인 2개의 클래스를 만듭니다.

  4. 만약 두 개체가 하는 일에 추가적으로 무언가를 더 한다면 구현 상속을 사용합니다.

 

3.적용이슈

 

  1. 혼동될 여지가 없고 트레이드 오프를 고려해 선택한 것이라면 그대로 둡니다.

  2. 다형성을 위한 상속 관계가 필요 없다면 Replace with Delegation을 합니다. 상속은 깨지기 쉬운 기반 클래스 등을 지니고 있으므로 IS-A관계가 성립되지 않습니다. LSP를 지키기 어렵다면 상속대신 합성(composition)을 사용하는 것이 좋습니다.

  3. 상속 구조가 필요 하다면 Extract Subclass, Push Down Field, Push Down Method 등의 리팩토링 기법을 이용하여 LSP를 준수하는 상속 계층 구조를 구성 합니다.

  4. IS-A관계가 성립한다고 프로그램에서 까지 그런것은 아닙니다. 이들간의 관계 맺음은 이들의 역할과 이들 사이에 공유하는 연산이 있는지, 그리고 이들 연산이 어떻게 다른지 등을 종합적으로 검토 해 봐야 합니다.

  5. Design by Contract(“서브 클래스에서는 기반 클래스의 사전 조건과 같거나 더 약한 수준에서 사전 조건을 대체할 수 있고, 기반 클래스의 사후 조건과 같거나 더 강한 수준에서 사후 조건을 대체할 수 있다.”)적용: 기반 클래스를 서브 클래스로 치환 가능하게 하려면 받아들이는 선 조건에서 서브 클래스의 제약사항이 기반 클래스의 제약 사항보다 느슨하거나 같아야 합니다. 만약 제약조건이 더 강하다면 기반 클래스에서 실행되던 것이 서브 클래스의 강 조건으로 인해 실행되지 않을 수도 있기 때문입니다. 반면 서브 클래스의 후 조건은 같거나 더 강해야 하는데, 약하다면 기반 클래스의 후 조건이 통과시키지 않는 상태를 통과시킬 수도 있기 때문입니다.

 

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

1.정의

 

 

- 위키피디아

  • 정의 :클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙이다.

    인터페이스 분리 원칙은 큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시킴으로써 클라이언트들이 꼭 필요한 메서드들만 이용할 수 있게 한다. 이와 같은 작은 단위들을 역할 인터페이스라고도 부른다.

- 블로그 발췌

  • 정의 :ISP원리는 한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다는 원리입니다.

    즉 어떤 클래스가 다른 클래스에 종속될 때에는 가능한 최소한의 인터페이스만을 사용해야 합니다.

     

2.적용방법

 

  1. 클래스 인터페이스를 통한 분리

    • 클래스의 상속을 이용하여 인터페이스를 나눌 수 있습니다. 이와 같은 구조는 클라이언트에게 변화를 주지 않을 뿐 아니라 인터페이스를 분리하는 효과를 갖습니다. 하지만 거의 모든 객체지향 언어에서는 상속을 이용한 확장은 상속받는 클래스의 성격을 디자인 시점에 규정해 버립니다. 따라서 인터페이스를 상속받는 순간 인터페이스에 예속되어 제공하는 서비스의 성격이 제한 됩니다.

  2. 객체 인터페이스를 통한 분리

    • 위임(Delegation)을 이용하여 인터페이스를 나눌 수 있습니다. 위임이란, 특정 일의 책임을 다른 클래스나 메소드에 맡기는 것입니다. 만약 다른 클래스의 기능을 사용해야 하지만 그 기능을 변경하고 싶지 않다면, 상속 대신 위임을 사용 합니다.

 

3.적용이슈

 

  1. 기 구현된 클라이언트에 변경을 주지 말아야 합니다.

  2. 두 개 이상의 인터페이스가 공유하는 부분의 재사용을 극대화 합니다.

  3. 서로 다른 성격의 인터페이스를 명백히 분리 합니다.

 

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

1.정의

 

 

- 위키피디아

  • 정의 :상위 계층(정책 결정)이 하위 계층(세부 사항)에 의존하는 전통적인 의존 관계를 반전(역전)시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있다. 이 원칙은 다음과 같은 내용을 담고 있다.

    이 원칙은 '상위와 하위 객체 모두가 동일한 추상화에 의존해야 한다'는 객체 지향적 설계의 대원칙을 제공한다.

- 블로그 발췌

  • 정의 :의존 관계의 역전 Dependency Inversion 이란 구조적 디자인에서 발생하던 하위 레벨 모듈의 변경이 상위 레벨 모듈의 변경을 요구하는 위계관계를 끊는 의미의 역전입니다. 실제 사용 관계는 바뀌지 않으며, 추상을 매개로 메시지를 주고 받음으로써 관계를 최대한 느슨하게 만드는 원칙입니다.