객체지향설계 (SOLID)

REASON·2022년 11월 15일
0

STUDY

목록 보기
118/127

클린 소프트웨어 책에 나오는 SOLID에 대해 공부해보았다.

SOLID

객체 지향 프로그래밍 설계 다섯가지 원칙을 SOLID라고 부른다.
SRP, OCP, LSP, ISP, DIP의 앞 글자를 따서 SOLID이다.

SOLID의 다섯 가지 원칙을 살펴보면 다음과 같다.

  • 단일 책임 원칙 (Single responsibility principle)
    하나의 클래스는 하나의 책임만 가져야 한다.

  • 개방-폐쇄 원칙 (Open/closed principle)
    확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

  • 리스코프 치환 원칙 (Liskov substitution principle)
    프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

  • 인터페이스 분리 원칙 (Interface segregation principle)
    특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

  • 의존관계 역전 원칙 (Dependency inversion principle)
    프로그래머는 추상화에 의존해야 한다. (구체화에 의존하면 안된다.)

이게 무슨 말인가 싶을 정도로 교과서나 전공 서적에나 나올 법한 내용들로 이루어져있다.
아니 무슨 말을 저렇게 어렵게 하는거죠..ㅠㅠ😟 각 원칙에 대해 하나씩 자세히 살펴보기로 했다.

⭐ 단일 책임 원칙 (SRP)

클래스는 하나의 책임을 가져야 한다.
로버트 마틴에 의하면 클래스를 변경해야 하는 이유는 하나 뿐이어야 한다고 했다.
클래스로 분리하는 것이 중요한 이유는 책임이 변경의 이유가 되기 때문이다.
즉, 하나의 클래스가 하나 이상의 책임을 맡는다는 것은 클래스를 변경할 이유도 한가지 이상이 된다는 것이다.

Rectangle 클래스가 존재할 때, Rectangle 클래스는 화면에 직사각형을 그리는 메서드와 직사각형의 넓이를 계산하는 메서드를 가지고 있다고 해보자.
계산 기하학을 위한 애플리케이션과 그래픽을 위한 애플리케이션이 존재하고 이 애플리케이션은 Rectangle 클래스를 사용한다고 가정했을 때 이 설계는 Rectangle 클래스가 두 가지 책임을 하고 있기 때문에 단일 책임 원칙(SRP)에 위반하는 것이다.
Rectangle 클래스는 화면에 직사각형을 그리는 책임과 직사각형 넓이를 계산하는 책임 두가지를 맡고 있기 때문이다. 단일 책임 원칙을 위배하지 않으려면 Rectangle에서 계산하는 부분은 새로운 클래스를 만들어서 옮기는 방식 등을 통해 클래스가 하나의 책임만을 하도록 설계하는 것이 좋다.

위와 같이 애플리케이션에 따라 변경이 실제로 일어날 때만 SRP를 적용하는 것이 좋다.
불필요한 복잡성이 생길 수 있기 때문이다.

⭐ 개방 폐쇄 원칙 (OCP)

소프트웨어 개체(클래스, 모듈, 함수 등)가 확장에 대해 열려 있어야 하고
수정에 대해서는 닫혀 있어야 한다는 원칙이다.
OCP 적용이 잘 되어있다면 같은 종류의 변경은 더 이상 수정을 유발하지 않는다고 한다.
기존의 코드를 변경하지 않고 새로운 코드를 추가하는 것만으로도 충분하기 때문이다.

OCP의 중요한 속성 2가지

  1. 확장에 대해 열려있을 것.
    요구 사항이 변경될 때 새로운 것을 추가하여 모듈을 확장 시킬 수 있어야 한다.
    여기서 모듈 확장은 모듈이 하는 일을 변경함을 의미한다.

  2. 수정에 대해 닫혀있을 것.
    모듈의 행위를 확장하는 것은 소스나 바이너리 코드의 변경을 초래하지 않는다.

즉, 모듈의 소스 코드 변경 없이도 행위를 바꿀 수 있고,
모듈을 변경하지 않고도 모듈이 하는 일을 변경할 수 있어야 한다는 건데..

두 가지 속성을 살펴보면 상반되는 것처럼 느껴진다.
도대체 무엇을 의미하는 걸까? 😶

OCP는 책 내용만으로 이해하기 어려워서 다른 외부 자료를 구글링해서 찾아보고
직접 타이핑해보았다.

class Animal {
  constructor(type) {
    this.type = type;
  }
  say() {
    if (this.type === "Cat") {
      console.log("meow");
    } else if (this.type === "Dog") {
      console.log("bark");
    } else {
      throw new Error("잘못된 타입입니다.");
    }
  }
}

const navi = new Animal("Cat");
const coco = new Animal("Dog");
const mimi = new Animal("Rabbit");
navi.say();
coco.say();
mimi.say(); // error

Animal 이라는 클래스를 만들었고 내부 메서드로는 say가 있다.
인스턴스의 타입에 따라 Cat 일때는 meow를 출력하고, Dog일 때는 bark
그 외의 동물인 경우 에러를 발생시키는 코드이다.

Rabbit을 추가했지만 타입이 Cat이나 Dog가 아니기 때문에 say 메서드를 호출하면 에러가 발생한다.

Animal이 현재 Dog, Cat이 있고 Rabbit을 확장하고자 하는 것인데
이 경우 say 메서드에 Rabbit에 대한 if문을 추가해서 코드를 수정해야 한다.

개방 폐쇄 원칙에 따르면 확장은 할 수 있지만 수정은 닫혀있어야 한다.
따라서 위 코드는 OCP에 위배되는 코드라고 볼 수 있다.

OCP를 지키려면 interface추상화하는 방법을 통해 해결할 수 있다.
자바스크립트에서는 인터페이스가 지원되지 않아서 해당 부분은 코드로 작성해보진 못했지만..!

⭐ 리스코프 치환 원칙 (LSP)

서브타입은 그것의 기반 타입으로 치환 가능해야 한다. (넹..? 그게 뭐죠..)
설명만 들어보면 이게 무슨 말인가 싶은 것들이 많다.

리스코프 치환 원칙은 분류하는 것으로 이해하는 게 좋다고 한다.
즉, 하위 클래스가 상위 클래스로 바꿔서 봐도 이상하면 안된다는 것 같다.
참고한 블로그 자료에 따르면 포유류와 고래(고래는 포유류의 한 종류다.) 와 같이 올바른 상속 관계(논리적으로 맞아 떨어지는)를 의미한다고 했다. 예컨대 아버지 클래스를 상속받은 아들은 LSP에 위배되는 올바르지 못한 상속 관계라고 한다. (아들은 아버지의 한 종류이다가 되기 때문에)

고양이라는 클래스가 있을 때 고양이 클래스를 상속한 러시안 블루, 페르시안 클래스가 있다면 러시안 블루나 페르시안 클래스를 고양이 클래스로 치환하더라도 프로그램 실행에 문제가 없어야 한다는 것이다.

고양이 클래스를 강아지 클래스가 상속받았다고 해보자. 고양이 클래스에는 고양이만 할 수 있는 메서드가 존재한다면 고양이 클래스를 상속받은 강아지 클래스에서 해당 메서드는 사용할 수 없도록 exception 처리를 하게 될텐데 이때 리스코프 치환 원칙에 위배된다는 것이다.
왜냐하면 강아지 클래스를 고양이 클래스로 치환하더라도 문제가 없어야 하기 때문이다.

⭐ 인터페이스 분리 원칙 (ISP)

인터페이스 분리 원칙은 응집력이 없는 인터페이스를 필요로 하는 객체가 있다는 것을 인정하지만 클리언트는 그것을 하나의 단일 클래스로 생각해서는 안된다... 라고 로버트 마틴은 설명하고 있다.
아무리 봐도 이 책은 내용보다 말이 어려워서 이해를 못할 것 같다..

큰 인터페이스는 더 작은 단위로 분리 시키는 것이 좋다.

큰 인터페이스를 기반으로 클래스를 만들게 되면 사용하지 않는 메서드들도 함께 들어오기 때문에
분리해서 작은 인터페이스로 나누고, 필요한 인터페이스만 가져와서 클래스를 만드는 것이 좋다는 원칙이다.

⭐ 의존관계 역전 원칙 (DIP)

  • 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안된다. 둘 모두 추상화에 의존해야 한다.
  • 추상화는 구체적인 사항에 의존해서는 안 된다. 구체적인 사항은 추상화에 의존해야 한다.

의존관계 역전 원칙은 추상화를 강조하고 있었다.
하이레벨 클래스에 dependency가 증가하면 코드의 수정이나 관리가 어려워진다.
즉, 하이레벨 클래스에는 직접적인 디펜던시가 증가하도록 설계하면 안된다는 것이고
이것을 해결하기 위해서 의존관계 역전 원칙(DIP)가 필요한 것이다.
DIP에서 Abstract에 의존하라고 하는 것도 이 때문으로 볼 수 있다.

[하이레벨] - [로우레벨] ❌
[하이레벨] -> [추상화 모듈] <- [로우레벨] ⭕

하이레벨과 로우레벨 모두 추상화 클래스에 의존하도록 만들자!

이름이 너무 어려운 솔리드 원칙.. 직접 코드 짤 때 지킬 수 있을 지는 모르겠지만
이런 내용이 있었지 하고 상기 시켜보고 언젠가(?) 도움이 되지 않을까 싶다.
물론 지금은 너무 어렵게만 느껴진다..ㅎㅎㅎㅎ.. 어렵다 어려웡 💫


참고 자료
위키 백과 SOLID(객체 지향 설계)
클린 소프트웨어
디자인패턴, open Closed principles, 개방 폐쇄 원칙, Design Patterns
[Java] 객체 지향 설계란? (SOLID)
디자인패턴, 의존관계 역전, Dependency inversion , SOLID, 솔리드

0개의 댓글