도서/기술

[책] 데이터 지향 프로그래밍 - 13장 다형성

egg528 2025. 2. 9. 15:48

 OOP에서는 다형성을 활용해 여러 구현 클래스를 동일한 형태의 인터페이스로 다룰 수 있다. 반면 DOP에서는 OOP의 객체(클래스)를 데이터와 코드로 분리하고 데이터를 범용 자료 구조로만 다룬다. 때문에 데이터 유형에 따라 다른 클래스를 가지기보다는 필드 값 기반으로 구분하는 방향을 지향한다. 

 

 그렇다면 DOP에서는 어떻게 OOP의 다형성이 주는 이점을 누릴 수 있을까?

 

 

 

다형성의 핵심


interface IAnimal {
    public void greet();
}

class Dog implements IAnimal {
    private String name;

    // 생성자 추가
    public Dog(String name) {
        this.name = name;
    }

    public void greet() {
        System.out.println("Woof woof! My name is " + this.name);
    }
}

class Cat implements IAnimal {
    private String name;

    public Cat(String name) {
        this.name = name;
    }

    public void greet() {
        System.out.println("Meow! I am " + this.name);
    }
}

public class Main {
    public static void main(String[] args) {
        IAnimal dog = new Dog("Buddy");
        IAnimal cat = new Cat("Whiskers");

        dog.greet();
        cat.greet();
    }
}

 위 코드는 OOP 다형성의 예시이다. 이렇게 구현할 경우 우선 (1) greet()의 구현을 수정하려 할 때 모든 유형의 동물을 다루는 코드를 수정할 필요가 없다. 또한 (2) 유효한 타입이 아닐 경우 오류를 반환한다. 마지막으로 (3) 원래 코드를 수정하지 않고도 새로운 유형을 추가할 수 있다.

 

 하지만 생각해보면 단순히 switch 문을 활용하고 JSON 스키마로 validation을 추가한다면 1번과 2번은 충족시킬 수 있다. 하지만 3번 확장성의 경우 단순히 switch문 만으로는 얻을 수 없는 다형성만의 이점이다.

 

 

 

다중 메서드


 다형성의 이점을 DOP에서도 모두 이용하고자 책에서는 multi-method 라이브러리 활용을 권한다. (책에서 제안한 라이브러리를 찾아보니 github star 생각보다 너무 적어서 좀 당황스럽긴 하다...) 사용법은 간단한다.

 

 

단일 디스패치

function greetDispatch(animal) {
    if(dev()) {
        if(!ajv.validate(animalSchema, animal)) {
            var errors = ajv.errorsText(ajv.errors);
            throw ("greet called with invalid arguments: " + errors);
        }
    }
    return animal.type;
}

var greet = multi(greetDispatch);

 우선 데이터에서 분기의 기준이 되는 값을 반환하는 메서드를 정의하고 이를 활용해 메서드를 재정의한다. 

 

function greetDog(animal) {
    console.log("Woof woof! My name is " + animal.name);
}
greet = method("dog", greetDog)(greet);

function greetCat(animal) {
    console.log("Meow! I am " + animal.name);
}
greet = method("cat", greetCat)(greet);

 이후 타입 별 로직을 메서드로 구현하고 multi로 정의한 메서드에 적용하면 된다.

 

 

greet(myDog);
// Output: "Woof woof! My name is Fido"

greet(myCat);
// Output: "Meow! I am Milo"

 적용이 완료됐다면 greet 메서드를 사용해보자. 디스패처 메서드에 의해 기준 값이 반환되고 해당 값을 기준으로 적절한 메서드가 실행된다.

 

 

다중 디스패치

function greetLangDispatch(animal, language) {
    if(dev()) {
        if(!ajv.validate(greetLangArgsSchema, [animal, language])) {
            throw ("greetLang called with invalid arguments: " + 
                ajv.errorsText(ajv.errors));
        }
    }
    return [animal.type, language.type];
}

var greetLang = multi(greetLangDispatch);

 Dispatcher에서는 단일 값을 기준으로 로직을 구분할 수도 있지만 위와 같이 다중 필드를 기준으로 실행할 로직을 구분할 수도 있다.

 

 

동적 디스패치

function dysGreetDispatch(animal) {
    if(dev()) {
        if(!ajv.validate(animalSchema, animal)) {
            var errors = ajv.errorsText(ajv.errors);
            throw ("dysGreet called with invalid arguments: " + errors);
        }
    }
    var hasLongName = animal.name.length > 5;

    return [animal.type, hasLongName];
}

var dysGreet = multi(dysGreetDispatch);

 또한 정해진 값 뿐만이 아니라 동적으로 계산하여 실행할 로직을 구분할 수도 있다. 이렇게 다중 메서드를 활용하면 DOP에서도 OOP의 다형성의 이점을 얻을 수 있다.

 

(1) greet()의 구현을 수정하려 할 때 모든 유형의 동물을 다루는 코드를 수정할 필요가 없다.

(2) 유효한 타입이 아닐 경우 오류를 반환한다.

(3) 원래 코드를 수정하지 않고도 새로운 유형을 추가할 수 있다.

 

 

내 생각


 다형성의 이점이 뭐였는지 다시 한번 되새길 수 있는 13장이었다. 또 DOP 방식으로 개발을 하지 않는다고 하더라도 js나 python 작업을 할 때 사용해볼 수 있는 방법이지 않을까 싶었다. 다만, 생소한 개념이다 보니 회사에서 사용하려면 주변 팀원의 동의를 먼저 구해야 할 것 같다.