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

참고 - 우아한형제들 김영한 개발 팀장님의 좋은 객체 지향 설계의 5가지 원칙(SOLID) 강좌

SOLID란?

클린코드로 유명한 로버트 마틴이 정리한 좋은 객체 지향 설계의 5가지 원칙

  • SRP : 단일 책임 원칙 (single resposibility principle)
  • OCP : 개방-폐쇄 원칙 (Open/closed priciple)
  • LSP : 리스코프 치환 원칙 (Liskov substitution priciple)
  • ISP : 인터페이스 분리 원칙 (Interface segregation priciple)
  • DIP : 의존관계 역전 원칙 (Despendency inversion priciple)

1. SRP 단일 책임 원칙

  • 하나의 클래스는 하나의 책임만 가져야 한다.

사실 하나의 책임이라는 말은 모호한 부분이 있다. 책임이라는 것은 클 수도, 작을 수도 있고 문맥과 상항에 따라 다를 수가 있다. 그래서 이 부분에서 설계가 잘 되었는지 판단할 수 있는 기준이 필요한데 이러한 중요한 판단의 기준은 ‘변경’이다.

  • 중요한 기준은 변경

어떠한 변경이 있을때 파금이 적으면 SRP를 잘 따른 것이고 파급이 크다면 잘 따르지 못했다고 할 수 있다. 예를 들어 UI하나를 변경하는데 SQL 코드부터 application까지 모두 고쳐야하는 상황이 생긴다면, SRP를 따르지 않고 설계한 것이다. 반대로 변경이 있을 때 하나의 클래스나 하나의 지점만 고치면 SRP를 잘 따르는 것이라고 볼 수 있다.

  • 책임의 범위를 잘 조절하는 것이 필요

책임의 범위를 너무 작게 하면 class가 너무 잘게 쪼개지고 너무 크게하면 책임이 많아져 이를 잘 조절하는 것이 객체지향의 묘미!

2. OCP 개방-폐쇄 원칙

  • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

새로운 기능을 추가할 때 기존 코드는 변경하지 않고 인터페이스를 구현한 새로운 클래스를 하나 만들어서 기능을 구현해야 한다.

  • 문제점

다형성만으로는 OCP 원칙을 지키기 힘들다.

ex) MemeberService 클라이언트가 구현 클래스를 직접 선택할 때 구현객체를 변경하려면 클라이언트 코드를 변경해야 한다. 즉 인터페이스를 구현한 새로운 클래스를 하나 만들어도 OCP 원칙을 지킬 수 없다. 이를 해결하기 위해서는 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다.(ex : Spring)

3. LSP 리스코프 치환 원칙

  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

다형성에서 하위 클래스는 인터페이스 규약을 지켜 기능적으로 보장을 해줘야 한다는 개념이다. 예를 들어 자동차 인터페이스를 상속받아 엑셀 기능을 구현할 때 엑셀기능은 앞으로 전진하도록 구현해야 한다. 만약 엑셀을 앞이 아닌 뒤로 가도록 구현한다면 컴파일은 단계에서는 문제가 없지만 엑셀은 앞으로 전진한다느 규약을 지키지 않아 LSP를 위반하는 것이다.

4. ISP 인터페이스 분리 원칙

  • 특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 낫다.

예를 들어 자동차 인터페이스를 운전자 인터페이스워 정비 인터페이스를 분리하면 client를 사용자 client와 정비 client로 분리할 수 있고, 분리된 사용자 client는 다시 운전자 client와 정비사 client로 분리할 수 있다. 이렇게 인터페이스를 분리해 사용하면 정비 인터페이스가 변해도 운전자 클라이언트에 영향을 주지 않는다. 즉 인터페이스를 특정 client를 위해 분리해 놓으면 인터페이스가 명확해지고 대체 가능성이 높아진다.

5. DIP 의존관계 역전 원칙

  • 프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 라는 원칙을 따르는 방법 중 하나이다.

구현클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻이다.

운전자는 K3에 대해서 디테일하게 알아야 하는 것이 아니라 자동차의 역할에 대해서 상세히 알아야 한다.

  • 문제점

다형성만으로는 DIP 원칙을 지키기 힘들다.

ex) OCP에서 설명한 MemberService는 인터페이스에 의존하지만, 구현 클래스도 동시의 의존하고 있어 DIP를 위반하고 있다.