스프링/MVC

[MVC 기초] 객체 지향의 특징과 SOLID 원칙 + SPRING이 어떻게 다형성을 이용해 기존 한계를 극복했는가(좋은 객체지향 설계)

nomoreFt 2022. 3. 24. 23:25

스프링 핵심 기능 (객체지향 프로그래밍을 도와준다.)

  • 객체 지향 특징
  1. 추상화
  2. 캡슐화
  3. 상속
  4. 다형성

--> 유연하고 변경이 용이하게 만들기 때문에, 대규모 소프트웨어 개발에 많이 사용된다.
-> 객체들의 모임으로 각각 객체는 메시지를 주고받고, 데이터를 처리할 수 있다.(협력)

컴포넌트 단위로 갈아끼우면서 쉽고 간단하게 조립. ( 다형성!!)

실전 다형성

역할 (interface) / 구현 (impl)으로 구분.

역할에만 맞으면 뭐든 구현은 교체 가능하다. (자동차 역할 -> 자동차, 뮤지컬 특정 역할 -> 어떤 배우)
유연, 변경 용이하다는 의미이다.

  • 사용자(클라이언트)는 역할(인터페이스)만 알면 되어서, 구현 대상의 내부 구조, 변경, 구현 대상 자체 변경에도 영향을 받지 않는다.
  • --> ex)자동차 오너가 자동차를 바꿔도 운전을 잘하고, 엔진이랑 엑셀이 어떻게 작동하는지 몰라도 되는 것과 같다.
    따라서, interface를 구현하고, 구현체를 만든다.
  • 자바의 다형성 구현
    @Override를 통해, 오버라이딩 된 메서드가 실행되기 때문에, 구현체가 실행이 된다.
    필요한 구현체를 선택시, 해당 구현체의 메서드가 실행되기 때문에 다형성이 사용된다.
  • -> 따라서 부모 interface 객체로 자식들을 new 하여 받을 수 있으나, 자식은 반대로 안된다. (구현체는 역할로 교체가 되지만, 구현체끼리는 독립적이어야 한다.)
    클라이언트를 변경하지 않고, 서버의 구현 기능을 '유연' 하게 변경 가능하다는 의미다.
    (인터페이스를 구현한 구현체 인스턴스를 실행 시점에 유연하게 변경할 수 있기 대문에)

스프링은 객체지향의 꽃 다형성을 극대화 시켜준다.

IOC, DI들은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원한다.
(레고 블럭 조립처럼 구현을 편하게 변경 가능)

  • 그렇다면, 이 스프링 프레임워크로 좋은 객체지향 프로그램을 만드려면?
  • SOLID 법칙을 따르자.

SOLID 법칙

SRP 단일 책임 원칙 (Single Responsibility principle)

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

한 클래스 내의 비즈니스 로직이 변경되면, 그 클래스 내부만 변경하면 되지
그것이 사용되는 나머지 class들에서 추가적으로 기능들을 수정하지 않아도 되게 개발해야 한다는 의미이다.

# 관심사의 분리
한 클래스는 작동만 어떻게 구현할지만 생성하면 된다. 어떤 객체를 사용할지는 생각하지 않아야 한다. interface로 필요한 기능을 사용하면 실 객체, 구현체는 Spring, Config 등으로 넣어서 업무를 분담해야 한다.
이런 것을 관심사를 분리했다고 한다. 

OCP 개방-폐쇄 원칙 (Open Close principle)

확장에는 열려 있으나, 변경에는 닫혀 있어야 한다.
-> 사전에 JAVA 다형성을 사용하여 클라이언트가 DIP를 지켜야 한다.
-> 그렇다면 관심사가 분리되어 구현체 주입을 외부 설정에서 해주기 때문에, 구현체 교체에 클라이언트 코드 변경이 필요하지 않다.

(interface를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현, but 기존 코드의 변경은 하지 않는다.)
다형성 을 사용함
Spring IOC,DI로 클라이언트가 직접 구현 클래스를 선택하는 것이 아닌, 별도의 객체를 생성, 연관관계를 맺어주는 조립 설정자가 있으면 (스프링 빈 컨테이너 등) 클라이언트 코드 변경 없이 자동으로 주입 된다.
Spring IOC,DI가 OCP 원칙을 지키기 위해(기존 코드 변경 없이 조립하기 위해) 존재한다.

Spring을 통해 DIP 의존관계 역전 원칙이 잘 지켜지면, 프로그램은 추상체(interface)에만 의존하게 되어,
구현체는 생성하여 역할에 맞게 교체만 해주면 되어서 구현체(Impl) 확장에는 무한정 가능하나,
클라이언트 코드 변경은 필요 없다.*변경에 닫혀있다. 라는 의미이다.

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

interface가 있으면, 구현체는 interface에 정의된 메서드의 기능을 정확히 구현해야 한다.
LSP가 잘 지켜져야 클라이언트가 구현체가 변경되어도 interface만 보고 기능을 사용할 수 있게 된다.

(ex/ 자동차 역할 인터페이스면 엑셀 기능은 모든 차가 동일해야 한다.)

ISP 인터페이스 분리 원칙 (Interface segregation principle)

역할을 잘 구분해서 interface를 잘 구별해놓을수록 좋다.
ex) 사용자 인터페이스 -> 운전자, 정비사로 분리
ex) 자동차 -> 운전, 정비로 분리
세세하게 덩어리를 나눌 수록, 인터페이스가 명확해지고 구현체 교체시에 영향이 적어진다.

DIP 의존관계 역전 원칙 (Dependency inversion principle)

프로그래머는 '추상화에 의존해야지 구체화에 의존하면 안된다.' -> 의존관계 주입을 이용하여 가능케 했다.

memberService가 memberRepository interface만 알면 되고, 여러 종류의 impl들은 몰라도 된다는 의미이다.
ex) 운전자는 자동차가 무슨 역할인지만 알면 되지, K3에 대해서만 구체적으로 알 필요가 없다. (다른 차로 교체하기 힘듬)

DIP 가 추상화(Interface)에만 의존해야하는데, 구체화에 의존하여 위법한 경우

//추상화에도 의존, 구체화에도 의존 (DIP 위반)
    //구현체가 변경될 때 마다 Client 코드를 변경해야하기 때문에 OCP도 위반
    private final MemberRepository memberRepository = new MemoryMemberRepository();

image

-> 의존이란? 그냥 사용된다는 의미. ex)MemberRepository m = new JdbcRepository() 시에 사용자는 memberRepository, JdbcRepository에 의존한다.


따라서, 다형성 만으로는 OCP, DIP를 지킬 수 없기 때문에 (클라이언트의 코드 변경 최소화, 실제 new로 의존성 감소를 위함)

Spring은

  • DI(Dependency Injection) : 의존관계, 의존성 주입
  • DI 컨테이너 제공
    을 통해, 클라이언트의 코드 변경 없이 기능 확장, interface 위주로 구현체 교체하듯이 개발이 가능하게 하였다.

결론

-> 기존 JAVA에서는 다형성만으로 충분한 객체지향설계 (SOLID 원칙을 지키며)가 힘들어서,
SPRING을 만들어 DI,IOC, 컨테이너 등의 도입을 통해 클라이언트의 변경은 최소로 하면서 부품 갈아 끼듯이
구현체 확장(interface로 Impl 찍기), 클라이언트 변경 최소화 (Interface만 알면 처리 가능)가 가능케 하였다.

public class MemberServiceImpl implements MemberService{

    //DIP를 위해 interface에만 의존한 상태
    //Spring 빈 컨테이너에게 의존성 주입을 해달라고 요청한 객체이다.
    @Autowired
    private final MemberRepository memberRepository 
   }

-> 인터페이스로 구현체를 신경쓰지 않고 먼저 개발을 시도할 수 있다는 것도 큰 장점이 된다. (구현기술이 바뀌면 구현체만 바꾸면 되기
때문)