처음부터 차근차근

[OOP] 객체 지향 프로그래밍에서 다형성 본문

Architecture pattern/Design Pattern

[OOP] 객체 지향 프로그래밍에서 다형성

HangJu_95 2024. 1. 3. 13:45
728x90

이번 포스팅에서는 객체 지향 프로그래밍의 특징 중 하나인 다형성에 대해 조금 더 깊게 공부해보겠습니다.

객체 지향 프로그래밍에 대한 포스팅을 먼저 보고 오는 것을 추천드립니다.

 

[OOP] 객체 지향 프로그래밍이란?

객체 지향 프로그래밍(OOP)란? 컴퓨터 프로그램을 명령어릐 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것 - wikipedia 프로그램 구현에 필요한

hangju95.tistory.com

다형성??

다형성(Polymrophism)

어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있는 성질

 

즉, 어떤 객체의 속성이나 기능이 그 맥락에 따라 다른 역할을 수행할 수 있는 객체 지향의 특성을 의미합니다.

이를 통해 객체 지향 프로그래밍 언어에서는 부모 클래스의 참조 변수로 자식 클래스의 참조 변수를 다루거나, 동일한 이름을 같은 여러 형태의 메소드를 만들 수 있도록 하고 있습니다.

 

자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있게 함으로써 다형성을 프로그램적으로 구현하였습니다.

조금 더 구체적으로 표현하자면, 부모 클래스 타입의 참조변수로 자식 클래스 인스턴스를 참조할 수 있도록 하였다는 내용입니다.

(다른 언어도 비슷한 형태인것 같은데..?) 

오버로딩, 오버라이딩, 업캐스팅, 다운캐스팅, 인터페이스, 추상메소드, 추상 클래스 방법 모두 다형성에 속한다고 생각하면 편합니다.

 

즉, 다형성은 클래스가 상속 관계에 있을 떄 나타나는 다채로운 성질인 것입니다.

다형성을 실세계에 비유해보자.

간단한 예시를 하나 들어보겠습니다.

실 세계에서 면허증이 있는 사람은 자동차를 몰 수 있습니다. 또한 수 많은 자동차가 구현되어 있습니다.

이때 K3, 아반떼, 스포티지 등 다양한 자동차가 각각 동작 방법이 제각각이라면?

운전자는 각각의 동작 방법을 익혀야 합니다.

 

그러나 자동차를 구현할 때는 동작 방법이라는 역할(동작 설계도라고 하면 편할지.. 인터페이스라고 하겠습니다.)을 동일하게 구현해야 합니다. 이를 통해 운전자가 각각의 동작 방법을 익힐 필요 없이 간단히 자동차를 동작시키는 방법만 알면 됩니다.

 

이런 예시 말고도 다양한 예시가 존재합니다.

  • 운전자 - 자동차
  • 정렬 알고리즘 : 정렬을 할껀데, 구현이 간단한 버블 정렬을 할 것인가? 아니면 빠른 Merge 정렬을 사용할건가?
  • 할인 정책 로직
    • 전체 금액에서 일정한 금액을 제외하는 역할이 할인입니다.
    • 정액 할인인가? 아니면 정률 할인인가?? 이것은 구현해야 합니다.

역할과 구현을 분리

  • 역할구현으로 구분하면 세상이 단순해지고, 유연해지며 변경도 편리해집니다.
  • 클라이언트는 대상의 역할(인터페이스)만 알면 된다.
  • 클라이언트는 구현 대상의 내부 구조를 몰라도 된다.
    • 차 내부 구조 (엔진, 동력 장치, 히터 등..)을 몰라도, 어떻게 동작시키는지만 알면 됩니다.
  • 클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다.
    • 갑자기 엔진 1 -> 엔진 2 로 업그레이드 되었다고 해서 운전하는 방법이 변하지는 않습니다.
  • 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.
    • K3와 Avante는 구현 대상 자체가 다른데, 둘 다 운전할 수 있습니다.

객체 지향에서는 이를 어떻게 분리할까요?

자바, 혹은 Typescript 등 프로그래밍 언어에서는 이렇게 구분합니다.

  • 역할 : 인터페이스 or 추상 클래스
  • 구현 : 이것을 이용하여 구현한 실제 클래스, 구현 객체

따라서, 객체를 설게할 때 역할과 구현을 명확히 분리해야 합니다.

객체의 협력이라는 관계를 생각해봅시다.

객체 지향 프로그래밍을 구현할 때, 혼자 있는 객체는 없습니다.

클라이언트를 요청, 서버를 응답이라고 해보면, 수 많은 객체 클라이언트와 객체 서버는 서로 협력 관계를 가집니다.

코드 예제

위 그림에서는 Driver가 클라이언트입니다. 한번 Java를 통해 구현해보겠습니다.

먼저 Car라는 역할(인터페이스)을 구현하겠습니다.

package poly.car1;

public interface Car {
    void startEngine();
    void offEngine();
    void pressAccelerator();

}

위 기능은 단순하게 Car의 역할이 무엇인지 적어두었으며, 내부 동작 코드는 구현하지 않았습니다.

단순히 Car라는 타입은 이러한 동작이 있어야 한다! 라고 역할을 부여했슴니다.

 

그렇다면 이번에는 Car를 한번 실제로 구현해보겠습니다.

package poly.car1;

public class K3Car implements Car{
    @Override
    public void startEngine() {
        System.out.println("K3Car.startEngine");
    }

    @Override
    public void offEngine() {
        System.out.println("K3Car.offEngine");
    }

    @Override
    public void pressAccelerator() {
        System.out.println("K3Car.pressAccelerator");
    }
}
package poly.car1;

public class Model3Car implements Car{
    @Override
    public void startEngine() {
        System.out.println("Model3Car.startEngine");
    }

    @Override
    public void offEngine() {
        System.out.println("Model3Car.offEngine");
    }

    @Override
    public void pressAccelerator() {
        System.out.println("Model3Car.pressAccelerator");
    }
}

두 가지 차를 구현하였습니다.

이것은 Car라는 설계도를 받아 실제로 동작을 구현하였습니다. 하지만 동작하는 방법(메서드)의 이름은 동일합니다.

단지 Model3 냐 K3이냐의 차이일 뿐입니다.

 

그렇다면 이제 Driver가 이제 조종을 해볼까요??

package poly.car1;

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();
    }
}

운전자는 setCar를 통해서 차의 종류를 선택할 수 있습니다.

이때 매개변수로 car를 받는데, Car 설계도를 통해 만든 구현체만 가능하죠.

만약 비행기를 여기다가 넣는다면? 조종할 수 없습니다.

 

이제 운전자가 차를 고르고 운전하는 코드를 작성해보겠습니다.

public class CarMain1 {
    public static void main(String[] args) {
        Driver driver = new Driver();

        //차량 선택(k3)
        Car k3Car = new K3Car();
        driver.setCar(k3Car);
        driver.drive();

        //차량 변경(k3 -> model3)
        Car model3Car = new Model3Car();
        driver.setCar(model3Car);
        driver.drive();
    }
}

 

이 부분을 그림을 통해 한번 알아보겠습니다.

클라이언트(운전자)는 서버(Car)에 K3나 Model3를 받을 수 있습니다.

그리고 클라이언트는 이를 어떻게 동작시키는 지 알기만 하면 되고, Car 인터페이스를 통해 이미 알고 있습니다.

만약 K3에서 Model3로 갈아타고 싶다면, setCar를 통해 자동차를 바꾸기만 하면 됩니다. 왜냐면 동작시키는 기능은 똑같기 때문입니다.

다형성의 본질

위 예제를 통해 간단하게 다형성의 본질을 알아봤습니다.

즉 간단하게 정리하자면 이렇습니다.

  • 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있다.
  • 다형성의 본질을 이해하려면 협력이라는 객체 사이의 관계에서 시작해야 한다.
  • 클라이언트를 변경(수정)하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있어야 한다.

역할과 구현을 분리하면 단점도 존재하지 않나?

그러나 이것에는 한계점이 있습니다.

만약 역할(인터페이스) 자체가 변한다면, 클라이언트와 서버 모두에 큰 변경이 발생합니다.

 

예를 들어 지금 자동차에 엑셀과 브레이크의 위치를 변경해본다고 생각하다면, 클라이언트에도 큰 혼란과 변경이 오고, 서버(구현한 자동차)에도 많은 변화가 필요합니다.

 

정리

  • 실세계의 역할과 구현이라는 편리한 컨셉을 다형성을 통해 객체 세상으로 가져올 수 있다.
  • 유연하고, 변경이 용이
  • 확장 가능한 설계
  • 클라이언트에 영향을 주지 않는 변경 가능
  • 인터페이스를 안정적으로 잘 설계하는 것이 중요합니다.

참고

https://www.inflearn.com/course/김영한의-실전-자바-기본편/dashboard

https://inpa.tistory.com/entry/OOP-JAVA의-다형성Polymorphism-완벽-이해