본문으로 건너뛰기

팩토리 패턴_JavaScript 디자인 패턴 9

무료2015-07-28#JS#Design_Pattern#JavaScript工厂模式#鸭子类型#API契约#抽象工厂模式

팩토리 패턴은, 그 이름대로, 객체를 생성하는 데 사용됩니다. 복잡한 객체 생성 프로세스를 캡슐화하고, 간단한 기능 엔트리 포인트를 제공합니다. 본문에서는 JavaScript 로 구현된 팩토리 패턴을 자세히 소개합니다

零.덕 타이핑 (API 계약)

시작하기 전에 재미있는 것을 살펴봅시다, 덕 타이핑 간단히 말해 "행동이 타입을 결정한다"입니다.

Java 등의 강제 컴파일 시 타입 체크를 가진 언어와는 달리, JavaScript 는 동적 타입이며, 덕 타이핑을 지원합니다. 예를 들어:

function getDinner(chief) {
    chief.cook('DINNER');
}

var littleBoy = {
    cook: function() {
        // ...
    }
}
var dog = {
    wang: {
        // ...
    }
}
var dinner1 = getDinner(littleBoy);
// var dinner2 = getDinner(dog);    // Uncaught TypeError: chief.cook is not a function

getDinner 메서드를 정의하고, 파라미터는 chief 이어야 하며, 의미적으로 타입 Chief 를 제약합니다. 이 타입의 정의는 cook 메서드가 있으면 모두 해당합니다. 위의 테스트 결과에서 알 수 있습니다: 타입 체크는 실행 시 (Runtime) 에 발생하며, 타입이 일치하지 않으면 Error 를 스로우합니다. "행동이 타입을 결정한다"没错吧?

一.팩토리 패턴

function LittleDog() {
    // ...
}
function LovelyDog() {
    // ...
}
function BeautifulDog() {
    // ...
}

function DogFactory() {}

DogFactory.prototype.type = LittleDog;  // 디폴트로 LittleDog 를 생성

DogFactory.prototype.getDog = function(spec) {
    spec = spec || {};

    if (spec.type === 'LovelyDog') {
        this.type = LovelyDog;
    }
    else if (spec.type === 'BeautifulDog') {
        this.type = BeautifulDog;
    }

    return new this.type(spec);
}

// test
var dogFactory = new DogFactory();
var dog1 = dogFactory.getDog(); // 디폴트의 littledog 를 생성
var dog2 = dogFactory.getDog({
    type: 'LovelyDog',
    attr: 'val' // 기타 초기화 데이터, spec 에서 구체적인 생성자에 전달
});

위의 구현은 JavaScript 판의 팩토리 패턴으로, 팩토리의 모든 속성을 프로토타입 객체에 배치하고, getDog 내부에서 프로토타입 객체상의 type 을 오버라이트합니다. 조금 싱글톤 패턴의 味道가 있습니다.

二.팩토리 패턴의 장단점

###1.장점

  1. 객체 생성 프로세스의 복잡도를 저감

팩토리는 객체와 현재의 환경의 연락을 차폐하고, 팩토리를 호출하여 객체를 생성할 때는 현재의 환경을 고려할 필요가 없습니다

  1. 유사한 소형 객체 또는 컴포넌트의 관리가 용이

객체/컴포넌트 간에 약간의 차이만 있는 경우, 팩토리 패턴을 사용하여 직접 객체를 생산하는 것은 상속보다 더 나은 선택입니다

  1. "덕 타입"의 객체를 생산하기 쉬움

예를 들어 덕 타입 Chief 를 정의한 경우, 팩토리 메서드를 사용하여 해당 타입의 객체를 생산할 수 있습니다 (팩토리 메서드 내에서 객체에 cook 메서드를 추가하기만 하면 됨), 타입을 정의하기 위해 타입을 정의할 필요가 없습니다

###2.단점

  1. 추가의 오버헤드가 존재

팩토리 패턴의 본질은 한 조의 관련 생성자의 이차 캡슐화이므로, 추가의 오버헤드를 가져옵니다 (층이 1 개 증가)

  1. 유닛 테스트에 불리

객체 생성의 프로세스는 팩토리 뒤에 숨겨져 있으며, 객체 생성 프로세스가 매우 복잡한 경우, 유닛 테스트에 문제를 가져올 수 있습니다

三.추상 팩토리 패턴

JavaScript 에서도 추상 팩토리 패턴을 구현할 수 있으며, 게다가 구현은 Java 보다도 심플합니다. 샘플 코드는 다음과 같습니다:

var factory = (function() {
    // 存储车辆类型
    var types = {};

    return {
        getVechicle: function(type, customizations) {
            var Vechicle = types[type];
            return (Vechicle) ? new Vechicle(customizations) : null;
        },

        registerVechicle: function(type, Vechicle) {
            var proto = Vechicle.prototype;

            // 只注册实现车辆契约的类
            if (proto.drive && proto.breakDown) {
                types[type] = Vechicle;
            }
        }
    }
})();

추상 팩토리는 registerXXX 인터페이스를 제공하여 생성 메서드를 통일 관리하고, 내부에서 "API 계약"으로 타입을 제약합니다. 생성 메서드를 등록한 후 getVechicle 을 호출하여 객체를 생성하기만 하면 됩니다. 예를 들어:

// 构造方法(简单起见,直接返回参数对象)
function Car(arg) {
    return arg;
}
Car.prototype.drive = function() {};
Car.prototype.breakDown = function() {};
function Truck(arg) {
    return arg;
}
Truck.prototype = Car.prototype;

// 用法
factory.registerVechicle('car', Car);
factory.registerVechicle('truck', Truck);

// 基于抽象车辆类型实例化一个新 car 对象
var car = factory.getVechicle('car', {
    color: 'lime green',
    state: 'like new'
});

// 同理,实例化一个新 truck 对象
var truck = factory.getVechicle('truck', {
    wheelSize: 'medium',
    color: 'neon yellow'
});

console.log(car);
console.log(truck);

팩토리 패턴의 첫 번째 예와 비교하면, 여기서는 생성자와 팩토리를 분리했을 뿐입니다 (if(type === 'xxx')factory.create 에서 꺼냄), 생성자를 유연하게 등록할 수 있습니다

고전적인 추상 팩토리 패턴의 의미와는 크게 다르며, 최대해도 팩토리 패턴의 일종의 강화 (최적화) 로 간주할 정도이지만, 그래도 상관없습니다. 결국 디자인 패턴을 사용하기 위해 사용하는 것이 아니며, 적당한 결합 해제 의 목적을 달성하면 좋기 때문입니다

추상 팩토리 패턴에 관한 더욱 많은 정보는, 참고 자료 부분에서 인용한 2 편의 블로그 기사를 참조

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성