OCP란?

OCP (Open-Closed Principle)는 소프트웨어 디자인 원칙 중 하나로, 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다는 원칙을 나타냅니다. 이 원칙은 로버트 C. 마틴(Robert C. Martin)이 개발한 SOLID 원칙 중 하나로, 소프트웨어 시스템을 유지보수하고 확장하기 쉽게 만드는 데 기여합니다.

OCP의 주요 개념은 다음과 같습니다.

확장에는 열려 있어야 함(Open for Extension): 소프트웨어 엔티티(클래스 또는 모듈)는 새로운 기능을 추가하거나 변경할 수 있어야 합니다. 이것은 새로운 요구 사항이나 기능 추가에 대응하기 위한 확장 가능성을 의미합니다.

변경에는 닫혀 있어야 함(Closed for Modification): 소프트웨어 엔티티는 이미 동작 중인 코드를 변경하지 않아야 합니다. 새로운 기능을 추가하거나 수정하기 위해서는 이미 작성된 코드를 변경하지 않고도 가능해야 합니다.

스프링 프레임워크는 OCP 원칙을 따르도록 설계되었습니다. 스프링은 인터페이스, 추상 클래스, 의존성 주입(Dependency Injection), AOP(Aspect-Oriented Programming) 등을 통해 시스템을 확장 가능하고 변경에 닫힌 형태로 구성합니다. 이러한 디자인은 스프링 애플리케이션을 유지보수하고 확장하기 쉽게 만들어주며, 새로운 기능을 추가하거나 기존 기능을 수정할 때 코드의 안정성과 일관성을 유지하는 데 도움이 됩니다.

그럼 어떻게 하면 좋을 수 있을까?

예제로 은행에 조회를 요청하는 메서드를 작성 중 각 은행의 요구사항에 맞는 컷오프를 설정할때를 얘로 들어 볼 예정이다.

OCP 체험 해보기

추상화

우선 다음과 같이 BankCutOff라는 interface로 추상화를 구현한다.

public interface BankCutOff {

    boolean isSameBank(Bank bank);

    boolean isCutOff();

}

그다음으로 은행을 판별할 enum을 생성해 주고

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum Bank {

    HANA,
    SC,
    WOORI,

    ;

}

추상화 구현

그다음으로 BankCutOff를 구현하는 구현체 3가지를 구현해 보자.

@Service
@Slf4j
public class HanaBankCutOff implements BankCutOff {

    private static final String BANK_NAME = "HANA_BANK";

    @Override
    public boolean isSameBank(Bank bank) {
        return bank == Bank.HANA;
    }

    @Override
    public boolean isCutOff() {
        log.info("하나 은행 컷오프 실행");
        return true;
    }

}
@Service
@Slf4j
public class ScBankCutOff implements BankCutOff {

    @Override
    public boolean isSameBank(Bank bank) {
        return bank == Bank.SC;
    }

    @Override
    public boolean isCutOff() {
        log.info("제일 은행 컷오프 실행");
        return false;
    }

}
@Service
@Slf4j
public class WooriBankCutOff implements BankCutOff {

    @Override
    public boolean isSameBank(Bank bank) {
        return bank == Bank.WOORI;
    }

    @Override
    public boolean isCutOff() {
        log.info("우리 은행 컷오프 실행");
        return true;
    }

}

다음과 같이 구현한 후

핸들러 구현

마지막으로 아래와 같은 핸들러를 하나 구현해 보자

@RequiredArgsConstructor
@Component
public class BankCutOffHandler {

    private final Set<BankCutOff> bankCutOffs;

    public boolean getBankCutOff(Bank bank) {
        BankCutOff cutOff = bankCutOffs.stream()
                .filter(bankCutOff -> bankCutOff.isSameBank(bank))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("BAD REQUEST"));
        return cutOff.isCutOff();
    }

}

테스트 코드 작성

image

테스트 코드를 다음과 같이 작성 후 결과를 확인해보면~~~~~~

결과

image

잘 실행되는 걸 알 수 있다.

image