Spring

(Spring) SOLID, 좋은 객체 지향 설계 5원칙

하눤석 2022. 11. 7. 11:45
728x90

 

 

 

SOLID는 로버트 마틴이 제안한 좋은 객체 지향 설계의 5가지 원칙을 정리한 것이다.

 

 

SRP(Single Responsibility Principle) : 단일 책임 원칙

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

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

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

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

 

 

의 앞글자를 따서 SOLID 원칙이라고 부른다.


SRP(Single Responsibility Principle)

 

SRP는 단일 책임 원칙으로 "한 클래스는 하나의 책임을 가진다."는 원칙이다.

 

이 개념은 말로 설명하기에 모호한 부분이 있다. 

책임이란 것은 클 수도, 작을 수도 있고 상황에 따라 달라질 수 있기 때문이다. 

 

핵심은 객체의 변경이 발생했을 때 파급범위를 적절히 조절하여 파급효과를 줄이는 것에 있다.

 


OCP(Open/closed principle)

 

 

OCP는 개방/폐쇄 원칙으로 소프트웨어 요소가 확장에는 열려 있으나 변경에는 닫혀 있어야 한다는 원칙이다.

 

이 말은 모순되게도 확장을 하려면 당연히 소스의 변경은 이루어져야 한다.

어떻게 변경 없이 확장을 한다는 것일까?

 

답은 자바 객체의 다형성에 있다. 

 

확장이 필요할 때 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현하면 기존 소스의 변경 없이 확장이 이루어질 수 있다.

 

즉, 객체의 역할은 변하지 않고 새로운 구현체만 추가하는 것이다.

 

OCP는 문제점이 있다.

예를 들어, 아래와 같은 코드가 있을 때

 

MemoryMemberRepository를 JdbcMemberRepository로 확장하려 새로운 구현체를 만들어서 사용하는 것 까진 OCP를 만족한다. 그러나 클라이언트측 코드에서 호출부인

 

private MemberRepository memberRepository = new MemoryMemberRepository(); 가 

private MemberRepository memberRepository = new JdbcMemberRepository(); 로 변경되는 (OCP를 만족하지 않는)

상황이 발생하는 것이다.

public class MemberService {
 private MemberRepository memberRepository = new MemoryMemberRepository();
}


public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository();
 private MemberRepository memberRepository = new JdbcMemberRepository();

}

 

즉, 구현 객체를 변경하려면 클라이언트 코드를 변경해야 한다는 문제점이 발생한다.

위 코드에서 분명 다형성을 사용했지만 OCP 원칙을 지킬 수 없다. 이 문제를 어떻게 해결해야 할까?

 

정답은 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다는 것이다. 이 역할을 스프링의 컨테이너가 해준다.

 


LSP (Liskov substitution principle)

 

 

LSP는 리스코프 치환 원칙으로  프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다 

 

말은 어렵지만 되게 간단한 개념이다.

 

예를 들어, 자동차의 엑셀이라는 인터페이스를 구현할 때, 반드시 앞으로만 가야한다는 인터페이스 규약이 있다면

느리던 빠르던 반드시 앞으로 가는 기능을 구현해야 한다. 이 때, 만약 속도값을 음수로 두어 뒤로 가게 만들더라도 컴파일이나 실행에서는 문제가 없지만 인터페이스 규약을 지키지 않았기 때문에 LSP를 위반했다고 할 수 있다.

 


ISP (Interface segregation principle)

 

ISP는 인터페이스 분리 원칙으로 범용 인터페이스를 기능별로 나누어 사용하는 것에 대한 원칙이다.

 

예를 들어, 자동차 인터페이스를 운전 인터페이스와 정비 인터페이스로 분리하고 사용자 클라이언트를 운전자 클라이언트 정비사 클라이언트로 분리한다면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않는다.

 

이런 경우에 ISP를 만족하여 인터페이스의 기능이 명확해지고, 대체 가능성이 높아진다.

 


DIP (Dependency inversion principle)

 

DIP는 의존관계 역전 원칙으로 "구현체에 의존하지 말고 추상화에 의존하라"는 원리이다.

 

시스템을 구현할 때 객체는 추상체(인터페이스)와 구현체로 나뉜다. DIP는 구현체에 집중하지 말고 추상체에 집중해야 한다는 것이다. 즉, 어떤 구현체가 오더라도 역할이 수행될 수 있게 하라는 것이다. 객체지향의 다형성만을 사용해서는 이 원칙을 만족시킬 수 없다.

 

예를 들어,  아래와 같은 코드에서 MemberRepository라는 인터페이스에 대한 정보를 알고 있기 때문에 이를 사용하는 것은 올바른 선택이다. 그러나 new MemoryMemberRepository();로 특정 구현체에 의존하여 객체를 사용하는 것은 적절하지 않으며 DIP를 위반하는 선택이다.

public class MemberService {
 private MemberRepository memberRepository = new MemoryMemberRepository();
}


public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository();
 private MemberRepository memberRepository = new JdbcMemberRepository();

}

 

이 원칙도 OCP와 마찬가지로 객체지향의 다형성만으로는 만족하기 힘들다. 

Spring의 IOC 컨테이너를 통해 해결할 수 있다. (아직 공부중이라 추후 내용을 추가하겠습니다.)

 

 

 

 

 

*본 포스팅은 김영한님의 "스프링 핵심 원리 - 기본편" 에서 학습한 내용을 기반으로 작성되었습니다.

320x100