base
개발을 하다 보면 기능이 점점 늘어나고 요구사항도 계속 바뀌기 마련입니다. 처음에는 깔끔하고 잘 작성된 코드가 시간이 지나면 점점 복잡해지고, 무언가를 수정할 때마다 연쇄적으로 다른 부분까지 영향을 미쳐서 코드를 건드리는 것이 두려워질 때도 있습니다. 이런 문제를 방지하고 코드를 안정적이고 깔끔하게 유지하기 위한 원칙이 바로 **개방 폐쇄 원칙(Open Closed Principle, OCP)**입니다.
이번 포스팅에서는 SOLID 원칙 중 두 번째 원칙인 OCP에 대해 명확하게 이해하고, 실제로 적용할 수 있는 방법을 예시와 함께 살펴보겠습니다.
개방 폐쇄 원칙(Open Closed Principle, OCP)은 로버트 C. 마틴(Robert C. Martin, Uncle Bob)이 제안한 SOLID 원칙 중 두 번째 원칙으로, 아래와 같이 정의됩니다. "소프트웨어 요소는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다." 조금 쉽게 말하자면, 기존에 잘 작동하는 코드는 그대로 두고, 새로운 기능을 추가할 때 기존 코드를 수정하지 않고도 확장할 수 있도록 만들어야 한다는 원칙입니다.
OCP를 잘 지키면 다음과 같은 장점이 있습니다:
이제 간단한 예시를 통해 OCP를 적용하기 전과 후를 비교해 보겠습니다.
다음은 결제 시스템에서 OCP를 위반한 코드입니다.
class PaymentProcessor {
processPayment(paymentType: string, amount: number) {
if (paymentType === 'credit') {
// 신용카드 결제 로직
} else if (paymentType === 'paypal') {
// 페이팔 결제 로직
}
// 새로운 결제 방식이 추가될 때마다 if문을 계속 추가해야 함
}
}
위 코드의 문제는 새로운 결제 방식이 추가될 때마다 기존 코드에 새로운 조건문이 계속 추가되어야 한다는 것입니다. 결국 코드가 복잡해지고 관리가 어려워지며, 변경에 매우 취약한 구조가 됩니다.
OCP를 적용하면 다음과 같이 변경할 수 있습니다.
interface PaymentMethod {
pay(amount: number): void
}
class CreditCardPayment implements PaymentMethod {
pay(amount: number) {
// 신용카드 결제 로직
}
}
class PayPalPayment implements PaymentMethod {
pay(amount: number) {
// 페이팔 결제 로직
}
}
class PaymentProcessor {
processPayment(paymentMethod: PaymentMethod, amount: number) {
paymentMethod.pay(amount)
}
}
이제 새로운 결제 방식을 추가할 때는 새로운 클래스만 추가하면 됩니다.
class KakaoPayPayment implements PaymentMethod {
pay(amount: number) {
// 카카오페이 결제 로직
}
}
// 새로운 결제 방식 추가 시 기존 코드는 전혀 수정하지 않음
const processor = new PaymentProcessor()
processor.processPayment(new KakaoPayPayment(), 50000)
이렇게 하면 기존 코드의 변경 없이 손쉽게 시스템을 확장할 수 있으며, 코드 유지보수가 편리해집니다.
OCP를 적용할 때는 아래와 같은 사항들을 주의해야 합니다:
지금까지 개방 폐쇄 원칙(OCP)에 대해 살펴본 내용을 다시 한번 요약하면 다음과 같습니다.
결국 OCP를 지키는 것이 장기적으로는 소프트웨어 품질을 높이고 개발 생산성을 증가시키는 가장 효과적인 방법 중 하나입니다.