デザインパターン勉強会 第8回：Abstract Factoryパターン

#はじめに

本エントリーは某社内で実施するデザインパターン勉強会向けの資料となります。
本エントリーで書籍[「Java言語で学ぶデザインパターン入門」](http://www.hyuki.com/dp/)をベースに学習を進めますが、サンプルコードはC#に置き換えて解説します。  

第1回：[Iteratorパターン](http://qiita.com/Nuits/items/800f080d44b52e1cae18)
第2回：[Adapterパターン](http://qiita.com/Nanakusajp/items/747d1913b1e8bea8d340)
第3回：[Template Methodパターン](http://qiita.com/_kyam/items/9b5722e6a9635af6e18b)
第4回：[Factory Methodパターン](http://qiita.com/ayayo/items/3eece04bbc4b7504179a)
第5回：[Singletonパターン](http://qiita.com/Keenag/items/cae53d55cb953562fee4)
第6回：[Prototypeタイプ](http://qiita.com/aconit96/items/9235d30a07f508886c7e)
第7回：[Builderタイプ](http://qiita.com/Uesk/items/8f12be304671e9905795)


#Abstract Factoryパターンとは
Factoryメソッドはインターフェイスを提供して、具体的な実装はすべてインターフェイスの実装クラス側で行います。Abstract Factoryメソッドは関連する部品(クラス)も含めてインスタンスを生成してくれる手順のインターフェイスを提供します。
よって作りたい部品を渡せばfactoryインターフェイスを実装したfactoryメソッドで部品のインスタンス生成してくれます。

#サンプルクラス図
![AbstractFactory.png](https://qiita-image-store.s3.amazonaws.com/0/195655/0ebd2462-ad84-9404-949b-b56ebb444bcf.png)


#サンプルクラス一覧
| パッケージ| 名　前     | 解　説       |
|:---------|:-----------|:------------|
|Abstractfactory| IItem    | LinkとTrayを統一的に扱うためのインターフェイス |
|Abstractfactory| Link    | 抽象的な部品：HTMLのリンクを表す |
|Abstractfactory| Tray    | 抽象的な部品：LinkやTrayを集めた部品 |
|Abstractfactory| Page    | HTMLのページを生成する |
|Abstractfactory| Factory | 部品を作成する|
|listfactory| ListLink    | 具体的な部品：HTMLのリンクを表すクラス |
|listfactory| ListTray    | 具体的な部品：LinkやTrayを集めたクラス |
|listfactory| ListPage    | 具体的な部品：HTMLのページを表すクラス |
|listfactory| Listfactory | 具体的な工場を表すクラス(ListLink, ListTray, ListPageを作る)|
|-| Main | 動作テスト用のクラス|



#抽象的な部品：IItemクラス
LinkとTrayを統一的に扱うためのインターフェイス。

```Item.cs
namespace AbstractFactory
{
    public interface IItem
    {
        void SetCaption(string caption);
        string MakeHTML();

    }
}
```

#抽象的な部品：Linkクラス
URLを取り扱うクラス。
MakeHTML()の具体的な実装がないため、こちらは抽象クラス。

```Link.cs
namespace AbstractFactory
{
    public abstract class Link : Item
    {
        protected string url;
        public Link(string caption, string url) : base(caption)
        {
            this.url = url;
        }
    }
}
```

#抽象的な部品：Trayクラス
複数のLinkやItemを集めるクラス。
MakeHTML()の具体的な実装がないため、こちらもLink同様に抽象クラス。

```Tray.cs
namespace AbstractFactory
{
    public abstract class Tray : IItem
    {
        protected string caption;

        protected List<IItem> tray = new List<IItem>();
        public Tray(string caption) : base()
        {
            this.caption = caption;
        }

        public void Add(IItem item)
        {
            tray.Add(item);
        }

        public void SetCaption(string caption)
        {
            this.caption = caption;
        }

        public abstract string MakeHTML();
    }
}
```
#Pageクラス
継承先のPageクラスで生成したHTML形式の文字列をファイル出力する。
※ファイル出力のみの為、継承元のPageクラスに実装する。

```Page.cs
namespace AbstractFactory
{
    public abstract class Page
    {
        protected string title;
        protected string author;
        protected List<IItem> content = new List<IItem>();
        public Page(string title, string author)
        {
            this.title = title;
            this.author = author;
        }

        public void Add(IItem item)
        {
            this.content.Add(item);
        }

        public void Output(string HtmlFileName)
        {
            try
            {
                string filename = HtmlFileName + ".html";
                using (StreamWriter writer = new StreamWriter(filename, false, Encoding.UTF8))
                {
                    writer.Write(this.MakeHTML());
                }
                Console.WriteLine($"{filename}を作成しました。");
            }
            catch (IOException e)
            {
                Console.Error.WriteLine(e);
            }
        }

        public abstract string MakeHTML();
    }
}
```


#抽象的な工場：Factoryクラス
Item、Link、Trayクラスを生成するクラス。
ただしインターフェイスの定義の為、各クラスのインスタンス生成は
具体的な工場クラスで実装する。
また、生成する工場は呼び出し元から渡される引数をもとに生成する。

```Factory.cs
namespace AbstractFactory
{
    public abstract class Factory

    {
        public static Factory GetFactory(string AsmName ,string ClassName)
        {
            Factory factory = null;
            try
            {
                Assembly asm = Assembly.Load(AsmName);
                factory = (Factory)asm.CreateInstance(
                   ClassName,
                   false,
                   BindingFlags.CreateInstance,
                   null,
                   null,
                   null,
                   null
                );
            }

            catch (TypeLoadException)
            {
                Console.Error.WriteLine($"クラス{ClassName}が見つかりません。");
            }
            catch (Exception e)
            {
                Console.Error.WriteLine(e.StackTrace);
            }
            return factory;
        }

        public abstract Link CreateLink(string caption, string url);
        public abstract Tray CreateTray(string caption);
        public abstract Page CreatePage(string title, string author);
    }
}
```

#具体的な部品：ListLinkクラス
Linkクラスの実装部。
引数で指定した見出し(caption)とURLをもとにHTMLタグを生成

```ListLink.cs
namespace ListFactory
{
    using AbstractFactory;

    public class ListLink : Link
    {
        public ListLink(string caption, string url) : base(caption, url) { }

        public override string MakeHTML()
        {
            return $"  <li><a href=\"{url}\">{caption}</a></li>\n";
        }
    }
}
```

#具体的な部品：ListTrayクラス
Linkクラスの実装部。
MakeHTML()の実装がありここでLinkとTrayからHTMLを生成する。
※ListItemとListTrayのMakeHTML()が実装されているため、イテレータ(ForEach構文)にて
インターフェイス側のIItemを指定しており、それぞれのMakeHTML()メソッドが呼び出される。

```ListTray.cs
namespace ListFactory
{
    public class ListTray : Tray
    {
        public ListTray(string caption) : base(caption) { }

        public override string MakeHTML()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<li>\n");
            sb.Append($"{caption}\n");
            sb.Append("<ul>\n");

            foreach (IItem i in tray)
            {
                sb.Append(i.MakeHTML());
            }

            sb.Append("</ul>\n");
            sb.Append("</li>\n");
            return sb.ToString();
        }
    }
}
```

#具体的な部品：ListPageクラス
Pageクラスの実装部。
ListTray、ListLinkにHead、bodyを付与してHTML形式の文字列を生成する。
※ListTrayと同様にForEach構文でIItemの型を指定している為、変数がListTray、ListLinkのいずれの型であっても
問題なくMakeHTML()が呼び出される。


```ListPage.cs
namespace ListFactory
{
    public class ListPage : Page
    {
        public ListPage(string title, string author) : base(title, author) { }
        public override string MakeHTML()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append($"<html><head><title>{title}</title></head>\n");
            sb.Append("<body>\n");
            sb.Append($"<h1>{title}</h1>");
            sb.Append("<ul>\n");

            foreach (IItem i in content)
            {
                sb.Append(i.MakeHTML());
            }

            sb.Append("</ul>\n");
            sb.Append($"<hr><address>{author}</address>");
            sb.Append("</body></html>\n");
            return sb.ToString();
        }
    }
}

```

#具体的な工場：ListFactoryクラス
ListLink、ListTray、ListPageを生成する。

```ListFactory.cs
namespace ListFactory
{
    using AbstractFactory;

    // ConcreteFactory
    public class ListFactory : Factory
    {
        public override Link CreateLink(string caption, string url)
        {
            return new ListLink(caption, url);
        }

        public override Tray CreateTray(string caption)
        {
            return new ListTray(caption);
        }

        public override Page CreatePage(string title, string author)
        {
            return new ListPage(title, author);
        }
    }
}
```

#メイン処理：Mainクラス
作成したいHTMLを作る工場を指定してインスタンス生成する。
生成した工場クラスに作りたいリンクおよびページの見出し、URLを渡すことで
Tray、Link、Pageを生成する。

```Main.cs
namespace Main
{
    class Program
    {
        // Main処理
         static void Main(string[] args)
        {
            if (args.Length != 2)
            {
                //第1引数：アセンブリ名　第2引数：名前空間.クラス名
                Console.WriteLine("Example 1: C# AbstractFactory ListFactory ListFactory.ListFactory");
                Environment.Exit(0);
            }

            Factory AbstractFactory = Factory.GetFactory(args[0], args[1]);
            Link asahi = AbstractFactory.CreateLink("朝日新聞", "http://www.asashi.com/");
            Link yomiuri = AbstractFactory.CreateLink("読売新聞", "http://www.yomiuri.co.jp/");

            Link usYahoo = AbstractFactory.CreateLink("Yahoo!", "http://www.yahoo.com/");
            Link jpYahoo = AbstractFactory.CreateLink("Yahoo!Japan", "http://www.yahoo.co.jp/");
            Link excite = AbstractFactory.CreateLink("Excite", "http://www.excite.co.jp/");
            Link google = AbstractFactory.CreateLink("Google", "http://www.google.com/");

            Tray traynews = AbstractFactory.CreateTray("新聞");
            traynews.Add(asahi);
            traynews.Add(yomiuri);

            Tray trayyahoo = AbstractFactory.CreateTray("Yahoo!");
            trayyahoo.Add(usYahoo);
            trayyahoo.Add(jpYahoo);

            Tray traysearch = AbstractFactory.CreateTray("サーチエンジン");
            traysearch.Add(trayyahoo);
            traysearch.Add(excite);
            traysearch.Add(google);

            Page page = AbstractFactory.CreatePage("LinkPage", "AbstractFactory Sample");
            page.Add(traynews);
            page.Add(traysearch);
            page.Output(args[1]);

            // 実行が一瞬で終わって確認ため、キーの入力を待ちます
            Console.ReadLine();
        }
    }
}
```

#実行結果
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/195655/455074f0-502b-40b0-9756-b4682a326818.png)


#実装してみて
・今後、工場（この場合、違うHTMLにて出力）するケースを増やす場合でも、新しく工場の実装を増やせば良い。（main処理は修正不要）。
・Itemクラスをインターフェイスにすることでlistクラス、Itemクラスは確実にMakeHTML()メソッドを実装されることを担保される。
・pageクラス（この場合、実装クラスのListpageクラス）のMakeHTMLクラスにおけるforEach構文
でイテレータのカウンタをitemにして実装することが可能。