OCP

안녕하세요 Jercy입니다. 오늘은 Swift에서의 OCP(Open Closed Principle, 개방폐쇄원칙)에 대해 알아보겠습니다.
OCP란 소프트웨어 개체(클래스, 함수 등)가 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다는 원칙입니다. 즉, 새로운 기능을 추가할 때는 기존 코드를 수정하지 않고 확장만으로 가능해야 하며, 추가된 코드로 인해 기존 코드가 영향 받아서는 안 된다는 뜻이죠.
예를 들어, 자동차 종류에 세단, SUV가 있는 상황을 가정해봅시다. OCP를 적용하지 않는다면 이렇게 if-else나 switch 문을 사용해야 할 것입니다:
enum CarType { case sedan case suv } func getCarInfo(type: CarType) { switch type { case .sedan: print("이 자동차는 세단이므로 연비는 11km/l입니다.") case .suv: print("이 자동차는 SUV이므로 연비는 6km/l입니다.") } }
Swift
복사
그런데 만약 해치백이라는 새로운 차종이 추가된다면 위 코드들을 모두 수정해야겠죠? 이는 OCP에 위배됩니다.
그 대신 OCP를 적용한다면 각 차종을 프로토콜을 준수하는 개별 타입으로 만들면 됩니다.
protocol Car { var fuelEconomy: String { get } var description: String { get } } struct Sedan: Car { var fuelEconomy: String { "11km/l" } var description: String { "이 자동차는 세단이므로 연비는 \\(fuelEconomy)입니다." } } struct SUV: Car { var fuelEconomy: String { "6km/l" } var description: String { "이 자동차는 SUV이므로 연비는 \\(fuelEconomy)입니다." } } let cars: [Car] = [Sedan(), SUV()] cars.forEach { print($0.description) }
Swift
복사
이렇게 하면 새 차종이 추가될 때 프로토콜을 준수하는 새 타입만 만들면 되므로 기존 코드를 건드릴 필요가 없게 됩니다. 즉, 확장에는 열려있고 변경에는 닫혀있게 되는 거죠.
하지만 OCP를 무분별하게 사용하면 오히려 복잡도만 높아질 수 있으니 주의해야 합니다. 예컨대 차종의 종류가 많지 않고 고정적인데 동작(인터페이스)만 계속 추가되는 경우라면 차종 자체를 추상화하기보다는 동작을 추상화해야 할 것입니다.
따라서 OCP는 확장 가능성이 있는 부분에 적용하되, 과도하게 사용하지 않도록 해야 합니다. 코드에 유연성이 필요한 시점이 되면 점진적으로 리팩터링해 나가는 게 좋겠죠. 이를 위해 의도적인 연습도 필요할 것 같네요.
이상으로 Swift에서의 OCP에 대해 알아보았습니다.
생각해볼 점:
SOLID 원칙 중 OCP가 중요한 이유는 무엇일까요?
어떤 경우에 OCP를 적용하는 것이 바람직할까요? 그 이유는 무엇인가요?
추천 답변:
소프트웨어를 확장하기 쉽고 변경으로 인한 사이드 이펙트를 최소화할 수 있기 때문입니다. 이는 소프트웨어의 유연성을 높이고 향후 변경 비용을 줄이는데 기여합니다.
변경이 거의 없고 안정적인 부분은 구체 타입을 사용해도 되지만, 새로운 요구사항이 자주 추가되는 등 확장 가능성이 높은 부분은 OCP를 적용하여 추상화하는 것이 좋습니다. 그래야 기존 코드를 수정하지 않고도 새로운 기능을 쉽게 추가할 수 있기 때문입니다.