-
SOLID in SwiftiOS/Wiki 2022. 4. 4. 01:44
안녕하세요! 오늘은 객체지향의 다섯가지 원칙인 SOLID에 대해서 공부해보겠습니다.
바로 시작하겠습니다 🐶
SOLID 원칙이란?
SOLID란 객체지향 프로그래밍 및 설계의 다섯 가지 기본 원칙을 각 원칙의 맨 앞글자만 딴 원칙입니다.
1. Single Responsibility Principle(단일 책임 원칙)
2. Open/Closed Principle(개방 폐쇄 원칙)
3. Liskov Substitution Principle(리스코프 치환 법칙)
4. Interface Segregation Principle(인터페이스 분리 원칙)
5. Dependency Inversion Principle(의존성 역전 원칙)
각 원칙을 따르지 않아도 프로그래밍은 가능하지만,
시간이 지나도 유지보수와 확장이 쉬운 시스템을 만들고자 할 때 이 원칙들을 적용해볼 수 있습니다.
각 원칙들을 하나하나 살펴보겠습니다.
Single Responsibility Principe"한 클래스는 단 하나의 책임만 가져야 한다."
SRP를 설명하는 문장입니다. 예제를 살펴보겠습니다.
위 예시에서 Handler 클래스는 네트워크 호출, 데이터 파싱, 데이터 저장 등 많은 책임이 있습니다.
그렇다면, SRP를 적용하려면 어떻게 해야 할까요?
이런식으로 책임을 다른 클래스로 분리함으로써 SRP를 적용할 수 있습니다.
Open/Closed Principle
"확장에 대해서는 열려있어야 하고, 변경에 대해서는 닫혀있어야 한다."
즉, 자기 자신의 기능 추가에는 열려있어야 하고, 주변의 변화에는 닫혀있어야 한다는 것입니다.
예시를 보겠습니다.
OCP 적용 전에는 Car의 종류를 추가하게 되면 Driver 클래스에서 generateEngine 메서드를 변경해야 합니다.
서로 강하게 결합되어 있기 때문에 유지보수 측면에서 바람직하지 않습니다.
그럼 OCP를 적용하면 어떻게 될까요?
OCP를 적용하기 위해 Car를 프로토콜로 둠으로써, 다양한 자동차가 생긴다고 해도 Car 프로토콜을 채택하기만 하면 Driver 클래스에서는 변경해야할 코드가 없어집니다. 결합도가 낮아져 유지보수가 쉬워졌습니다.
Liskov Substitution Principle
"서브클래스는 언제나 자신의 슈퍼클래스로 교체할 수 있어야 한다."
서브클래스는 슈퍼클래스를 상속받아 만들어진 클래스를 말합니다.
예시를 보겠습니다. 먼저, LSP를 적용하기 전입니다.
정사각형은 직사각형의 일종이니, Square 클래스는 Rectangle 클래스를 상속받아 만들었습니다.
정사각형의 너비는 높이와 같으므로, 너비만 이용하여 넓이를 구할 수 있습니다.
넓이를 구하는 getArea 메서드는 부모클래스(Rectangle)에서는 너비 * 높이를 이용했지만,
서브클래스(Square)에서는 너비 * 너비를 이용하여 넓이를 구함으로써 메서드의 일관성이 깨지게 됩니다.
그럼 어떻게 LSP를 적용할 수 있을까요?
먼저, 부모클래스의 기능의 재정의를 지양하는 방법이 있습니다.
그리고 불가피한 상황이라면, 프로토콜을 통해 적용할 수 있는 방법이 있습니다.
LSP를 적용한 예시를 보겠습니다.
Interface Segregation Principle
"클라이언트는 사용하지 않는 메서드를 강제로 구현해서는 안된다."
프로토콜을 채택할 때, 우리는 프로토콜에 명시된 메서드를 구현해줘야 합니다.
그런데, 우리는 프로토콜의 일부 메서드가 필요가 없을 경우가 있습니다.
예시를 살펴보겠습니다.
위와 같은 상황에서, DoubleTapButton은 didDoubleTap 메서드만 필요하고 나머지 메서드는 필요가 없습니다.
하지만 TapGesture라는 프로토콜을 채택했기 때문에, 필요없는 메서드라도 구현해야 합니다.
그럼 ISP를 적용하기 위해서는 어떻게 수정해야 할까요?
큰 프로토콜을 작은 프로토콜로 나누어 처리하는 방법이 있습니다.
Dependency Inversion Principle
"상위 모듈은 하위 모듈에 의존해서는 안된다.
둘 다 추상화 된 인터페이스에 의존해야 한다.
추상화는 세부사항에 의존해선 안된다. 세부사항이 추상화에 의존해야 한다."
예시를 보겠습니다.
제가 운동을 하는 상황이라고 생각하면 될 것 같습니다.
RedFitness는 헬스장이고, Jack은 RedFitness에서 근무하는 트레이너입니다.
위 예시에서 Yeonpark 클래스는 RedFitness 프로퍼티를 가지고 있고, RedFitness는 Jack이라는 프로퍼티를 가지고 있습니다.
따라서, Yeonpark은 RedFitness에 의존성이 있고, RedFitness는 Jack에 의존성이 있습니다.
위 예시에서는 더 바뀌기 쉬운 클래스에 의존하고 있는만큼 유지보수가 어렵습니다.
만약, 제가 RedFitness에서 TomatoFitness로 헬스장을 옮긴다고 가정했을 때, 고쳐야할 코드의 양이 많기 때문입니다.
그럼, DIP를 적용하기 위해서는 어떻게 해야할까요?
Yeonpark은 이제 변하기 쉬운 RedFitness에 의존하지 않고, 추상화 된 GYM이라는 인터페이스에 의존하게 됩니다.
또한, GYM 프로토콜을 채택한 TomatoFitness도 더이상 구체화된 트레이너(Jack)에 의존하지 않고, Trainer라는 추상화 된 인터페이스에 의존하게 됩니다.
이제 다니는 헬스장을 바꾸더라도, 혹은 헬스장의 트레이너가 바뀌었더라도 상위 모듈이 영향을 받지 않게 되었습니다.
'iOS > Wiki' 카테고리의 다른 글
POP(Protocol-Oriented Programming) in Swift (1/2) (0) 2022.03.28 ReactorKit 사용기 (0) 2022.03.05 Swift ARC(Automatic Reference Counting) (0) 2022.01.15 Localization (0) 2022.01.14 페이지네이션(Pagination) (0) 2022.01.13