メインコンテンツへ移動

ファクトリパターン_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 篇のブログ記事を参照

参考資料

コメント

コメントはまだありません

コメントを書く