4월 17, 2024

객체 지향 설계 원칙 (SOLID) 한방에 정리! 개발자라면 필수로 알아야 할 개념!

객체 지향 설계 원칙 (SOLID)란?

기술면접의 단골질문이자 시험에도 자주 나오는 SOLID 객체 지향 설계 원칙에 대해 알아보도록 하겠다. SOLID에 대해서는 개념적으로 숙지하고 있는 것도 좋지만 객체 지향 프로그래밍을 직접 할 때도 이를 참고하면 코드의 퀄리티를 높일 수 있기 때문에 꼭 알아두는 것을 추천한다. 

 

SOLID 원칙은 객체 지향 설계 원칙이라고도 불리며 객체 지향 프로그래밍 설계 시 필요한 다섯 가지의 원칙 (Single Responsibility Principle, Open Close Principle, Liskov Substitution Principle, Interface Segregation Principle, Dependency Inversion Principle)의 앞 글자를 따 붙여진 이름이다. 다섯 개 모두 다 중요한 의미를 가지고 있기 때문에 각각에 대해 자세히 알아보자.


1. Single Responsibility Principle 단일 책임의 원칙


비교적 간단한 원칙이다. 말 그대로 하나의 클래스는 하나의 책임만을 수행하게 하자는 뜻이다.

 

객체 지향이 절차 지향과 다른 점은 class를 가지고 객체를 생성할 수 있다는 뜻인데, 가끔 객체 지향 언어로 코드를 짜는 사람 중에서는 이러한 객체 지향의 장점을 적극 활용하지 않고 하나의 클래스에 모든 기능을 넣는 사람이 있다. 물론 메소드를 구분하여 기능을 분류할 수 있지만 엄연히 다른 일을 수행하는 객체의 경우 class를 구분하는 것이 더 맞는 방식이다. 만약 class를 책임 별로 구분하지 않는다면 하나의 class가 수행하는 일이 거대해져 객체 지향 프로그래밍의 의미를 잃게 된다.

 

따라서 단일 책임의 원칙 (Single Responsiblity Principle) 에서는 클래스가 제공하는 모든 서비스는 하나의 책임을 수행하는 데 집중되어 있어야 한다고 말하고 있다. 1번 원칙인 만큼 단일 책임의 원칙은 나머지 2~5번 원칙의 기초가 되는 원칙이다. 


2. Open Close Principle 개방 폐쇄 원칙


컴포넌트, 클래스, 모듈, 함수와 같은 소프트웨어 구성요소는 확장에는 열려있고, 변경에는 닫혀있어야 한다는 원칙이다.

 

Java 언어를 배운 사람들은 쉽게 이해할 텐데, 상속을 받아서 하위 클래스에서 overriding 등을 하는 것은 가능하지만, 외부 class에서 접근해서 특정 field를 바꾸는 것을 제한하기 위해서 접근 제한자(private, protected 등)를 사용한다. 즉 해당 class뿐 아니라 이외 소프트웨어의 구성요소를 확장하여 사용할 때는 open 되어 있는 반면, 해당 구성요소를 외부에서 임의로 변경하려는 시도는 제한하여 그 경우에는 closed 되어 있어야 한다는 원칙이다.

 

개방 폐쇄 원칙은 협업 개발 시 매우 중요하게 생각해보아야 할 원리로, 내가 개발한 구성요소가 다른 사람에게 적절한 때 개방되고 폐쇄되는지에 대해 신중히 고민해보아야 한다.

 


3. Liskov Substitution Principle 리스코프 치환의 원칙


상속받은 하위 클래스는 어디서나 상위 클래스로 교체할 수 있어야 한다는 원칙이다.

 

리스코프 치환의 원칙은 예시로 이해하는 것이 가장 빠른 것 같다. 다음은 리스코프 치환의 원칙에 위배되는 예시이다. 만약 '도형'이라는 상위 class가 있다고 가정해보자. 그리고 '도형'이라는 class에 '꼭짓점의 개수는 ~개이다'라는 것을 나타내주는 method가 있다고 가정해보자. 그리고 '도형'이라는 상위 class를 상속받아 '사각형'이라는 하위클래스와 '원'이라는 하위클래스를 만들어주었다. 사각형이라는 하위 클래스는 꼭짓점을 4개 가지고 있으니 상위클래스인 도형으로 교체가 가능하다. 하지만 '원'이라는 하위클래스는 상위클래스인 도형이 가지고 있는 메서드를 충족시킬 수 없으므로 리스코프 치환의 원칙에 위배된다. 리스코프 치환의 원칙에 위배되지 않게끔 클래스를 설계하려면 '꼭짓점이 있는 도형'과 '꼭짓점이 없는 도형'의 상위클래스를 설계하고 꼭짓점이 있는 도형을 상속받아 삼각형, 사각형 등의 하위클래스를 만들고, 꼭짓점이 없는 도형을 상속받아 타원, 원 등의 하위클래스를 만들어야 할 것이다. 


4. Interface Segregation Principle 인터페이스 분리의 원칙


class와 달리 인터페이스는 다중 구현이 가능하다. Java interface를 생각해보면 implements라는 keyword 이후에 여러 interface가 오는 것을 종종 본 적이 있을 것이다. 물론 여러 interface를 구현해도 되지만, 인터페이스 분리의 원칙에서는 한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다고 말하고 있다. 즉, 클라이언트가 사용하지 않는 인터페이스 때문에 영향을 받아서는 안 된다는 원칙이다.

 

가끔 만들어둔 모든 인터페이스를 모두 구현하여 필요한 것만 사용하는 경우가 있는데, 물론 편리하겠지만 사용하는 인터페이스만 구현하도록 노력해야 할 것이다.


5. Dependency Inversion Principle 의존성 역전의 원칙 (DIP) 


의존성 역전의 원칙은 의존성 관계를 맺을 때 변화하기 쉬운 것보다는 변화가 없는 것에 의존관계를 맺어야 한다는 원칙이다. 간단하게 DIP라고 부르기도 한다. 만약 어떠한 상점이 할인정책으로 fixed amount discount policy, 즉 모든 물건에 대해 정해진 양 ex) 2000원 할인을 실시했다고 치자. 

의존성 역전의 원칙이 잘 지켜지지 않는 경우

discount policy로 fixed amount discount policy를 채택해서 위와 같은 diagram 형식의 class 구조가 되었지만, 사실 이는 5번 의존성 역전의 원칙을 잘 준수하지 않은 예시이다. 왜냐하면 만약 이후 상점이 마음이 바뀌어 할인정책으로 정량할인정책이 아닌, 정률할인정책을 사용한다면 단순히 fixed amount class를 바꾸어야 할 뿐만 아니라 store의 다른 영향을 받는 부분까지 모두 다 변경해야 하기 때문이다. 현재 store class는 변화가 있을 수 있는 fixed amount policy라는 class에 의존관계를 맺고 있기 때문에 의존성 역전의 원칙을 만족시키지 않는 것이다. 

 

의존성 역전의 원칙을 잘 만족시키기 위해서는 변화가 없는 interface나 상위 클래스와 의존관계를 맺어야 한다. 위의 예시를 수정하면 아래와 같은 다이어그램이 될 것이다. 



의존성 역전의 원칙이 잘 지켜지는 경우 

즉 store는 변화가 잘 없는 discount policies라는 interface와 의존관계를 맺고 있으며, 이럴 경우 상점이 택하고 있는 할인정책이 변하더라도 store 자체 class는 큰 변경 없이 사용이 가능하다. 만약 두 가지 할인 정책 이외에 새로운 할인정책을 변경하고 싶다면 discount policies 를 구현하는 새로운 하위 클래스를 만들어 사용하면 되기 때문에 문제가 없다. 오히려 개발자의 입장에서는 변경이 용이해진 것이다. 

 

의존성 역전의 원칙은 5가지 원칙 중 가장 이해하기가 까다로운 원칙이었을 것이다. 중요하지 않아 보일 수 있지만 이러한 의존성 역전의 원칙은 자바, 특히 이후 Spring에서 매우 중요하게 강조되는 개념이므로 잘 알아두면 유용할 것이다.


개발자라면 필수로 알아야 할 SOLID 원칙에 대해 알아보았다. 개념이 이해되었다고 넘어가지 말고 실제 코드 작성을 할 때 유념하여 의식적으로 이 원칙들을 잘 적용하고 있는지 살펴보면 좋을 것 같다.