SOLID 원칙은 무엇을 의미할까?
객체 지향 언어를 다루다보니 SOLID 원칙에 대해서 접할 기회가 많았었는데, 졸업 작품에서 리팩터링을 진행하기 위해서 내가 알고 있는 내용들을 파트원들에게 공유하고자 처음으로 내용을 블로그에 정리한다.
* 내용은 주관적 서술로 진행됨을 알립니다.
원칙의 어원
SOLID 원칙은 로버트 마틴이 2000년 초반에 명명한 객체 지향 프로그래밍 및 설계의 다섯 기본 원칙을 말한다. 각자 SRP, OCP, LSP, ISP, DIP로 총 5개의 구성으로 이루어져 있으며, 각자의 앞글자를 따와 SOLID 원칙이라 한다.
중요하지는 않다.
사실 위에 적혀있는 SOLID 원칙의 어원은 중요한 것이 아니다. 우리가 중요하게 생각해야 하는 것은 왜 사용해야하는지에 대해서 판별을 내리는 것이 중요하다고 생각한다. 아래에서 정리한 각자의 원칙들을 보면서 사용해야 하는 적기나 기타 생각을 가질 수 있으면 좋겠다.
꽤나 널리 알려진 말이지만, Maslow의 망치를 기억할 수 있도록 하자. 적합한 곳이 있고 적합하지 않은 곳이 있을 수 있다.
* Maslow의 망치 : 손에 쥔게 망치 밖에 없으면, 모든 문제가 못으로 보인다라는 뜻으로 망치에 대한 과도한 의존성(= 익숙한 것에 기대려 하는 확증 편향)을 비판한다.
디자인 원칙? 디자인 패턴?
사용되는지 모르겠으나, 유니티에서는 SOLID 원칙을 디자인 원칙으로 분류했다. 디자인 패턴과는 다른 별도의 개념이므로 헷갈리지 않길 바란다. 조금 더 찾아봤는데 디자인 패턴으로 SOLID 원칙을 분류하는 것 보다 디자인 원칙이라는 별도의 개념으로 분류하는 것이 옳다 판단하고 SOLID 원칙을 디자인 원칙이라 말한다.
디자인 원칙은 구조 설계를 진행할 때, 참고하는 방식들을 뜻한다. SOLID 원칙에는 정형된 코드가 없다. 그때마다 프로그래머의 사고에 따라서 SOLID 원칙을 지켜서 프로그래밍을 할 수 있게 할 뿐이다. 그렇기 때문에 원칙과 패턴의 차이를 나눠야 한다고 생각했다.
( 아래에 내용은 Unity 블로그 내용을 우선시 하여 정리하였음 )
단일 책임 원칙 ( Single Responsibility Principle ) - SRP
각 모듈, 클래스 또는 함수는 단 한 가지만을 책임 져야 한다.
단 한가지의 책임을 지라는 말이 굉장히 모호하고 애매하다고 생각한다. 이 내용은 책임 = 기능으로 생각해도 좋을 것 같다.
각각 A, B, C라는 시스템들이 있다면 A 시스템에서 작동되는 기능들을 B 시스템과 C 시스템으로 전이하지 않고, A 시스템에서 작동될 수 있게끔 구현해야한다는 것이다.
유니티를 예시로 들면, 자주 사용되는 Rigidbody, Collider, 등 각자의 컴포넌트가 있다. ( Rigidbody는 물리에 관련된 내용 이외는 다루지 않고, Collider 또한 충돌에 관련된 내용 이외는 다루지 않는다. )
위 사진에서 처럼 PlayerController에서 작동되는 Audio, Effect, Input을 한꺼번에 관리하는 스크립트가 있다는 것은 한 클래스에 3가지의 기능이 공존하므로(AudioEffectInputManager) SRP에 위배된다.
위 사진에서는 이전에 AudioEffectInputManager를 3개의 클래스로 나누어 각자 한 클래스당 하나의 기능만 담당하도록 되었다.
개방-폐쇄 원칙 ( Open-Closed Principle ) - OCP
확장에는 열려(Open)있고 변경에는 닫혀(Closed)있어야 한다. 원래의 코드를 수정하지 않고서 새로운 동작을 생성할 수 있도록 클래스를 구현해야 한다.
마치 여태 나온 SOLID 원칙들은 설명되어 있는 내용만 보았을 때에는 어떤 말인가? 싶을 정도로 단어의 선택이 모호하고 추상적인 설명들이 많다. 하지만 막상 이해를 하고 나서는 썩 재미없는 옛날 서구식 개그 같은 배열의 연속임을 알 수 있다.
사과, 배, 바나나라는 과일의 맛(Taste)을 출력하는 클래스를 만들어야 한다고 생각해보자. 사과, 배, 바나나가 전부 다 단 맛이라고 가정한다면 어떻게 구현을 해야 할까? Fruit Class를 만들어서 상속 받게 하여, 맛을 알려주는 GetTaste() 메서드를 가져온다면 문제가 없을 것이다.
하지만, 사과, 배, 바나나에 석류가 추가가 되었고 맛이 달지 않고 시다면? 아마 GetTaste() 메서드에 여러 예외처리를 해주어야 할 것이다. 이러한 경우를 변경에 닫혀있지 않다. 새로운 예외처리를 추가해야 하기 때문이다.
이러한 점을 만들지 않도록 interface나 추상화를 통해서 각자 사과, 배, 바나나, 석류 클래스에서 GetTaste()를 구현한다면 확장에 열려있되, 변경에 닫혀있다고 볼 수 있다.
리스코프 치환 원칙 ( Liskov Substitution Principle ) - LSP
하위 객체는 상위 객체에서 가능한 행위를 수행할 수 있어야 한다.
하위 객체는 상위 객체에서 정한 inteface, method, 등의 규약들을 지킬 수 있어야 한다. 이 원칙은 다형성을 통한 확장인 OCP를 포함하고 있기 때문에 LSP는 OCP를 포함하는 구조가 될 수 있다.
리스코프 치환 원칙의 예시로는 베이스 클래스 School을 상속 받은 A School과 B School이 있을 경우, 상속 받은 A School, B School에서는 임의로 메서드를 추가하거나 더 확장할 수 없다.
그러니까, 베이스 클래스를 상속한 서브 클래스는 베이스 클래스(A School을 호출하기 위하여 베이스 클래스인 School을 호출한 경우)에서 사용되는 동작들을 사용할 수 있어야 한다는 것이다.
인터페이스 분리 원칙 ( Interface Segregation Principle ) - ISP
클라이언트는 사용되는 메서드만 의존해야 하며, 사용하지 않는 인터페이스를 구현하지 않아야 한다.
설명 자체는 어렵게 느껴질 수 있겠지만, 클라이언트가 의미하는 것은 클래스로 생각해도 된다. 그렇게 된다면 결론적으로 '클래스에 사용되는 메서드만 사용하라'는 말로 축약할 수 있으며, 더욱 더 간단하게 소개한다면 한 인터페이스에 기능을 많이 담지 말고 소분하라는 것이다.
한 인터페이스에 최대한 많은 정보를 담게 된다면, 클라이언트마다 불필요한 데이터가 있을 수 있는데 이러한 정보들로 인하여 영향을 받을 수 있기 때문에 여러 개의 인터페이스로 나누고 필요한 인터페이스를 추가 상속하는 구조로 이루어야 한다.
의존 관계 역전 원칙 ( Dependency Inversion Principle ) - DIP
상위 모듈은 하위 모듈에서 직접 임포트하지 않고 추상화를 거쳐야 한다.
두 개의 클래스가 연관성이 있을 경우, 종속성 혹은 커플링이 발생하게 된다. 이러한 경우에 한 클래스가 다른 클래스의 작동 방식에 너무 많이 관여할 경우에 해당 클래스를 수정하면 다른 클래스가 손상되거나, 많은 수정을 거치게 된다.
그렇기 때문에 클래스 간 종속성 혹은 커플링을 줄이는 것이 가장 이상적이다. 각 클래스의 메서드들은 외부 연결에 의존하지 않고 내부에서 조화롭게 작동되어야 한다. DIP에서 가장 훌륭한 시나리오는 느슨한 결합과 높은 응집력을 목표로 가지는 것이다.
* 느슨한 결합, 높은 응집력(Low Coupling, High Cohesion) : 추상화를 통해 다른 클래스와의 의존도를 낮춰서 결합하고(Low Coupling), 해당하는 클래스 안에서만 시스템을 관리하라(High Cohesion)
내용을 끝마치며...
생각보다 SOLID 원칙을 정리하는 시간이 꽤나 걸렸다. 대략적으로 알고 있었던 내용들이었지만 블로그에 게시글을 올리기 위해서 내가 알고 있는 정보의 신뢰성을 확인할 수 있어야 하기 때문이다. 마지막으로 정리를 가볍게 해보면 SOLID 원칙은 아래와 같을 것이다.
- SPA : 한 클래스에 하나의 기능만 담당할 수 있어야 한다.
- OCP : 클래스의 기능 확장이 진행될 때에는 기존 코드의 수정이 동반되면 안된다.
- LSP : 상속한 클래스를 대체할 수 있어야 한다.
- ISP : 하나의 인터페이스에 많은 정보를 담는 것은 좋지 않다.
- DIP : 하위 모듈에서 상위 모듈을 직접 부르지 말고 추상화를 사용하라.
* 다시 한 번 전달하지만, SOLID 원칙은 모든 상황에서 절대적 정답이 아니다.
'공부 > CS' 카테고리의 다른 글
락(Lock)에 대해서 알아보자. (0) | 2025.05.08 |
---|---|
[CS] 시스템 콜(System Call) (0) | 2024.10.18 |
[사운드 - LostArk 배경음악] (0) | 2019.08.21 |