...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

クラス間依存関係の除去 第4回 - ファクトリクラスと生成されるクラスの分離


編集中です m(..)m


第3回では,シンプルなファクトリクラス AnimalFactory を追加することによって,利用側の main.d ファイルが具象クラス Dog/Cat クラスに直接依存することを避けました.
ところが,今度はファクトリクラス AnimalFactory が具象クラス Dog/Cat に依存しているので,具象クラスを追加する度に AnimalFactory の修正が必要になりました.


第3.5回では,ファクトリクラスから具象クラス Dog/Cat への依存を絶つ方法として,ファクトリメソッドパターンを適用してみました.
しかし,クラスの数が増えて関係が複雑になってしまい,また,具象クラスを追加するときに main.d ファイルの書き換えがまた必要になってしまいました.


そこで,今回は,第3回のコードを元に,ファクトリクラス AnimalFactory から Dog/Cat クラスへの依存関係を逆転させます.
これにより,具象クラスを追加するときに,既存のファイルを変更しなくてもいいようになります.


まずは,元のコードを示します.

import std.stdio;

// ++ animal.d
interface IAnimal
{
    void bark();
}
class AnimalTemplate(T) : IAnimal
{
    private T t;
    this()
    {
        t = new T;
    }
    void bark()
    {
        t.bark();
    }
}
// ++ dog.d
class Dog
{
    void bark()
    {
        writefln("wan!wan!");
    }
}
// ++ cat.d
class Cat
{
    void bark()
    {
        writefln("nyan!nyan!");
    }
}
// ++ animalfactory.d
//import animal, dog, cat;
class AnimalFactory
{
    static IAnimal create(uint code)
    {
        switch (code)
        {
        case 0: return new AnimalTemplate!(Dog);
        case 1: return new AnimalTemplate!(Cat);
        }
    }
}
// ++ main.d
//import animal, animalfactory;
void main()
{
    IAnimal animal = AnimalFactory.create(0);
    animal.bark();
    animal = AnimalFactory.create(1);
    animal.bark();
}


次に,ファクトリクラス AnimalFactory から Dog/Cat クラスへの依存関係を逆転させたコードを示します.

import std.stdio;
import std.string : toStr = toString; // 名前衝突回避のため

// ++ animal.d
interface IAnimal
{
    void bark();
}
class AnimalTemplate(T) : IAnimal
{
    private T t;
    this()
    {
        t = new T;
    }
    void bark()
    {
        t.bark();
    }
}
// ++ dog.d
class Dog
{
    void bark()
    {
        writefln("wan!wan!");
    }
}
// ++ cat.d
class Cat
{
    void bark()
    {
        writefln("nyan!nyan!");
    }
}
// ++ dogregister.d
//import animalfactory, animal, dog;
class DogRegister
{
    static this()
    {
        AnimalFactory.regist!(AnimalTemplate!(Dog))(0);
    }
}
// ++ catregister.d
//import animalfactory, animal, cat;
class CatRegister
{
    static this()
    {
        AnimalFactory.regist!(AnimalTemplate!(Cat))(1);
    }
}
// ++ animalfactory.d
//import animal;
class AnimalFactory
{
    private static IAnimal delegate()[uint] factoryMethods;
    static void regist(ConcreteType)(uint code)
    {
        if ((code in factoryMethods) != null)
        {
            throw new Exception("Code '" ~ toStr(code) ~ "' is already registered.");
        }
        factoryMethods[code] = delegate IAnimal(){ return new ConcreteType; };
    }
    static IAnimal create(uint code)
    {
        if((code in factoryMethods) == null)
        {
            throw new Exception("Code '" ~ toStr(code) ~ "' is not registered yet.");
        }
        return factoryMethods[code]();
    }
}
// ++ main.d
//import animal, animalfactory;
void main()
{
    IAnimal animal = AnimalFactory.create(0);
    animal.bark();
    animal = AnimalFactory.create(1);
    animal.bark();
}
// おまけのテストコード
unittest
{
    class Bad { void no_bark() {} }
    
    // Runtime error; Code '0' is already registered.
    AnimalFactory.regist!(AnimalTemplate!(Cat))(0);
    // Rutime error; Code '3' is not registered yet.
    auto temp = AnimalFactory.create(3);
    // Complie error; 'Bad' class do not have 'bark' method.
    AnimalFactory.regist!(AnimalTemplate!(Bad))(2);
}


まず,注目していただきたいのは,既存の多くのファイル animal.d/dog.d/cat.d/main.d を変更していないことです.
逆に,変更したファイルは,animalfactory.d ただ一つです.
また,dogregister.d/catregister.d の2つのファイルを追加してあります.


詳しい説明に続く...