카테고리 없음

스프링 핵심 원리 기본편 정리 - 1. 객체 지향 설계와 스프링

우디혜 2021. 3. 2. 17:11

인프런 스프링 핵심 원리 기본편을 참고.

 

객체 지향 프로그래밍

각 객체들은 메시지를 주고받고 데이터를 처리할 수 있다. 

프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.

 

다형성

서로 다른 유형의 객체가 동일한 메시지에 대해 서로 다르게 반응하는 것을 의미한다. 예를 들어서 클라이언트가 새에게 fly()라는 메시지를 보내면, 새 객체가 참새든 비둘기든간에 나는 역할을 수행할 것이다. 이렇게되면 나중에 까마귀라는 객체를 새로 추가하더라도 새 객체 하위에 있기 때문에 fly()라는 역할을 그대로 수행할 수 있다. 이처럼 다형성을 활용하면 역할과 구현을 분리해서 로직을 단순하게, 유연하게 작성할 수 있다. 그만큼 유지보수도 편리해진다.

 

좀 더 일반화해서 생각해보면 클라이언트는 대상의 역할만 알면 된다. 내부 구조가 어떻게 구현되는지 변경이 되었는지에 대해서 신경쓸 필요가 없고 영향을 받지도 않는다. 

 

자바에서 다형성이란

자바에서는 상속 개념을 통해 다형성을 표현할 수 있다. 인터페이스에서는 역할을 정의하고, 해당 역할에 맞게 구현을 해놓은 결과물이 인터페이스를 구현한 클래스와 객체가 된다. 인터페이스로 구현한 객체를 실행 시점에 유연하게 변경할 수 있다는 점이 자바 언어에서 다형성을 활용할 때 얻을 수 있는 장점이다. 때문에 프로젝트 설계는 역할 즉 인터페이스를 먼저 고려하고나서 구체적인 객체를 구현하는 순서로 진행되어야 한다.

 

프로젝트 단위로 확장시켜 생각해보면 클라이언트는 요청 메시지를 전달하는 주체이고, 서버는 요청 메시지를 받아서 실제로 구현된 로직을 실행하는 주체이다. 클라이언트는 서버가 어떤 역할을 할 것인지에만 집중하면 되고, 서버가 어떤 로직으로 역할을 수행하는지는 관심도 없고 변경되었다고 해서 영향을 받지도 않는다(아까 까마귀 객체가 추가했을 때 fly()라는 역할에는 변동이 없었던 것처럼). 때문에 프로젝트 유지보수를 할 때도 클라이언트는 변경하지 않고도 서버의 구현 기능을 유연하게 변경할 수 있다.

 

+ 확장 가능한 설계

+ 유연함, 변경에 용이

+ 클라이언트에 영향을 주지 않고 로직 변경 ㅇ가능

- 인터페이스 구현이 변하면 다 뜯어고쳐야 한다.

 

스프링과 객체 지향

다형성이 제일 중요하다

스프링은 다형성을 극대화해서 사용할 수 있게 해준다.

스프링의 Ioc(제어의 역전), DI(의존관계 주입)도 다형성 활용의 연장선

 

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

단일 책임 원칙 SRP (single responsibility principle)

하나의 클래스는 하나의 책임만

변경을 했을 때 side effect가 적다? SRP를 잘 따른 코드

 

개방 패쇄 원칙 OCP (open / closed principle)

확장에는 열려있지만 변경에는 닫혀있어야 한다.

다형성과 관련된 요소, 역할과 구현이 잘 분리된다면 OCP를 잘 지킬 수 있다.

 

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

프로그램의 객체는 프로그램의 정확성을 깨지 않으면서 하위 타입의 인스턴스를 바꿀 수 있어야 한다. 컴파일 여부를 넘어서 기능적으로도 하위 클래스로 치환했을 때 상위 인터페이스 규약을 여전히 지키고 있어야 한다(기존의 로직에 문제가 없이 대체할 수 있어야 한다).

 

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

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

기능 단위에 맞게 적당한 크기로 인터페이스를 분리하는 것이 좋다. 

 

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

클라이언트는 구현 클래스가 아닌 인터페이스에 의존해야 한다(역할에 의존해야한다).

 

👀의존한다? → 현재 클래스가 다른 클래스를 알고있다.

public class UserService {
   // 1. UserService 클래스는 MemberRepository 클래스에 의존적
    private Repository repository = new MemberRepository(); 
    
    // 2. 생성자를 통해서 자원을 주입하게 되면 MemberRepository 클래스와의 의존성이 사라진다.
    public UserService(Repository repository){
    	this.repository = repository;
    }
}

 

UserService 클래스는 MemberRepository에 의존적이다. 즉, 다형성만으로는 OCPDIP를 지킬 수 없다.

 

스프링과 객체지향

스프링은 DI(Dependency Injection) 컨테이너를 제공해서 다형성만으로 달성하지 못했던 OCP, DIP를 지원한다. 최대의 장점은 클라이언트 코드 변경없이 기능을 확장할 수 있다(부품교체하듯). 자바의 장점인 다형성을 사용하면서도 스프링이 제공하는 컨테이너를 통해 조금 더 객체지향적으로 프로그래밍할 수 있도록 도와주는 프레임워크다.

 

 

인터페이스 추상화에 대한 장점을 이야기했지만, 실제 실무에서는 추상화로 인해 비용이 발생한다. 코드에 문제가 발생했을 때, 추상 클래스 → 어떤 구체 클래스에서 문제가 발생했지? 탐색하는데 시간이 걸리기 때문이다. 그래서 만약 기능을 확장할 가능성이 없다고 하면 그냥 바로 구체 클래스를 사용하고 리펙터링을 통해서 인터페이스를 사용하는 것도 좋다.