객체 지향 설계 5원칙 - SOLID

Posted on May 2nd, 2022

시작

나는 과거에 '스프링 입문을 위한 자바 객체 지향의 원리와 이해'를 읽었다. '음 그렇군.' 하고 그냥 읽었다. 개발자 사이에서 얘기하는 개구리책이며 잘읽히는 '갓책'으로 유명하다. 나는 이 책을 다시 보려한다. 그 첫번째 시작점은 SOLID.

왜 SOLID 를 공부 하는가 ?

'도구를 올바르게 사용하는 법이 있는 것처럼 객체 지향의 특성을 올바르게 사용하는 방법, 즉 객체 지향 언어를 이용해 객체 지향 프로그램을 올바르게 설계해 나가는 방법이나 원칙이 존재할까?' 결론은 응집도는 높이고 결합도는 낮추는 원칙이라 할 수 있다. 책의 일부분이다. 이 문장으로 부터 시작을 해야한다.

응집도와 결합도가 뭔데? - feat. 의존성

결합도 : 모듈(클래스) 간의 상호 의존 정도로서 결합도가 낮으면 모듈 간의 상호 의존성이 줄어들어 객체의 재사용이나 수정, 유지보수가 용이 응집도 : 하나의 모듈 내부에 존재하는 구성 요소들. 응집도가 높은 모듈은 하나의 책임에 집중하고 독립성인 높아져 재사용이나 기능, 유지보수가 용이

너무 말이 어렵다. 코드를 보고 간단하게 생각하자.

@Requiredargsconstructor
@Service
public Class Service {
	private final Repository repository;
}

코드를 보자. Service 라는 모듈은 Repository 라는 모듈을 의존하고 있다. 즉, Service라는 모듈은 Repository라는 모듈이 없으면 안된다. Service는 Respository에 의존하고 있다는 것이다. 하지만 Repositroy는 Service가 있든 말든 상관이 없다. 이게 바로 의존성이다. 그럼 결합도은? 그거나 그거다 ! 의존성은 결합도이라고도 이야기 하며, 모듈이나 함수간에 얼마나 밀접하게 연결되어 있는가를 표현하는 말일뿐!

그럼 응집도는? 이부분은 되게 애매 모호했다. 구글링을 해보니 클린코드 177page에 좋은 글이 있다는 것을 발견했고 곧바로 집에 있는 책을 보고 이해해버렸다. 누구든 이 글을 보면 쉽게 이해할 수 있을 것이라 생각한다.

SRP [ Single Responsibiliy Principle ] : 단일 책임

단일 책임. 말 변경해야 하는 이유가 오직 하나 뿐이어야한다. 무슨 말이냐면, 하나의 클래스 또는 하나의 메소드 등 역할과 책임이 많으면 안되고 단 하나! 의 역할과 책임이 있어야 한다는 말이다. 돌아가지 않고 쉽게 이해하기 위해서는 코드를 보는 것이 가장 좋다.

@Allargsconstructor
class Person {
	private Long companyNumber;
}
Person jobless = new Person( ? );
Person employee = new Person(1010L);

이게 어떤 의미인가? 백수에겐 회사 번호란 존재하지 않다. 즉, Person이라는 클래스에 책임을 전부 다 주지 않고 백수와 직장인의 클래스를 분리해야 하는 것이다. 그래야 '단일 책임'이다.

다른 예를 보자. 이번에는 메소드다.

class Person {
	final static Boolean jobless = false;
    	final static Boolean employee = true;
    	boolean state;
        
    	void sayJobState(){
    		if(state){
            		sout("직장인입니다.");
                }else{
               		sout("직장인이 아닙니다.");
                }
    	}
}

메서드가 단일 책임 원칙을 지키지 않을 경우 나타나는 가장 대표적인 것이 분기처리를 위한 if문이다. 즉, 이러한 경우

abstract class Person {
	abstart void sayJobState()
}
class Jobless extends Person {
	...
}
class Employee extends Person {
	...
}

추상 클래스로 추상화를 하고 각 Jobless, Employee 클래스에서 상속을 받아 sayJobState를 if문이 아닌 각자 구현을 해주면서 단일 책임 원칙을 적용할 수 있다.

OCP [ Open Closed Principle ] : 개방 폐쇄

너무나 당연한 얘기. 확장에 대해서는 열리고, 변경에 대해서는 닫힌다. 자신의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 한다. 너무 어렵다. 근데 이미 우리가 항상 코딩하는 스타일이다. 쉽게 보자.

Controller에서 ServiceInterface를 의존하고 있다. 그림을 보고 코드를 짜보자.

@Requiredargsconstructor
@Controller
class Controller{
	private final ServiceInterface serviceinterface;
}

여기서 Controller는 ServiceInterface를 주입받고 있다. 그리고 실제로 Controller에서 쓰는 Service는 Interface에 구현된 구현체 ServiceImplementation을 쓴다. 그럼 여기서 같은 Service하나가 추가 되어야하고 그 Service는 겉으로 보기에는 똑같은 기능이지만[ 물론 결과값은 비지니스로직마다 다름] 내부 비지니스 로직이 다른 구현체. 즉, ServiceImplementation2가 있을 때, 어떻게 되는가? 간단히 Controller의 Interface 주입되는 부분은 가만히 냅두고 ServiceImplementation2를 추가해주면 된다.

간단히 요약하자. Service 입장에서는 자신이 확장되는 것에 개방적이고 Controller 입장에서는 주변의 변화에 전혀 영향이 가지않는 폐쇄돼있다고 말할 수 있다.

이러한 원칙으로 유연성, 재사용성, 유지보수성 을 얻을 수 있다.

LSP [ Liskov Substituion Principle ] : 리스코프 치환

내가 가장 어려워하는 부분이다. 이름부터가 이상하다. 리스코프. 쉽게 이해하기 위해서는 리스코프 치환 원리가 잘된 것과 안된 것을 보는 것이 가장 쉬울 것같다.

1.MOTHER GGOM = new SON()
2.ANIMAL DURI = new DOG()

1번의 예시를 보자. 아들을 낳고 곧바로 엄마의 역할을 주고 있다. 2번의 예시는 강아지가 태어나고 그 동물의 이름은 DURI이다. 직감적으로 1번은 이상하고 2번은 맞다는 것을 알수 있다. 1번은 원칙에 어긋나고 2번은 원칙에 맞다는 것.

'하위 클래스의 인스턴스는 상위형 객체 참조 변수에 대입해 상위 클래스의 인스턴스 역할을 하는데 문제가 없어야한다.'

결국 리스코프 치환 원칙은 객체 지향의 상속이라는 특성을 올바르게 활용하면 자연스럽게 얻게 되는 것이다. 아마 여러 개발자들이 리스코프 치환이라는 개념을 모르지만 충분히 이 원칙을 지키면서 개발하고 있을 것이라고 생각한다.

ISP [ Interface Segregation Principle ] :인터페이스 분리

클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다. 만약, 한명의 남자가 어떤 여자에 남자친구고 어떤 부모의 아들이며 어떤 회사의 개발자라면 그 남자에 대한 역할이 너무 많다. 그래서 그 역할을 분리하는 목적으로 하나의 방법인 각 역할에 따른 남자친구, 아들, 개발자 각각의 클래스를 두어 역할을 분리하는 방법이 있다.

근데 이 방법은 단일 책임 원칙에 적합하나 방법이 이것이 최선이었을까?

인터페이스를 다중 상속하여 다중 인격화를 시키면 굳이 클래스를 분리하지않아도 가능하다.

DIP [ Dependency Inversion Principle ] : 의존 역전

DIP. 의존 역전에 대해서 공부를 해보니 SOLID의 O. 개방 폐쇄 원칙하고 비슷했다. 좀더 이해하기 쉽게 이부분 또한 그림으로 이해해보자. 차는 스노우타이어에 의존적이다. 이 타이어가 있어야 돌아간다. 하지만 차는 겨울의 계절이 지나가면 의존적이지 않게 된다. 그래서 다른 타이어로 바꿔야한다. 즉, 의존하기에 부서지기 쉬움이라는 단점이 있다. 어떻게 개선해야할까?

자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는것이 의존 역전 원칙이다.

누군가 물어 볼때

누군가 : 객체 지향 설계 5법칙 말해보세요.

급할 필요 없이. SOLID 부터 생각하고 곧바로 앞글자만 따고 차근차근 얘기하기. 하나하나 차근차근 S : 싱글, O : 오픈, 클로스 L : 리스코프, I : 인터페이스, D : 의존

끝내며

공부를 하면서 느낀 것은 원칙을 모르면서도 이미 개발을 할때 고려했던 부분이 많았다는 점이다. 그래서 이게 굳이 원리를 알아가면서까지 외울필요가 있을까? 싶기도 하다. 하지만 누군가에게 지식을 전파할 때는 꼭 필요하다고 생각한다. 나는 궁극적으로 이런 방향성을 갖고 있기에 오늘 투자한 시간이 굉장히 값지다.