개방 폐쇄 원칙(Open-Close Principle, OCP)
정의
- 소프트웨어의 구성요소(컴포넌트, 클래스, 모듈, 함수 등)는 확장에는 열려있고, 변경에는 닫혀있어야 한다.
확장에는 열려있어야 한다
- 새로운 기능을 추가하거나, 새로운 동작을 추가하는 등의 확장이 가능해야 한다.
변경에는 닫혀있어야 한다
- 새로운 기능을 추가하거나, 새로운 동작을 추가할 때, 기존 코드를 수정하지 않아야 한다.
적용
첫번째 시나리오
아무개 보험회사
에서 건강 보험 을 판매한다.InsuranceDiscountCalculator
를 통해서 보험 할인율을 계산한다.VIP
고객은 25% 할인을,일반
고객은 10% 할인을 받는다.
// 고객 프로필
public class HealthInsuranceCustomerProfile {
private String name;
private int age;
private boolean isVIP;
...
public boolean isVIP() {
return isVIP;
}
...
}
// 보험 할인율 계산기
public class InsuranceDiscountCalculator {
public double calculate(HealthInsuranceCustomerProfile profile) {
if (profile.isVIP()) {
return 0.75;
}
return 0.9;
}
}
아무개 보험회사
에서 자동차 보험 을 판매하기로 했다.VIP
와일반
고객의 할인율은 건강 보험 과 동일하며, 그대로InsuranceDiscountCalculator
를 사용하기로 했다.- 자, 우선
CarInsuranceCustomerProfile
을 만들어보자.
// 자동차 보험 고객 프로필
public class CarInsuranceCustomerProfile {
private String name;
private String carModel;
private boolean isVIP;
...
public boolean isVIP() {
return isVIP;
}
...
}
- 보험 할인율의 계산은 그대로
InsuranceDiscountCalculator
를 사용하기로 하였다. - 자, 한 번 할인율을 계산해보자.
public static void main(String[] args) {
HealthInsuranceCustomerProfile healthProfile = new HealthInsuranceCustomerProfile("홍길동", 20, true);
CarInsuranceCustomerProfile carProfile = new CarInsuranceCustomerProfile("홍길동", "아반떼", true);
InsuranceDiscountCalculator calculator = new InsuranceDiscountCalculator();
System.out.println(calculator.calculate(healthProfile));
System.out.println(calculator.calculate(carProfile)); // 컴파일 에러
}
// 보험 할인율 계산기
public class InsuranceDiscountCalculator {
public double calculate(HealthInsuranceCustomerProfile profile) {
if (profile.isVIP()) {
return 0.75;
}
return 0.9;
}
}
InsuranceDiscountCalculator
는HealthInsuranceCustomerProfile
을 사용하도록 설계되어 있다.- 개방 폐쇄 원칙에 따라,
InsuranceDiscountCalculator
를 수정하지 않고,CarInsuranceCustomerProfile
을 사용할 수 있어야 하나, 자동차 보험 고객의 할인율을 계산하려면InsuranceDiscountCalculator
를 수정해야 한다.
// 수정된 보험 할인율 계산기
public class InsuranceDiscountCalculator {
// 기존 메소드
public double calculate(HealthInsuranceCustomerProfile profile) {
if (profile.isVIP()) {
return 0.75;
}
return 0.9;
}
// 메소드 오버로딩
public double calculate(CarInsuranceCustomerProfile profile) {
if (profile.isVIP()) {
return 0.75;
}
return 0.9;
}
}
- 만약
아무개 보험회사
가 계속해서 사업을 확장해 나아가면서 화재 보험, 반려동물 보험 등을 판매하기로 했다면,InsuranceDiscountCalculator
는 계속해서 수정되어야 한다.
두번째 시나리오
스마트 보험회사
에서 건강 보험 을 판매한다.- 스마트한 사장은 회사가 확장될 것을 미리 예상하고,
CustomerProfile
이라는 인터페이스를 만들어 두었다.
// 고객 프로필 인터페이스
public interface CustomerProfile {
boolean isVIP();
}
// 건강 보험 고객 프로필
public class HealthInsuranceCustomerProfile implements CustomerProfile {
private String name;
private int age;
private boolean isVIP;
...
@Override
public boolean isVIP() {
return isVIP;
}
...
}
- 이제
스마트 보험회사
의InsuranceDiscountCalculator
를CustomerProfile
을 사용하는 방식으로 구성해보자.
// 스마트 보험회사의 보험 할인율 계산기
public class InsuranceDiscountCalculator {
public double calculate(CustomerProfile profile) {
if (profile.isVIP()) {
return 0.75;
}
return 0.9;
}
}
스마트 보험회사
에서 자동차 보험 을 판매하기로 했다.VIP
와일반
고객의 할인율은 건강 보험 과 동일하며, 그대로InsuranceDiscountCalculator
를 사용하기로 했다.- 자, 우선
CarInsuranceCustomerProfile
을 만들어보자.
// 자동차 보험 고객 프로필
public class CarInsuranceCustomerProfile implements CustomerProfile {
private String name;
private String carModel;
private boolean isVIP;
...
@Override
public boolean isVIP() {
return isVIP;
}
...
}
- 보험 할인율의 계산은 그대로
InsuranceDiscountCalculator
를 사용하기로 하였다. - 자, 한 번 할인율을 계산해보자.
public static void main(String[] args) {
HealthInsuranceCustomerProfile healthProfile = new HealthInsuranceCustomerProfile("홍길동", 20, true);
CarInsuranceCustomerProfile carProfile = new CarInsuranceCustomerProfile("홍길동", "아반떼", true);
InsuranceDiscountCalculator calculator = new InsuranceDiscountCalculator();
System.out.println(calculator.calculate(healthProfile));
System.out.println(calculator.calculate(carProfile));
}
- 기존의
InsuranceDiscountCalculator
는CustomerProfile
을 사용하도록 설계되어 있어,HealthInsuranceCustomerProfile
과CarInsuranceCustomerProfile
을 모두 사용할 수 있다. - 개방 폐쇄 원칙에 따라,
InsuranceDiscountCalculator
를 수정하지 않고,CarInsuranceCustomerProfile
을 사용할 수 있어야 한다. - 추후에
스마트 보험회사
에서 화재 보험, 반려동물 보험 등을 판매하기로 했다면,InsuranceDiscountCalculator
는 수정할 필요가 없다.
왜 개방 폐쇄 원칙이 필요한가?
- 만약에 개방 폐쇄 원칙 을 지키지 않았다면, 계속해서 기능을 확장해 나아가는 과정에서
InsuranceDiscountCalculator
는 계속해서 수정되어야 했을 것이다. - 개방 폐쇄 원칙 은 추상 클래스 또는 인터페이스를 사용하도록 권장한다. 구체적인 구현이 아닌 인터페이스로 코딩하면 클래스 간의 종속성을 줄여 느슨한 결합(Loose Coupling) 을 유도할 수 있다.
- 다형성을 통해 인터페이스를 준수하는 구체 클래스들은 자유롭게 교체할 수 있으며, 이를 통해 기존 코드를 변경하지 않고도 새로운 기능을 추가할 수 있다.