Edited at

C#で学ぶデザインパターン入門 ⑥Prototype

More than 1 year has passed since last update.


概要

@hyuki 先生著の『Javaで学ぶデザインパターン入門』(2004年、SB Creative)の1章ずつをベースに、サンプルコードをC#で置き換えながら勉強していく記事です。

※著者の @hyuki 先生には適切に書籍への参照を入れれば問題ない旨ご確認いただいています。


本題

Prototypeパターン

第6回はPrototypeパターンです。Prototypeパターンは「クラスからインスタンスを生成するのでなく、インスタンスから別のインスタンスを作り出す」デザインパターンです。


サンプルコード

早速具体的な事例を見てみましょう。『Javaで学ぶデザインパターン入門』(2004年、SB Creative)に掲載されているコードをC#で(大体)書き換えます。


// コンソールアプリケーションで実行を確認しました
using System;
using System.Collections.Generic;
using System.Text;

namespace PrototypePattern
{
using Framework;
class Program
{
static void Main(string[] args)
{
Manager manager = new Manager();
UnderlinePen upen = new UnderlinePen('~');
MessageBox mbox = new MessageBox('*');
MessageBox sbox = new MessageBox('/');
manager.Register("strong message", upen);
manager.Register("warning box", mbox);
manager.Register("slash box", sbox);

Product p1 = manager.Create("strong message");
p1.Use("Hello, world");
Product p2 = manager.Create("warning box");
p2.Use("Hello, world");
Product p3 = manager.Create("slash box");
p3.Use("Hello, world");
// =>
// "Hello, world"
// ~~~~~~~~~~~~
// ****************
// *Hello, world *
// ****************
// ////////////////
// / Hello, world /
// ////////////////

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

// ConcretePrototype
public class MessageBox : Product
{
private char decochar;
public MessageBox(char decochar)
{
this.decochar = decochar;
}
public void Use(string s)
{
Encoding sjisEnc = Encoding.GetEncoding("Shift_JIS");
int length = sjisEnc.GetByteCount(s);
for (int i = 0; i < length + 4; i++)
{
Console.Write(decochar);
}
Console.WriteLine("");
Console.WriteLine($"{decochar} {s} {decochar}");
for (int i = 0; i < length + 4; i++)
{
Console.Write(decochar);
}
Console.WriteLine("");
}
public object Clone()
{
return this.MemberwiseClone();
}
public Product CreateClone()
{
return (Product)this.Clone();
}
}

// ConcretePrototype
public class UnderlinePen : Product
{
private char ulchar;
public UnderlinePen(char ulchar)
{
this.ulchar = ulchar;
}
public void Use(string s)
{
Encoding sjisEnc = Encoding.GetEncoding("Shift_JIS");
int length = sjisEnc.GetByteCount(s);
Console.WriteLine($"\"{s}\"");
Console.Write(" ");
for (int i = 0; i < length; i++)
{
Console.Write(ulchar);
}
Console.WriteLine("");
}
public object Clone()
{
return this.MemberwiseClone();
}
public Product CreateClone()
{
return (Product)this.Clone();
}
}
}

namespace Framework
{
// Prototype
public interface Product : ICloneable
{
void Use(string s);
Product CreateClone();
}

// Client
public class Manager
{
private Dictionary<string, Product> showcase = new Dictionary<string, Product>();
public void Register(string name, Product proto)
{
showcase.Add(name, proto);
}
public Product Create(string protoname)
{
Product p = showcase[protoname];
return p.CreateClone();
}
}
}


効能

次のような場合に有効とのことです。


  1. 種類が多すぎてクラスにまとめられない場合


    • サンプルだとshowcaseに登録したのは3パターンですが、もっと増えるような使い方をするとき、そのクラスを全部作ると管理大変...というお話です。




  2. クラスからインスタンス生成が難しい場合


    • とてもとても複雑なインスタンスと同じものを作るとき、といってもなかなか実感ないですがそういう局面があるそうです。



  3. フレームワークと生成するインスタンスを分けたい場合


    • クラス名でインスタンスを生成しないことで、フレームワーク(サンプルで言うFramework)をクラスに縛られないようにすることができます。




使用上の注意


  • このパターンというより、コピーするためのメソッドがどういう意味合いのコピーか(shallow copy or deep copy)を把握する必要があります。サンプルで使用しているMemberwiseClone()はshallow copyを行うもので、フィールドが参照型だと参照がコピーされても中身そのものはコピーされません。


関連しているパターン


  • Flyweightパターン

  • Mementoパターン

  • CompositeパターンおよびDecoratorパターン

  • Commandパターン


感想や疑問


  • newするのが面倒なクラス、実際どんなんやろう...

  • 調べてるとIClonableはレガシーで非推奨な雰囲気がありました。コピーを実現する実装はけっこういろいろありそうです。



  • サンプルのshowcaseの型はC#なのでDictionary<T>にしましたが、元はHashMapでした。




C#で学ぶデザインパターン入門

①Iterator

②Adapter

③Template Method

④Factory Method

⑤Singleton

⑥Prototype

⑦Builder

⑧AbstractFactory

⑨Bridge

⑩Strategy

⑪Composite Pattern

⑫Decorator Pattern