후라이

객체지향에서의 다형성, 그리고 OCP 본문

Spring

객체지향에서의 다형성, 그리고 OCP

힐안 2024. 12. 7. 19:21

 

다형성

객체지향언어의 가장 큰 특징은 다형성이라고 할 수 있습니다. 이를 예시를 들어 설명하자면,

운전자(클라이언트)가 자동차라고 하는 클래스를 운전하는 상황입니다.

하지만, 자동차는 K5, 아반떼, 테슬라 등 종류야 여러가지가 될 수 있겠죠? 여기서 이 각각의 자동차들은 공통적으로

엑셀 밟으면 가고, 브레이크 밟으면 멈추고 이런 표준화된 기능들을 가질 겁니다.

여기서 운전자는 테슬라의 전기 모터 구조나 원리에 대해서는 알 필요가 없습니다. 즉, "구현"은 알지 않아도 됩니다.

 

  • 운전자: 자동차의 내부 구조나 구체적인 작동 방식(구현)을 몰라도 핸들, 브레이크 등 표준 인터페이스만으로 운전할 수 있다.
  • 자동차: 다양한 종류(K5, 아반떼, 테슬라)가 있지만, 표준화된 기능(운전)이 구현되어 있다.
  • 다형성: 운전자가 동일한 방식으로 다양한 자동차를 운전할 수 있게 하는 개념

이렇게 정리할 수 있겠습니다.

그래서, 코드적으로 말하면

  1. 운전자는 Car 인터페이스만 사용하고, 구체적인 자동차(K5, 아반떼, 테슬라)의 동작 방식을 알 필요가 없습니다.
  2. 운전자의 코드(Driver 클래스)는 수정할 필요 없이 새로운 자동차 클래스를 추가하면 확장 가능합니다.
    • 예를 들어, Genesis라는 새로운 자동차를 추가해도 기존 코드는 건드리지 않습니다.

OCP :Open-close principle

1. 문제 상황

결제 시스템에서 다양한 결제 수단(카드, 현금, 모바일 결제 등)을 처리해야 합니다.

잘못된 설계 (OCP 위반)

public class PaymentProcessor {
    public void processPayment(String method, int amount) {
        if (method.equals("card")) {
            System.out.println(amount + "원을 카드로 결제합니다.");
        } else if (method.equals("cash")) {
            System.out.println(amount + "원을 현금으로 결제합니다.");
        } else if (method.equals("mobile")) {
            System.out.println(amount + "원을 모바일로 결제합니다.");
        } else {
            throw new IllegalArgumentException("알 수 없는 결제 수단입니다.");
        }
    }
}

 

문제점:

  • 새로운 결제 수단(예: 암호화폐 결제)을 추가하려면 기존 processPayment 메서드를 수정해야 합니다.
  • 조건문이 많아지면서 가독성이 떨어지고 유지보수가 어렵습니다.
  • 기존 코드에 대한 수정은 오류를 범하기 쉽습니다.

2. OCP를 만족하는 설계

Java의 인터페이스다형성을 활용하면 OCP를 만족하는 설계를 할 수 있습니다.

리팩토링된 설계

  1. 결제 수단 인터페이스 정의
    공통된 인터페이스를 통해 다양한 결제 방식을 확장할 수 있습니다.
// 결제 수단 인터페이스
public interface PaymentMethod {
    void pay(int amount);
}
  1. 구체적인 결제 방식 구현
    각 결제 방식은 PaymentMethod 인터페이스를 구현합니다.
// 카드 결제
public class CardPayment implements PaymentMethod {
    @Override
    public void pay(int amount) {
        System.out.println(amount + "원을 카드로 결제합니다.");
    }
}

// 현금 결제
public class CashPayment implements PaymentMethod {
    @Override
    public void pay(int amount) {
        System.out.println(amount + "원을 현금으로 결제합니다.");
    }
}

// 모바일 결제
public class MobilePayment implements PaymentMethod {
    @Override
    public void pay(int amount) {
        System.out.println(amount + "원을 모바일로 결제합니다.");
    }
}
  1. 결제 처리 클래스
    PaymentProcessor 클래스는 결제 수단의 구체적인 구현에 의존하지 않고, PaymentMethod 인터페이스만 사용합니다.
public class PaymentProcessor {
    private PaymentMethod paymentMethod;

    public PaymentProcessor(PaymentMethod paymentMethod) {
        this.paymentMethod = paymentMethod;
    }

    public void processPayment(int amount) {
        paymentMethod.pay(amount);
    }
}

3. 사용 예시

이제 다양한 결제 수단을 처리할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        PaymentMethod card = new CardPayment();
        PaymentMethod cash = new CashPayment();
        PaymentMethod mobile = new MobilePayment();

        PaymentProcessor processor = new PaymentProcessor(card);
        processor.processPayment(1000);  // "1000원을 카드로 결제합니다."

        processor = new PaymentProcessor(cash);
        processor.processPayment(500);   // "500원을 현금으로 결제합니다."

        processor = new PaymentProcessor(mobile);
        processor.processPayment(2000); // "2000원을 모바일로 결제합니다."
    }
}

4. 새로운 기능 추가

암호화폐 결제를 추가해야 한다면, PaymentMethod를 구현하는 새로운 클래스를 추가하면 됩니다. 기존 코드를 변경할 필요가 없습니다.

// 암호화폐 결제
public class CryptoPayment implements PaymentMethod {
    @Override
    public void pay(int amount) {
        System.out.println(amount + "원을 암호화폐로 결제합니다.");
    }
}

// 사용 예시
public class Main {
    public static void main(String[] args) {
        PaymentMethod crypto = new CryptoPayment();

        PaymentProcessor processor = new PaymentProcessor(crypto);
        processor.processPayment(3000);  // "3000원을 암호화폐로 결제합니다."
    }
}

5. OCP 원칙 준수 결과

  1. 확장에는 열려 있음:
    새로운 결제 방식을 추가하려면 PaymentMethod 인터페이스를 구현하는 클래스를 추가하기만 하면 됩니다.
  2. 변경에는 닫혀 있음:
    기존의 PaymentProcessor 클래스나 다른 결제 방식 클래스는 수정되지 않습니다.

6. 정리

  • 확장을 위해 기존 코드를 변경하지 않고도 새로운 기능을 추가할 수 있습니다.
  • 핵심: 인터페이스와 다형성을 사용해 역할과 구현을 분리