개발을 하고 있는 사람이라면 한 번쯤은 무조건 들어봤을 단어가 있다. 그것이 바로 SOLID이다. SOLID가 도대체 무엇이길래 이렇게 자주 언급되는 것일까? 이것에 대해 알려면 먼저 객체지향에 대해서 알아야 한다. SOLID가 바로 좋은 객체지향프로그래밍을 하기 위한 5가지 원칙이기 때문이다. 간단하게 주어진 구조 순서대로 쭉 실행되는 절차지향과는 달리 실세계를 빗대어 객체라는 것을 만들어 객체에 변수나 메서드 등의 정보를 포함시키고 이를 가지고 다른 객체들과 상호작용하는 객체지향을 이용하면 재사용성과 유지 보수성을 높일 수 있다. 하지만 객체지향이라고 해서 장점만 있는 것은 아니다. 무엇보다 학습하기 어려운 내용들이 많다는 것을 다들 이미 알고 있을 것이다. 디자인 패턴부터 여러 원칙들... 물론 개발할 때 이런 방식들을 무조건적으로 따르지 않아도 된다. 지나치게 사용하면 오히려 독이 되는 경우도 있다. 하지만 여러 사람들이 언급하고 자주 사용되는 이유가 있지 않을까? 디자인 패턴이나 원칙들은 수많은 사람들에게 인정받고 사용되어 온 것들을 정리하여 만든 것들이다. 이러한 것들을 사용하면 팀원들 간의 의사소통이 원활해지고, 코드를 보기 좋아지며, 유지보수하기 좋아진다. 그럼 이제 과연 좋은 객체지향적인 설계를 할 수 있도록 해주는 원칙이 무엇인지 살펴보자!
SRP : 단일 책임 원칙
SRP = Single Responsibility Principle
클래스는 하나의 책임을 가져야 한다.
하나의 클래스에 여러 책임을 가지는 기능을 넣지 말라는 것이다.
예를 들어, 우리한테 조립을 할 수 있는 로봇이 하나 있다. 사람들은 이 로봇이 조립을 하는데 도움이 될만한 기능이 추가되었으면 좋다고 생각했다. 그래서 조립하는 기능 외에도 회전하는 기능, 하늘을 날 수 있는 기능 (?) 등을 넣었다. 이제 이 로봇은 조립하면서 회전할 수도 있고, 날 수도 있다! 그렇게 로봇을 잘 사용하고 있었는데 어느 날 회전 기능을 약간 수정해야 할 일이 생겼다. 그래서 로봇을 분해해 봤는데... 어디부터 어디까지가 조립 기능이고, 회전하는 기능이고, 하늘을 나는 기능인지 굉장히 애매해서 어디부터 손대야 할지 한참을 헤매게 되었다.
조립하는 로봇 클래스에 회전하는 기능과 하늘을 나는 기능을 섞어서 설계했기 때문에 이런 일이 일어난 것이다. 만약 회전하는 기능과 하늘을 나는 기능을 모듈로 만들어서 탈부착할 수 있게 만들었으면 어땠을까? 그러면 하늘을 나는 기능을 수정해야 할 때 해당 모듈만 가져다가 수정하면 되는 것이다! 해당 모듈에는 하늘을 나는 기능밖에 없을 테니 수정하기도 용이할 것이다.
OCP : 개방-폐쇄 원칙
OCP = Open-Closed Principle
확장에는 열려있어야 하고, 수정에는 닫혀 있어야 한다.
확장에는 열려있고, 수정에는 닫혀 있다는 말이 무엇일까? 이를 이해하려면 인터페이스에 대해서 알고 있어야 한다. 인터페이스에 필요한 기능들을 선언만 해놓고 정의하는 부분은 해당 인터페이스를 구현한 클래스에게 맡기는 것이다. 즉, 인터페이스를 통해서 역할과 구현을 나눈 것이다.
예를 들어, 게임을 하는데 qwer의 키를 눌러서 각 키마다 다른 스킬이 나가는 게임이 있다고 하자. 그 게임 그래서 총 4가지 스킬을 사용할 수 있는데 사실 이 게임은 스킬을 구매해서 갈아끼우는 식이었다! 그래서 q에 해당하는 스킬을 구매해서 다른 공격을 하도록 할 수 있다. 그런데 우리는 이 스킬들을 어떻게 사용할까? 그렇다. q를 누르거나 w를 누르거나 e를 누르거나 r을 눌러서 사용한다! 그냥 특정 키를 누르기만 하면 스킬을 사용할 수 있는 것이다. 내가 q에 해당하는 스킬을 모르더라도 해당 키를 눌러서 그냥 단순히 스킬을 사용할 수 있는 것이다. 여기서 스킬이 바뀌어도 q에 해당하는 스킬을 사용할 수 있는 것이 확장이고, 스킬을 변경하기 위해 캐릭터 내용을 변경하는 것이 수정이다. 나중에 스킬을 추가하든, 변경하든 해당 q 구현 스킬만 수정하거나 새로 추가하면 되는 것이지 캐릭터의 내용에 파고들어 구체적인 스킬 내용을 변경하는 것은 좋지 않다는 것이다.
이러한 처리를 할 수 있도록 하는 것이 객체 지향의 특징 중 하나인 다형성이다. 부모 타입인 인터페이스에 이를 구현한 자식 구현체를 할당하여 동작할 수 있도록 할 수 있다. 다만 OCP의 경우는 단순 코드로만 구현하면 완전히 해당 원칙을 달성하지 못하는 경우가 있다. 구현체를 할당하는 코드가 포함된 시점부터 이미 OCP를 위반한 것이기 때문이다. 이는 DI라는 의존성 주입을 통해 해결할 수 있는데 자세히 설명하기는 길어서 스프링의 DI 컨테이너를 찾아보면 어떤 원리인지 잘 알게될 것이다. DIP와도 연관이 있는 내용이다.
LSP : 리스코프 치환 원칙
LSP = Liskov Substitution Principle
슈퍼 클래스가 할 수 있는 일을 서브 클래스가 할 수 있어야 한다.
간단하게 하위 타입 요소가 바뀌어도 이전과 동일한 방식으로 작동해야 할 수 있어야 한다는 것이다.
예를 들어, 당신이 공장의 감독이라고 해보자. 사람들이 일을 제대로 잘 하는지 감독이 판단을 하게 된다. 이 감독은 A, B, C라인을 맡았는데 각 라인마다 다른 일을 맡고 있기 때문에 감독은 해당 라인에 맞는 일을 잘하고 있는지 확인을 하는데 어느 날, 파란색 색칠을 담당하고 있는 A 라인의 인원이 교체가 되었다. 이전에는 해당 라인의 사람이 바뀌어도 파란색으로 색칠하는 과정을 잘 수행했는데 이번에 새로 온 신입은 얼마나 폐급인지 파란색으로 색칠하는 게 아니라 초록색으로 색칠한 것이 아닌가! 이것이 리스코프 치환의 원칙을 위반한 예시이다.
확실히 동작하는 행위 자체만 보면 문제가 없다. 하지만 그 내용을 열어보면 원하는 방식대로 동작하지 않았음을 알 수 있다. 간단히 정리하자면, 인터페이스에서 약속한 규칙대로 수행해야 한다는 것이다.
ISP : 인터페이스 분리 원칙
ISP = Interface Segregation Principle
여러 기능을 가진 하나의 인터페이스 보다는 유사한 기능을 모은 여러 인터페이스로 나누는 것이 좋다는 원칙이다.
예를 들어, 두가지 종류의 약을 담을 수 있는 캡슐이 있다. 팔 아플 때 먹는 약과 다리가 아플 때 먹는 약을 담아서 캡슐약을 만들었다. 그런데 내가 팔이 아파서 이 캡슐약을 복용하려고 하는데 굳이 다리가 아플 때 먹는 성분이 포함된 해당 캡슐을 먹어야 할까? 팔이 아픈 거니까 그냥 팔 아플 때 먹는 약만 먹으면 될 일이다. (진짜 팔, 다리 구분해서 먹는 약이 있는지는 모르니까 그냥 단순 예시로만 봐주세요)
즉, 단순히 약이라는 큰 주제보다는 구체적인 내용의 여러 인터페이스로 나누어서 각 상황에 맞는 기능만 사용하는 것이 좋다는 것이다.
DIP : 의존관계 역전 원칙
DIP = Dependency Inversion Principle
인터페이스에 의존해야지, 구현체에 의존해서는 안 된다는 원칙이다.
예를 들어, 게임을 개발하는데 공격 기능을 추가하려고 한다. 일단 총을 쏘게 하고 싶어서 조준하는 기능, 방아쇠를 당기는 기능 등을 만들고 이거에 맞춰서 캐릭터의 기능을 추가했다. 그런데 나중에 이번에는 검을 사용하도록 하고 싶어서 캐릭터는 그대로 놔두고 총만 검으로 변경하려고 하는데 여기서 뭔가가 이상한 것을 느낄 수 있다. 검으로 조준을 할 수 있을까? 뭐 이건 어떻게 가능할 거 같으니까 넘어가는데 검의 방아쇠를 당길수 있을까? 무언가가 단단히 잘못되었다는 것을 알 수 있다. 아... 어느정도는 나의 경험담이다ㅠㅠ
즉, 개발할 때부터 구현체에 맞춰서 개발하면 안 된다는 것이다. 공격 기능이니까 단순히 공격 기능을 할 수 있다는 가정하에 캐릭터를 만들고 해당 공통 공격 기능을 수행하는 총과 검을 나중에 만들면 되는 것이다. 이렇게 인터페이스에만 의존하면 무기에 의존하지 않고 나중에 어떤 무기를 장착하든 동일하게 공격 기능을 수행할 수 있을 것이다.
'공부 > 기타' 카테고리의 다른 글
| [Redis] Spring Data Redis 사용하기 (0) | 2024.05.21 |
|---|---|
| MapStruct는 Lombok보다 먼저 작성하자 (0) | 2024.04.29 |
| Apache Kafka 실행 및 기본 기능 테스트하기 (0) | 2024.04.03 |
| [테스트] 좋은 단위 테스트(Unit Test)를 위해서 (0) | 2024.02.19 |
| 정규 표현식(Regular expression) (0) | 2023.08.29 |