다형성(Polymorphism)

이준호·2022년 9월 24일
0

자바

목록 보기
1/1
post-thumbnail

서론

이전에 객체지향 프로그래밍 언어를 배울 때 "객체지향의 꽃은 다형성이다" 라는 문구를 봤던 것 같다.
그 당시에는 다형성을 적용하는 코드만 이해하고 넘어갔지 다형성이 추구하는 바를 이해하지는 못했던 것 같다.
이번에 스프링을 공부하면서 다형성이 의미하는 바를 정리해보기로 했다.

다형성이란?

다형성을 사전에서는 다음과 같이 정의한다.

프로그램 언어의 다형성(多形性, polymorphism)은 그 프로그래밍 언어의 자료형 체계의 성질을 나타내는 것으로, 프로그램 언어의 각 요소들(상수, 변수, 식, 오브젝트, 함수, 메소드 등)이 다양한 자료형(type)에 속하는 것이 허가되는 성질을 가리킨다.

프로그래머는 글보다는 코드가 이해하기 빠르죠?

class Parent {
    public void method() {
        System.out.println("나는 Parent 메서드!");
    }
}

class Child extends Parent {
    @Override
    public void method() {
        System.out.println("나는 Parent 메서드를 오버라이드한 메서드!");
    }
}

public class Main {
    public static void main(String[] args) {
        Parent p = new Child();
        p.method();
    }
}

// 출력
나는 Parent 메서드를 오버라이드한 메서드!

여기까지는 다형성을 적용하는 방법만 아는 것이고 이후부터가 핵심이다.

다형성의 목적

이전에 자바를 공부할 때 위 코드를 보고 '굳이 좌변의 타입을 Parent로 해주는 이유가 뭐지? Child 인스턴스를 만들고 싶으면 좌변의 타입도 Child로 해주면 되는데 왜 이런 기능이 객체지향의 꽃이야?' 라고 생각한 적이 있다. 이제 왜 이런 기능이 필요한 지 알아보자!

[실생활 예제]
운전면허를 따기 위해 A운전면허 학원에서 아반떼 자동차로 주행 연습을 열심히 마치고 운전면허 시험장에 가니 K3 자동차로 시험을 보는 상황

이런 상황에서 연습했던 자동차와 시험보는 자동차의 종류가 다르다고 주행을 전혀 할 수 없는 경우는 없을 것이다.
그럼 자동차의 종류가 다른데도 주행이 가능한 이유는 무엇일까?
그건 자동차의 종류가 달라도 서로의 기능은 유사하기 때문이다. 액셀을 밟으면 앞으로 나갈 것이고 브레이크를 밟으면 정지하는건 모든 자동차에서 동일하니까!

운전면허만 있으면 자동차 종류가 달라도 사용이 가능하다는 것 이게 다형성이다.

interface Car {
    void pushAccel();

    void pushBrake();
}

class Avante implements Car {
    @Override
    public void pushAccel() {
        System.out.println("아반떼 자동차 전진!");
    }

    @Override
    public void pushBrake() {
        System.out.println("아반떼 자동차 정지!");
    }
}

class K3 implements Car {
    @Override
    public void pushAccel() {
        System.out.println("K3 자동차 전진!");
    }

    @Override
    public void pushBrake() {
        System.out.println("K3 자동차 정지!");
    }
}

class Person {
    public Car myCar;

    public void buyNewCar(Car c) {
        myCar = c;
    }
}

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        p.buyNewCar(new Avante());
        p.myCar.pushAccel();
        p.myCar.pushBrake();
        p.buyNewCar(new K3());
        p.myCar.pushAccel();
        p.myCar.pushBrake();
    }
}

운전면허를 따기 위해 자동차의 기능인 pushAccelpushBrake를 배웠다면 자동차의 사용자인 Person p는 자동차의 종류(Avante, K3)에 상관없이 기능을 사용할 수 있게 되고 그 기능의 내부 동작은 자동차의 종류에 따라 달라지게 되는 것이다.

[프로그래밍 예제]
친구들과 프로젝트를 진행하기로 하여 이런 저런 논의 끝에 데이터를 저장하고 삭제하는 기능을 넣기로 하였다. 하지만 현재 우리는 D.B를 다루는 기술을 전혀 모르고 메모리에서 작업하는 방식만 알아서 일단 메모리에서 작업하는 코드를 짜놓고 나중에 기술을 배워 D.B와 연결하려는 상황

이런 상황에 다형성은 아주 요긴하게 사용될 수 있다. 먼저 savedelete 메서드를 가지는 Interface를 정의하고 이를 상속받는 MemoryStorage 클래스에 save 로직, delete 로직을 작성해놓고 서비스 부분에 MemoryStorage를 끼워넣었다가 나중에 DBStorage로 갈아끼면 되는 것이다.

interface Storage {
    void save(String data);

    void delete(String targetData);
}

class MemoryStorage implements Storage {
    private List<String> memory = new ArrayList<>();

    @Override
    public void save(String data) {
        memory.add(data);
        System.out.println("메모리에 데이터 저장 완료!");
    }

    @Override
    public void delete(String targetData) {
        memory.remove(targetData);
        System.out.println("메모리에 해당 데이터 삭제 완료!");
    }
}

class DBStorage implements Storage {
    @Override
    public void save(String data) {
        // 나중에 배워서 코드 작성
    }

    @Override
    public void delete(String targetData) {
        // 나중에 배워서 코드 작성
    }
}

class Service {
    private Storage storage = new MemoryStorage(); // 나중에 DBStorage 작성 완료되면 new DBStorage()로 변경만 해주면 된다.

    public void saveData(String data) {
        storage.save(data);
    }

    public void deleteData(String data) {
        storage.delete(data);
    }
}

정리

결국 다형성이 가져오는 장점은 클라이언트(위 예제 코드에서는 Service를 작성하는 개발자)가 컴포넌트(위 예제 코드에서는 MemoryStorage, DBStorage)를 쉽고 유연하게 변경하면서 개발할 수 있게 해준다는 것이다.

그리고 이런 유용한 다형성을 잘 사용하기 위해서는 역할을 잘 정의하는 것이 중요하다.

역할구현을 명확히 분리하여 복잡한 문제를 단순하게 해결하는 것이 객체지향 프로그래밍이 탄생하게 된 이유인 것 같다.

profile
코딩 조아

0개의 댓글