SOLID Principle


Why design patterns and SOLID principle?

  • 코드의 작성, 유지보수, 변경, 확장을 쉽게 할 수 있도록 코드 작성하기 전부터 코드의 구조를 잘 정해두면 도움이 된다.
  • 코드로 구현하고 싶은 기능의 유형에 따라 도움이 되는 구조에는 반복되는 패턴이 존재하는데, 이를 디자인 패턴이라고 부른다.
  • 디자인 패턴은 본질적으로 이해하기 좋은, 그래서 수정-확장하기 좋은 코드를 짜는 것을 목적으로 하기에, 개별적인 디자인 패턴을 배우기 전에 디자인패턴의 일반 원리인 SOLID principle 을 알아보자.

1. Single Responsibility

모듈 클래스 함수는 하나의 프로그램 기능 중 하나의 파트에 대해서만 responsibility를 가져야 한다.

  • 예를 들어서, 고양이 클래스 안에 먹기 함수, 걷기 함수가 포함되어있는 것은 고양이의 특성을 구현한다는 점에서 자연스럽지만 상태출력 또는 상태기록 함수가 고양이 클래스 안에 존재하는 것은 single responsibility에 어긋난다고 볼 수 있다.
  • 이 경우, 고양이 클래스 안에 먹기 함수, 걷기 함수만 넣어두고, represent 함수 하나만 추가해서 그 리턴값을 클래스 외부에서 출럭 혹은 기록할 수 있도록 하면, 고양이 클래스는 고양이의 내재적 특성만 구현하는 single responsibility를 만족한다.

2. Open/Closed Principle

코드는 확장에는 열려있고, 수정에는 닫혀있도록 짜야한다.

  • open/closed principle을 위반한 예시: 동물의 종류를 입력 받아, 그 종류에 따라 출력을 달리하는 함수를 생각해보자. (이미지의 hey 함수) 이 경우 동물의 종류를 확장할 때마다, 함수를 수정해줘야 한다. 즉, 기능을 확장할 때마다 매번 기존 코드를 수정해줘야하는 불편함이 발생한다.
  • 해결 방법? 클래스 상속, 인터페이스 클래스, 추상 클래스 등을 사용해서 다른 동물을 추가할 때, 새로운 클래스를 만들어 코드를 확장할 수 있게 만들자. 이렇게 하면, 기존 코드에 손을 댈 필요가 없다.
    1. Animal 부모 클래스을 만들고,
    2. Cat, Dog 클래스가 이를 상속하도록 구조를 짜놓았다고 해보자.
    3. 이 상태에서 Sheep이나 Cow를 추가확장하고 싶으면, Sheep 클래스와 Cow 클래스를 덧붙이기만 하면 된다.
    4. 이러면, 기존의 짜놓은 코드를 변경할 필요가 없게 된다!!

3. Liskov Substitution Principle

$S$ 가 $T$ 의 subtype 일 때, type $T$ 의 object 는 type $S$ 의 object로 치환 할 수 있다.

  • 상위 클래스의 객체를 이용해 코드를 짠 경우에, 해당 객체를 하위 클래스의 객체로 바꿔서 선언하더라도 (치환하더라도), 코드가 잘 돌아가야 한다.
  • 이를 위해선, 부모클래스의 속성/메서드가 자식클래스의 속성/메서드에 포함되어 있어야 한다.
  • 잘못 된 예로는,
    1. 자식클래스가 속성이나 메서드 오버라이딩 할 때, 타입/리턴값 갯수를 다르게 바꾸는 경우,
    2. 메서드 오버라이딩할 때, 부모클래스의 의도와 다른 기능을 구현한 경우가 있다.

4. Interface Segregation

Client를 사용하지도 않을 메소드에 의존하게 만들어서는 안된다.

  • 다시 말해, 큰 인터페이스를 작은 인터페이스로 나누는 것이 좋다.
  • 인터페이스(혹은 추상 클래스)는 내부 메서드를 선언만 하고, 이를 상속받은 클래스가 실제 메서드를 구현하는 클래스를 말한다.
  • 따라서.. 인터페이스는 클래스를 만들기 위한 틀을 제공한다고 볼 수 있다. 그럼 한 그릇에 많은 걸 담고 싶은 욕망 때문에, 하나의 인터페이스가 여러가지 일을 하고 싶게 만들 수 있는데 (예를 들어 잠수함의 기능과 자동차의 기능을 선언한 인터페이스)
  • 이 경우, 인터페이스를 이용해 만든 클래스 (자동차 기능을 구현한 소나타 클래스) 에는 필요도 없는 잠수함의 기능이 들어가야 한다. 따라서, 자동차 인터페이스잠수함 인터페이스를 따로 만들어 사용하는 것이 좋다.
  • 그럼 잠수 기능이 있는 자동차 만들고 싶으면 어떡하지? 하는 호기심이 생길 수 있는데, 이 경우에는 자동차 인터페이스잠수함 인터페이스를 둘다 상속 받는 식으로 해결할 수 있다.

5. Dependency Inversion Principle

  1. 모듈이 계층화 되어있을 때, 높은 레벨의 모듈은 낮은 레벨의 모듈을 직접 포함해 의존성이 생기면 안된다.
  2. 어떻게? 높은 레벨의 모듈과 낮은 레벨의 모듈 모두 interface나 추상 클래스같은 abstractions에 의존하도록 만들면 된다
  3. 이 때 사용되는 인터페이스는 구현 클래스에 의존하면 안되고, 구현클래스가 인터페이스에 의존하도록 만들어야 한다.
  • 동물원 안에 고양이강아지가 살고 있으면,
  • 이를 구현할 때, 동물원 클래스가 고양이 클래스와 강아지 클래스를 포함하도록 직관적으로 코드를 짤 수 있다.
  • 다시 말해, 동물원 클래스가 여러 동물들에 대해 의존성이 생기도록 코드를 짤 수 있다. 이는 상위 레벨의 개념이 하위 레벨 개념을 포함하는 측면에서 꽤 자연스러운 구조라고 할 수 있다.
  • 그런데 여기서 문제가 발생한다. 동물원 에 다른 동물들 , 같은 애들이 더 입주하면, 매번 동물원 클래스를 수정해줘야 하는 문제가 생긴다.
  • 여기에서 Dependency Inversion 을 해주면 문제가 해결된다.
  • 구체적으로는, 동물 추상클래스를 만들고, 동물원과 여러 동물들이 모두 동물 추상클래스에 의존하게 만들면,
  • 새로운 동물을 추가할 때, 기존의 코드를 전혀 수정할 필요가 없게 된다 !!

Reference

Notes Mentioning This Note

There are no notes linking to this note.

Table of Contents


Share on: