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 작업을 할 때 사용해볼 수 있는 방법이지 않을까 싶었다. 다만, 생소한 개념이다 보니 회사에서 사용하려면 주변 팀원의 동의를 먼저 구해야 할 것 같다.
'도서 > 기술' 카테고리의 다른 글
[책] 데이터 지향 프로그래밍 - 15장 디버깅 (0) | 2025.02.09 |
---|---|
[책] 데이터 지향 프로그래밍 - 14장 고급 데이터 유효성 확인 (1) | 2025.02.09 |
[책] 데이터 지향 프로그래밍 - 12장 고급 데이터 유효성 확인 (0) | 2025.02.01 |
[책] 데이터 지향 프로그래밍 - 10, 11장 데이터 베이스 작업과 웹 서비스 (1) | 2025.01.25 |
[책] 데이터 지향 프로그래밍 - 9장 영속 자료구조 (2) | 2024.12.22 |