[Java] 객체 지향 설계 5가지 원칙 (Feat. SOLID)

nopecho·2021년 12월 30일
1

SOLID?

객체 지향과 항상 같이 붙어 다니는 녀석이 있다.
바로 SOLID라고 불리는 객체 지향 설계의 5가지 원칙이다.
솔리드 솔리드 하는데 좋은 객체 지향 설계와 SOLID가 뭔지 알아보자

먼저 SOLID라는 용어는 클린코드로 유명한 로버트 마틴이 좋은 객체 지향 설계의 5가지를 정리 해놓은 것이다.


SOLID 5형제 중 첫째 S 부터 살펴보자.

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

단일 책임 원칙은 말 그대로 하나의 클래스하나의 책임만 가져야 한다는 것이다.
좋다. 하나의 클래스가 하나의 책임만 가져야 한다는건 알겠다. 근데 그럼 하나의 책임이라는 범위를 어디까지 한정 할 것인가? 이 부분이 모호하다.
그러나 이 부분 하나는 확실히 알 수 있다. 바로 변경이라는 기준이다. 한 클래스 내부 로직에 변경이 있을때 로직의 변경으로 인한 파급 효과가 적으면 적을수록 SRP 단일 책임 원칙을 잘 따른 것이다.


단일 책임 원칙이란 결국 하나의 클래스는 해당 클래스의 역할만 잘해라 이거다. 다른 객체와 소통 할때엔 메세지를 통해(메소드) 소통하고 하나의 클래스 안에 이것 저것 많이 두지 말라는 말인것 같다.

그럼 두번째 O에 대해 알아보자.

OCP 개방-폐쇄 원칙(Open/Closed Principle)

소프트웨어 요소는 확장에는 열려있고 변경에는 닫혀있어야 된다.
??? 이게 뭔 소린가 확장에는 열려있는데 변경에는 닫혀있다? 확장을 하려면 변경을 해야하는것 아닌가?
이 말도 안되는 말이 자바의 다형성을 이용하면 가능하다.
생각해보자

public class 서비스 {
	private 회원저장소 m = new A회원저장소();
    	
        public void 회원조회서비스(){
        	m.조회()
        }
        
        public void 회원가입서비스(){
        	m.저장()
        }
}

예를 들어 이러한 코드가 있다. 서비스 라는 클래스가 있고 그 서비스 라는 클래스는 필드변수로 인터페이스인 회원저장소의 구현체 A회원저장소를 가지고 있다.
그런데 갑자기 A회원 저장소 말고 B회원 저장소를 사용해야 하는 상황이 발생 한다면?? 우리는 이런식으로 코드를 바꿀 수 있을것이다.

public class 서비스 {
	//private 회원저장소 m = new A회원저장소();
    	private 회원저장소 m = new B회원저장소();
    	
        public void 회원조회서비스(){
        	m.조회()
        }
        
        public void 회원가입서비스(){
        	m.저장()
        }
}

회원저장소 인터페이스의 구현체만 B회원저장소로 바꾸면 될 일이다. 그렇다면 서비스 클래스의 로직에는 변경이 없고 다른 저장소로 갈아 끼울 수 있으니 확장에는 열려 있다고 볼 수 있다.

그런데 여기서 한가지 문제점이 발생한다. 정말 서비스 클래스에 변경이 없다고 볼 수 있을까?

아니다! 우리는 결국엔 코드를 직접 변경을 한것이다. 구현체를 변경하려면 코드를 변경한다는 것이다.
분명 다형성을 잘 사용했다고 생각했는데 OCP를 지킬 수 없다...

이 문제의 해결을 바로 스프링 컨테이너가 해준다!!
이 부분은 다음에 알아보자..


자 그럼 세번째 L 차례다.

LSP 리스코프 치환 원칙(Liskv Substitution Principle)

이 친구는 앞의 두 친구보다 비교적(?) 간단하다.
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
자바의 상속 관계에서 Is-A관계를 생각하면 될 것 같다.
다형성에서 인터페이스의 구현체는 인터페이스의 메소드들을 반드시 오버라이딩으로 구현해야 한다. 인터페이스를 구현한 구현체를 믿고 사용하려면 이 원칙이 필요하다.


I를 알아보자.

ISP 인터페이스 분리 원칙(Interface Sergregation Principle)

특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
인터페이스 하나에 많은 기능이 들어가 있기보단 인터페이스 내에서도 기능 별로 잘게잘게 나누자! 많은 기능이 필요하다면 인터페이스의 다중 상속을 이용하자!


마지막으로 D다!

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

프로그래머는 "추상화에 의존해야하지, 구체화에 의존하면 안된다"라는 원칙이다. 의존성 주입은 이 원칙을 따르는 방법 중 하나이다.
쉽게 말해 인터페이스를 구현한 구현 클래스에 의존하지말고, 인터페이스 에 의존하라는 뜻이다.

그렇다면 아래와 같은 코드는 DIP를 위반 한 것일까?

public class 서비스 {
	private 회원저장소 m = new B회원저장소();
}

일단 위와 같은 코드는 DIP를 위반 한 것이다. 언뜻 생각하면 위반 했다고 볼 수 없다. 왜냐면 회원저장소라는 인터페이스 m 에 의존 하고 있고 구현체는 갈아 끼울 수 있으니 괜찮은것 아니냐는 생각이다.

그러나 서비스 클래스 관점에서 보면 서비스 클래스는 회원저장소도 의존하고 있고 B회원저장소 즉 인터페이스도 의존하고 구현체도 의존하고 있다.
여기서 의존이라는 개념은 클래스 내부에서 코드로 구현이 되어 있다는 말이다.
이게 무슨 소린가 그럼 어떻게 코드로 구현을 하지않고 인터페이스에 의존 하라는 말인가??
그 역활을 바로 마법의 스프링 컨테이너가 해주는 것이다.


여기까지 객체 지향 설계의 5가지 원칙을 알아 보았다.
이제 막 개발자로써 걸음마를 시작한 내 기준에선 어느정도 이해가 되는 부분도 있고 아직 까지 잘 이해가 되지 않는 부분도 분명 존재한다.
그래도 중요한건 뭘 모르는지 모르는 상태에서 내가 뭘 모르는지 아는 상태가 됐다.

이제 모르는걸 알아가기만 하면 된다(?!)

0개의 댓글