dev.jaieve 공부기록

[Spring] 다형성과 SOLID 본문

Back/Springboot

[Spring] 다형성과 SOLID

제이브 2022. 2. 26. 23:21

1. 다형성(polymorphism)

  • 실제 세상을 비유로 들었을 때, 세상은 역할구현으로 구분할 수 있다.
    • 키보드라는 개념과 다양한 모델들, 마우스라는 개념과 다양한 제품 모델들 과 같은 세상의 표준 인터페이스들
    • 공연 무대 남주 및 여주(역할)에 더블캐스팅된 배우들(구현)
    • 운전자(역할)와 자동차(역할). 그리고 다양한 자동차 모델(구현)

a. 다형성 장점

  • 프로그램을 사용하는 클라이언트는 대상의 역할만 알면된다.
  • 클라이언트는 구현 대상의 내부 구조를 몰라도 된다.
  • 클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다.
  • 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.

b. 객체지향언어인 자바의 다형성

  • 역할 = Interface / 구현 = 구현객체(Class implements Interface)
  • 객체를 설계할 때부터 역할과 구현을 명확히 분리하고, 역할을 먼저 부여한 다음, 그 역할을 수행하는 구현객체를 만든다.

c. @Overriding in java

  • 다형성 개념을 이용하여 인터페이스를 구현한 객체는 실행 시점에서 유연하게 제 2의 구현객체로 변경할 수 있다.
  • 다형성의 본질은 클라이언트를 변경하지 않고, 서버의 구현기능을 유연하게 변경할 수 있는 것이다.

d. 한계

  • 역할(interface)가 변하면 클라이언트, 서버 모두에 큰 변경이 발생하기 때문에 인터페이스를 안정적으로 잘 설계하는 것이 중요하다.

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

SRP

  • 한 클래스는 하나의 책임만 가져야 한다.
  • 하나의 책임이라는 것은 모호해서, 클 수도 있고, 작을 수도 있다. 문맥과 상황에 따라 정의가 달라진다.
  • 중요한 기준은 변경이다. 변경이 있을 때 파급 효과가 적으면 책임이 분명해지기 때문에, 변경에 의한 연쇄작용에서 자유로워질 수 있다.
  • ex. UI변경, 객체의 생성과 사용을 분리

OCP

  • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
  • 다형성을 활용하면 역할(인터페이스)를 구현한 새로운 클래스(구현체)를 만들어 새로운 기능을 구현할 수 있다.
  • 변경을 위한 비용은 가능한 줄이고, 확장을 위한 비용은 가능한 극대화해야 한다.
  • 요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소에는 수정이 일어나지 않고, 기존 구성요소를 쉽게 확장해서 재사용한다.

클린코드를 읽고 공부하며 정리했던 예제코드

class PaymentController {
    @RequestMapping(value="/api/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);
    }
}
새로운 카드사가 추가된다면?
class PaymentController {
    @RequestMapping(value="/api/payment", method=RequestMethod.POST)
    public void pay(@RequestBody CardPaymentDto.PaymentRequest req) {
        if(req.getType() == CardType.SHINHAN){
            shinhanCardPaymentService.pay(req);
        } else if(req.getType() == CardType.WOORI) {
            wooriCardPaymentService.pay(req);
        }
    }
}

이 예제코드는 확장에 유연하지 않은 코드이다. 다음과 같이 바꾸면 OCP 원칙을 지키게 된다.

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

class PaymentController {
    public void pay(@RequestBody CardPaymentDto.PaymentRequest req){
        finalCardPaymentServicecardPaymentService =
                cardPaymentFactory.getType(req.getType());
        cardPaymentService.pay(req);
    }
}

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

LSP(리스코프 치환 원칙)

  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • 다형성에서 하위 클래스는 인터페이스 규약(접근제한자, 예외 포함)을 다 지켜야 한다. 이러한 다형성의 원칙은 인터페이스 구현체를 믿고 사용하기 위해서 필요하다.
  • ex. 자동차 인터페이스의 엑셀 기능은 앞으로 가는 것인게 구현체에서 뒤로 간다면 LSP 위반!

ISP

  • 특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 낫다.
    • 자동차 인터페이스 → 운전 인터페이스, 정비 인터페이스로 분리
    • CarRepository → DriveService, RepairService
    • 사용자 클라이언트 → 운전자 클라이언트, 정비사 클라이언트로 분리
    • UserService → DriverService, RepairService
  • 인터페이스를 여러개로 나누면 명확해지고, 대체 가능성이 높아진다.

DIP

  • “추상화에 의존해야지, 구체화에 의존하면 안된다”는 원칙을 따르기 위해 의존성 주입을 활용한다. 즉, 구현 클래스에 의존하지 말고 인터페이스에 의존하라는 뜻이다.
  • 객체 세상은 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다.1. 다형성(polymorphism)1. 다형성(polymorphism)1. 다형성(polymorphism)

3. 객체 지향 설계와 스프링

스프링은 DI컨테이너를 제공함으로써 다형성 + OCP, DIP를 가능하게 만들어준다. 덕분에 클라이언트 코드의 변경없이 기능 확장, 쉽게 부품을 교체하듯이 개발이 가능하다.

스프링의 진짜 핵심

  • 스프링은 자바 언어 기반의 프레임워크이다.
  • 자바 언어의 가장 큰 특징 객체지향언어
  • 스프링은 객체지향언어가 가진 강력한 특징을 살려내는 프레임워크
  • 스프링은 좋은 객체지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크
반응형