티끌모아 태산

⭐다형성과 설계 본문

백엔드/JAVA

⭐다형성과 설계

goldpig 2024. 6. 2. 15:45
728x90

객체 지향 프로그래밍

객체지향 프로그래밍은 컴퓨터 프로그램을 객체들의 모임으로 보는 것이다. 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다. (협력) 그리고 프로그램을 유연하고 변경 용이하게 만든다. 즉, 레고 블록 조립하듯이 개발을 할 수 있다. 

다형성(Polymorphism)

객체 지향 프로그래밍의 가장 핵심적인 특징은 다형성이다. 다형은 역할과 구현을 분리시킨다. 아래 그림과 자동차의 역할과 자동차의 구현을 분리시킨다. 다형성의 본질은 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다는 것이다. 

역할과 구현을 분리

  • 클라이언트(운전자)는 역할(인터페이스)만 알면 된다. 즉, 인터페이스를 구현하는 K3, 아반떼, 테슬라 모델3의 세부적인 내부 구조를 몰라도 된다. 
  • 클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다. K3, 아반떼 등의 내부 구조가 바뀌어도 영향X
  • 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다. 즉, 자동차가 K3이든, 아반떼로 바뀌든 운전자에게는 영향을 주지 않는다.

객체 지향 프로그래밍의 핵심은 다형성이다. 디자인 패턴 대부분은 다형성을 활용하는 것이다. 스프링의 핵심인 제어의 역전(IoC), 의존관계 주입(DI)도 결국 다형성을 활용하는 것이다. 그리고 프레임워크인 스프링을 활용하면 레고 블럭 조립하듯이 구현을 편리하게 변경할 수 있다

역할과 구현 예제

다형성을 활용하면 역할과 구현을 분리해서, 클라이언트 코드의 변경 없이 구현 객체를 변경할 수 있다. 다음 관계에서 Driver가 클라이언트이다. 

  • 운전자(Driver)는 Car의 역할에만 의존한다. 구현인 K3, Model3 자동차에 의존하지 않는다
  • Driver 클래스는 Car car 멤버변수를 갖는다. 따라서 Car 인터페이스를 참조한다.
  • 인터페이스를 구현한 K3Car, Model3Car에 의존하지 않고, Car 인터페이스에만 의존한다.
  • 의존은 클래스 의존 관계를 뜻한다. 즉 클래스 상에서 어떤 클래스를 알고 있는가를 뜻한다. Driver 클래스 코드를 보면 Car 인터페이스만 사용하는 것을 알 수 있다. 
  • Car는 자동차의 역할이고 인터페이스다. K3Car, Model3Car 클래스가 인터페이스를 구현한다.
public class Driver {
    private Car car; // 멤버변수

    public void setCar(Car car) {
        System.out.println("자동차를 설정합니다. " + car);
        this.car = car;
    }

    public void drive(){
        System.out.println("자동차를 운전합니다.");
        car.startEngine();
        car.pressAccelerator();
        car.offEngine();
    }
}
  • Driver는 멤버 변수로 Car car를 갖는다. setCar(Car car)를 통해 멤버 변수에 자동차를 설정한다. 외부에서 누군가 이 메서드를 호출해주어야 Driver는 새로운 자동차를 참조하거나 변경할 수 있다. 
  • drive() 메서드는 Car 인터페이스가 제공하는 기능들을 통해 자동차를 운전한다.

  • driver.setCar(k3Car)를 호출해서 Driver의 Car car 필드가 K3Car의 인스턴스를 참조하도록 한다. 
  • driver.drive()를 호출하면 x001을 참조한다. car 필드가 Car 타입이므로 Car 타입을 찾아서 실행하지만 메서드 오버라이딩에 의해 K3Car의 기능이 호출된다. 

  • Model3Car를 생성한다.
  • driver.setCar(model3Car)를 호출해서 Driver의 Car car 필드가 Model3Car의 인스턴스를 참조하도록 변경한다.
  • driver.drive()를 호출하면 x002을 참조한다. car 필드가 Car 타입이므로 Car 타입을 찾아서 실행하지만 메서드 오버라이딩에 의해 Model3Car의 기능이 호출된다. 

⭐OCP(Open-Closed Principle) 원칙

좋은 객체 지향 설계 원칙 중 하나는 OCP 원칙이다. 

  • Open for extension: 새로운 기능의 추가나 변경 사항이 생겼을 때, 기존 코드는 확장할 수 있어야 한다. 
  • Closed for modification: 기존의 코드는 수정되지 않아야 한다

위 말이 좀 헷갈릴 수 있는데, 기존 코드 확장은 인터페이스나 인터페이스를 구현하는 코드를 추가하거나 변경하는 것이고 기존 코드를 수정하지 않아야 한다는 것은 클라이언트 코드를 변경하지 않는다는 뜻이다. 다시 말해, 클라이언트 코드를 변경하지 않고 서버의 구현 기능을 유연하게 변경하는 것이다. 

클라이언트인 Driver는 인터페이스인 Car에만 의존하고 인터페이스를 구현하는 K3Car, Model3Car, NewCar에 대해서는 알지 못한다. 그렇기 때문에 새로운 차량을 추가해도 Driver의 코드는 전혀 변경하지 않는다. 차량의 종류는 계속 늘어나도 이는 마찬가지다. 즉, 기능을 확장해도 main() 일부를 제외한 프로그램의 핵심 부분의 코드는 전혀 수정하지 않아도 된다. 

  • 확장에 열려있다는 뜻: Car 인터페이스를 사용해서 새로운 차량을 자유롭게 추가하거나 변경할 수 있다. 그리고 Car 인터페이스를 사용하는 클라이언트 코드인 Driver도 Car 인터페이스를 통해 새롭게 추가된 차량을 자유롭게 호출할 수 있다. 
  • 코드 수정은 닫혀 있다는 뜻: 새로운 차를 추가하게 되면 기능이 추가되기 때문에 기존 코드의 수정을 불가피하다. 하지만 클라이언트 코드의 수정은 일어나지 않는다
  • 변하지 않는 부분: 클라이언트 코드는 변하지 않는다.
  • 변하는 부분: main()과 같이 새로운 차를 생성하고 Driver에게 필요한 차를 전달해주는 역할은 당연히 코드 수정이 필요하다. main()은 전체 프로그램을 설정하고 조율하는 역할을 한다. 이런 부분은 OCP를 지켜도 변경이 필요하다. 

결국, Car를 사용하는 클라이언트 코드인 Driver 코드의 변경 없이 새로운 차량을 확장할 수 있다. 다형성을 활용하고 역할과 구현을 잘 분리한 덕분에 새로운 차량을 추가해도 대부분의 핵심 코드들을 그대로 유지할 수 있게 된다

전략 패턴(Strategy Pattern)

디자인 패턴 중 가장 중요한 패턴은 전략 패턴이다. 전략 패턴은 알고리즘을 클라이언트 코드의 변경 없이 쉽게 교체할 수 있다. 위 코드처럼 Car인 인터페이스가 바로 전략을 정의하는 인터페이스가 되고, 각각의 차량이 전략의 구체적인 구현이 된다. 그리고 전략을 클라이언트 코드(Driver)의 변경 없이 손쉽게 교체할 수 있다. 

728x90

'백엔드 > JAVA' 카테고리의 다른 글

다형성2  (0) 2024.06.01
10. 다형성(1)  (0) 2024.05.31
9. 상속  (0) 2024.05.30