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

권영태·2024년 1월 2일
0

스프링

목록 보기
13/18


출처 : 스프링 핵심 원리 - 기본편

SRP 단일 책임 원칙

Single responsibility principle

  • 한 클래스는 하나의 책임만 가져야 한다.
  • 중요한 기준은 바로 변경. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것

예를 들어 다음과 같은 post 클래스가 있다고 가정한다.

class Post {
  // 글 제목
  getTitle() { ... }
    
  // 글 내용
  getDesc() { ... }
    
  // DB에서 글 가져오기
  getPost() { ... }
    
  // DB에서 글 삭제하기
  deletePost() { ... }
}

Post 클래스는 Post Entity와 Post DB에 대한 두 접근이 가능하므로 2개의 책임을 갖고 있다.
그렇기에 SRP 원칙을 위반했고 이는 다음과 같이 고쳐 SRP 원칙을 지킬 수 있다.

class Post{
  // 글 제목
  getTitle() { ... }
    
  // 글 내용
  getDesc() { ... }
}

class PostDb{
  // DB에서 글 가져오기
  getPost() { ... }
    
  // DB에서 글 삭제하기
  deletePost() { ... }
}

OCP 개방-폐쇄 원칙

Open/closed pricniple

  • 소프트웨어 요소는 확장은 열려 있고, 변경은 닫혀 있어야 한다.
  • 여기서 확장은 새로운 기능 추가를, 변경은 수정을 의미한다.
  • 다형성을 활용하면 원칙을 지킬 수 있다.
public class OrderServiceImpl implements OrderService {
 //    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
     private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
 }

OrderServiceImple 클래스는 할인 정책을 담당하는 DiscountPolicy 인터페이스를 의존한다.
할인 정책이 변경될 때 DiscountPolicy의 구현체를 변경해야 됨에 따라 OrderServicImpl 클래스의 변경도 일어난다.
즉, 클라이언트 코드도 함께 변경해야 하고 이는 OCP 원칙 위반이다.

public class OrderServiceImpl implements OrderService {
     private final DiscountPolicy discountPolicy;
     
     public OrderServiceImpl(DiscountPolicy discountPolicy) {
         this.discountPolicy = discountPolicy;
     }
public class AppConfig {
     public OrderService orderService() {
         return new OrderServiceImpl(new FixDiscountPolicy());
	}
}

위 코드는 AppConfig 클래스를 통해 인터페이스 DiscountPolicy의 구현체를 주입 받는다.
이렇게 되면 할인 정책이 변경될 때, OrderServiceImpl에 직접적인 수정을 하지 않고 AppCofig 클래스만 변경하면 돼서 확장은 열려 있고 변경은 닫힌 OCP 원칙을 지킬 수 있게 된다.


LSP 리스코프 치환 원칙

Liskov substitution principal

  • 프로그램 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 하는데 즉, LSP 원칙은 다형성을 지원하기 위한 원칙이다.

예를 들어 자동차 인터페이스의 엑셀은 앞으로 가라는 기능인데 하위 클래스에서 이를 뒤로 가게 구현한다면 이는 LSP 원칙 위반이다. 속도가 빠르든 느리든 인터페이스 규약에 맞게 전진하도록 구현해야 한다.


ISP 인터페이스 분리 원칙

Interface segregation principle

  • 특정 클라이언트를 위한 인터페이스는 여러 개가 범용 인터페이스 하나보다 낫다.
  • 자동차 인터페이스는 운전 인터페이스 + 정비 인터페이스로 분리할 수 있고,
    사용자 클라이언트는 운전자 클리어언트 + 정비사 클라이언트로 분리할 수 있다.
  • 만약, 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않는다.
  • ISP 원칙을 따르면 인터페이스가 명확해지고, 대체 가능성이 높아진다.

DIP 의존관계 역전 원칙

Dependency inversion principle

  • '추상화에 의존해야지, 구체화에 의존하면 안 된다.' 의존성 주입은 이 원칙을 따르는 방법 중 하나.
  • 즉, 구현 클래스에 의존하지 말고 인터페이스에 의존하라는 뜻.
  • 역할(Role)에 의존하게 해야 한다는 것
public class OrderServiceImpl implements OrderService {
 //    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
     private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
 }

앞서 OCP 원칙을 위반한다는 예제 코드다. 사실 위 코드는 OCP 원칙뿐만 아니라 DIP 원칙도 위반하고 있다.
OrderServiceImpl 클래스는 할인 정책 인터페이스 DiscountPolicy의 구현체인 FixDiscountPolicy 또는 Rate DiscountPolicy를 의존하고 있기 때문이다.

public class OrderServiceImpl implements OrderService {
     private final DiscountPolicy discountPolicy;
     
     public OrderServiceImpl(DiscountPolicy discountPolicy) {
         this.discountPolicy = discountPolicy;
     }
public class AppConfig {
     public OrderService orderService() {
         return new OrderServiceImpl(new FixDiscountPolicy());
	}
}

하지만, OCP 원칙을 지킬 수 있도록 고친 위 코드는 OCP뿐만 아니라 추상화에만 의존하기 때문에 DIP 원칙도 지키고 있다.


정리

  • 객체 지향의 핵심은 다향성
  • 하지만 다형성만으로는 쉽게 부품을 갈아 끼우듯이 개발할 수 없다.
  • 다형성만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경된다.
  • 다형성만으로는 OCP, DIP를 지킬 수 없으며 뭔가 더 필요하다.

생각 정리

객체 지향을 공부하고 들어보기만 했던 좋은 객체 지향 설계의 5가지인 SOLID를 공부했다.
아직 용어가 익숙지 않아 헷갈리지만, 다형성이 이 아님을 확실하게 깨달을 수 있는 시간이었다.
지난 해커톤 때와 현재 진행 중인 졸업작품에서 Spring을 사용하면서 직접적으로 구현체를 의존시켰고 심지어 추상화 작업도 하지 않았다. 지난날들의 안일함을 반성하고 앞으로 SOLID를 지키며 코딩해야겠다.

profile
GitHub : https://github.com/dudxo

0개의 댓글