[F-lab 모각코 챌린지 48일차]TIL

JeongheeKim·2023년 7월 18일

TIL

목록 보기
48/66

학습계획


  • 개구리책 5장 - SOLID원칙

Today I Learned


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

목적 : 객체간의 응집도(chohesion)는 높이고, 결합도(coupling)는 낮추는 것

  • 응집도
    • 하나의 모듈 내부에 존재하는 구성 요소들의 기능적 관련성
    • 응집도가 높은 모듈은 하나의 책임에 집중 하고 독립성이 높아져 재사용 및 유지보수가 좋다
  • 결합도
    • 모듈간 상호 의존 정도

SOLID는 객체지향 프로그램을 구성하는 속성, 메서드, 클래스, 객체, 패키지, 모듈, 라이브러리, 프레임워크, 아키텍처 등 다양한곳에 적용 된다. 막상 SOLID가 적용되었는지 아닌지 애매모호하거나 보는 사람의 관점에 따라 다르게 해석이 될 수 있는 소지가 있다.

SRP : 단일 책임 원칙 (Single Resposibility Priciple)

  • 예) 하나의 클래스는 하나의 책임을 가진다
    • 하지만 하나의 책임의 기준이 애매함 ⇒ 변경이 있을 때 파급효과가 적으면 단일 책임 원칙을 잘 따르는 것
  • 하나의 속성이 여러 의미를 갖는 경우도 단일 책임 원칙을 지키지 못하는것
  • 객체지향 4대원칙 중 SRP와 관계가 깊은 것은 추상화(모델링)

OCP : 개방-폐쇄 원칙(Open/closed Priciple)

  • 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있다. = 자신의 확장에는 열려있어야하고 주변의 변화에는 닫혀있어야한다.
  • 예) 데이터 베이스 프로그래밍의 JDBC 데이터 베이스의 밴더사가 변경 되어도 connection 정보 수정빼고는 변경 부분이 없음 데이터 베이스 변경이라는 주변의 변화에 닫혀있고, 데이터 베이스를 교체한다는 자신의 확장에는 열려있다.

객체를 생성하고, 연관관계를 맺어주는 별도의 조립장치 역할

  • spring container, DI, IoC

LSP: 리스코프 치환 원칙(Liskov substitution Priciple)

  • 상위클래스의 타입은 하위 클래스의 타입으로 대체 될 수 있어야한다.
  • 인터페이스에서 정의한 기능을 무조건 따라야 함
    • 예)자동차 인터페이스 악셀기능은 앞으로 간다는 기능으로 정의됨
    • car interface를 구현한 테슬라 구현체가 엑셀 메서드 실행 시 뒤로가면 리스코프 치환 원칙을 위배하게됨

ISP : 인터페이스 분리 원칙

  • 책임분리에 대한 문제의 두가지 해결책 중 하나(SRP, ISP)
    • 특별한 경우가 아니라면 단일 책임 원칙을 적용하는것이 낫다.
  • 특정 클라이언트를 위한 인터페이스 여러개가 하나의 덩치가 큰 인터페이스 보다 낫다.
  • 자동차 인터페이스 → 운전, 정비 인터페이스로 나눔
  • 운전자, 정비사 클라이언트가 정비 인터페이스에서 변경이 일어나면 운전자 구현체에 영향주지 않음
  • 인터페이스 최소 주의 원칙
    • 인터페이스를 통해 메서드를 외부에 제공할때는 역할에 충실한 최소한의 메서드만 제공해라

DIP : 의존관계 역전 원칙(Dependency Inversion Principle)

  • 클라이언트가 추상화(인터페이스)에 의존해야함
  • 상위모듈은 하위 모듈의 구현에 의존해서는 안된다. 하위 모듈이 상위 모듈에 정의한 추상타입에 의존해야한다.

예제)

추상화에 의존하지 않은 경우

예시로 결제수단 서비스를 호출하는 컨트롤러이다. 결제 서비스 호출 시 PaymentController는 ShinhanCardPaymentService에 강하게 의존한다.

동일한 로직의 다른 카드사의 호출이 필요할 경우 아래 코드는 확장성이 떨어지게 된다.

class PaymentController {
    @RequestMapping(value = "/dip/anti/payment", method = RequestMethod.POST)
    public void pay(@RequestBody ShinhanCardDto.PaymentRequest req){
        shinhanCardPaymentService.pay(req);
    }
}
class ShinhanCardPaymentService {
    public void pay(ShinhanCardDto.PaymentRequest req) {
        shinhanCardApi.pay(req);
    }
}

컴파일 단계에서는 PaymentController는 CardPaymentService를 바라보지만 런타임에서는 getType으로 얻은 객체 CardPaymentService의 구현체 ShinhanCardPaymentService를 바라보게된다.

class PaymentController {
    @RequestMapping(value = "/payment", method = RequestMethod.POST)
    public void pay(@RequestBody CardPaymentDto.PaymentRequest req) {
        final CardPaymentService cardPaymentService = cardPaymentFactory.getType(req.getType());
        cardPaymentService.pay(req);
    }
}

public interface CardPaymentService {
    void pay(CardPaymentDto.PaymentRequest req);
}

public class ShinhanCardPaymentService implements CardPaymentService {
    @Override
    public void pay(CardPaymentDto.PaymentRequest req) {
        shinhanCardApi.pay(req);
    }
}

참고

Spring 예제로 보는 SOLID DIP - Yun Blog | 기술 블로그

https://vagabond95.me/posts/about-ioc-dip-di/

1개의 댓글

comment-user-thumbnail
2023년 7월 18일

좋은 글 감사합니다!

답글 달기