OCP 법칙
- 확장에는 열려있고, 변경에는 닫혀있어야 한다.
'확장에는 열려있고’라는 말은 손 쉽게 기능 추가를 할 수 있고(기존의 코드 변경 없이), ‘변경에는 닫혀있다’라는 말은 기능 추가 및 수정을 할 때, 여러 코드들이 한꺼번에 같이 수정된다는 말입니다.
그렇다면, 이러한 OCP 법칙을 만족하지 못하는 상황이 뭐가 있을까요?
일단 무분별하게 사용하고 있는 enum을 생각해볼 수 있습니다.
Enum에 case를 하나 추가하는 순간, 해당 enum을 switch문으로 분기처리하고 있는 곳에서 새로운 코드를 추가해주어야합니다. 그렇다면, 이러한 문제점을 해결할 수 있는 방법이 무엇이 있을까요?
평소에 저희는 OCP법칙을 만족시키기 위하여 프로토콜을 적극적으로 활용했습니다.
이러한 문제점 또한 enum을 protocol로 변경함으로써 해결해볼 수 있습니다.
(해당 글은 https://medium.com/swift-fox/refactoring-replace-enum-with-polymorphism-c4803baeba07 을 토대로 작성한 글입니다!)
저희가 흔히 활용하는 enum의 예를 봐보겠습니다.
enum Vehicle {
enum Speed {
case fast // over 400km
case middle // 400km ~ 50km
case slow // ~ 50 km
}
case car
case bicyle
case airplane
var noise: String {
switch self {
case .car:
return "brrr"
case .bicyle:
return "shhh"
case .airplane:
return "siuu!"
}
}
var speed: Speed {
switch self {
case .car:
return .middle
case .bicyle:
return .slow
case .airplane:
return .fast
}
}
}
위와 같이 Vehicle의 case로 자동차, 비행기, 자전거가 존재합니다. 그리고 해당 케이스 별 switch 구문을 통하여 speed와 nosie를 구현했습니다.
만약 여기서 새로운 case가 추가된다면 어떻게 될까요?
noise, speed 속 switch문 뿐만 아니라 enum 객체를 활용하는 다른 곳에서도 계속해서 swicth 문으로 분기처리를 해주어야합니다. 즉, 확장 시 다른 코드에도 영향을 주므로 OCP 법칙을 위반하는 상황이 생깁니다.
그러면 같은 코드를 프로토콜로 구현해보면 어떻게 될까요?
먼저 아래와 같이 Vehicle 프로토콜을 정의해줍니다.
protocol Vehicle {
var noise: String { get }
var speed: Speed { get }
}
enum Speed {
case fast // over 400km
case middle // 400km ~ 50km
case slow // ~ 50 km
}
그리고, 똑같이 여러 타입을 정의하여 Vehicle 프로토콜을 채택합니다.
struct Car: Vehicle {
var noise: String = "brrr"
var speed: Speed = .middle
}
struct Bicycle: Vehicle {
var noise: String = "shhh"
var speed: Speed = .slow
}
struct Airplane: Vehicle {
var noise: String = "siuu"
var speed: Speed = .fast
}
이렇게 코드를 구현할 경우, switch문 분기 없이 훨씬 깔끔하게 구현할 수 있습니다.
매번 반복되는 switch 코드를 제거할 수 있었고, 새로운 case를 추가하는 것은 그냥 프로토콜을 채택한 새로운 타입을 정의해주면 됩니다. 이 과정에서 다른 메소드를 건드릴 필요가 없습니다.
즉, OCP 법칙을 만족하는 코드를 작성할 수 있습니다!
그렇다면, enum 코드는 언제 활용하는 것이 좋을까요?
제가 생각하기에 enum 코드는 수정할 가능성이 적은 코드에서 사용하는 것이 좋은 것 같습니다. 즉 case가 계속해서 추가될 가능성이 있는 코드는 애초에 enum 코드 보다는 프로토콜 정의 후, 새로운 타입으로 구현하는 것이 좋습니다.
여기서 말하는 case가 추가될 가능성이 없다는 말은 enum을 정의하는 과정에서 이미 모든 case를 개발자가 알고있는 상태를 말합니다. 그래서 정해진 옵션 밖에 없을 때(확장 가능성이 적을 때), 주로 enum을 활용합니다.
위의 예를 통해서 다시 생각해보겠습니다.(극단적인 예 입니다만..😅)
Vehicle 타입의 경우, 해당 타입에 만족할 수 있는 타입을 수도 없이 만들 수 있습니다. 차, 자전거, 비행기 외에 오토바이나 킥보드등이 추가될 수 있겠죠. 이럴 경우에는 Vehicle을 enum으로 정의하여, 각각을 case로 설정하는 것은 너무나도 비효율적입니다.
그러나, Speed의 경우에는 Vehicle과 같이 더 이상 추가될 케이스가 없습니다. 빠름(400km~), 적당한 속도(50km~400km), 느린 속도(~50km) 말고는 다른 케이스를 생성할 일이 없습니다. 이러한 경우에는 enum을 활용하기에 적당하다고 생각이 듭니다.
Enum을 잘 사용하면 가독성 측면에서 정말 좋지만, 무분별하게 활용을 할 경우 유지 보수 측면에서 좋지 못할 수 있습니다. 앞서 말씀드린 것과 같이 Enum을 쓰는 것보다 Protocol을 활용하는 것이 좋지 않을 까를 먼저 고민해보고 enum을 활용해보는 습관을 들이는 것이 중요할 것 같습니다.
(부족한 부분이 많습니다. 피드백 주시면 열심히 공부해서 수정하겠습니다! 감사합니다 ☺️)
'Swift' 카테고리의 다른 글
[Swift] WWDC16 Understanding Swift Performance(2) (0) | 2023.07.31 |
---|---|
[Swift] WWDC16 Understanding Swift Performance(1) (0) | 2023.07.31 |
[Swift] 다형성과 추상화 (0) | 2023.07.14 |
[Swift] IUO(옵셔널 암시적 추출) (0) | 2023.06.12 |
[Swift] self는 언제 쓸까? (0) | 2023.04.13 |