概要
@hyuki 先生著の『Javaで学ぶデザインパターン入門』(2004年、SB Creative)の1章ずつをベースに、サンプルコードをC#で置き換えながら勉強していく記事です。
※著者の @hyuki 先生には適切に書籍への参照を入れれば問題ない旨ご確認いただいています。
本題
Adapterパターン
第2回はAdapterパターンです。Adapterパターンは本の表現を借りると、「すでに提供されているものと必要なものの間のずれを埋める」ようなデザインパターンです。
Adapterパターンには①Class Adapterパターン(継承を使う)
と②Object Adapterパターン(委譲を使う)
の二種類があります。
サンプルコード
早速具体的な事例を見てみましょう。『Javaで学ぶデザインパターン入門』(2004年、SB Creative)に掲載されているコードをC#で(大体)書き換えます。
まずはClass Adapterパターンからです。
①Class Adapterパターン
// コンソールアプリケーションで実行を確認しました
using System;
namespace ClassAdapterPattern
{
// Client
class Program
{
static void Main(string[] args)
{
Print p = new PrintBanner("Hello");
p.PrintWeak();
// => (Hello)
p.PrintStrong();
// => *Hello*
// 実行が一瞬で終わって確認できないので、キーの入力を待ちます
Console.ReadLine();
}
}
// このクラスは既に提供されているものとします
// Adaptee
public class Banner
{
private string str;
public Banner(string str)
{
this.str = str;
}
public void ShowWithPattern()
{
Console.WriteLine($"({str})");
}
public void ShowWithAster()
{
Console.WriteLine($"*{str}*");
}
}
// Target
public interface Print
{
void PrintWeak();
void PrintStrong();
}
// Adapter
public class PrintBanner : Banner, Print
{
public PrintBanner(string str) : base(str) { }
public void PrintWeak()
{
this.ShowWithPattern();
}
public void PrintStrong()
{
this.ShowWithAster();
}
}
}
次にObject Adapterパターンです。
②Object Adapterパターン
// コンソールアプリケーションで実行を確認しました
using System;
namespace ObjectAdapterPattern
{
class Program
{
// Client
static void Main(string[] args)
{
Print p = new PrintBanner("Hello");
p.PrintWeak();
// => (Hello)
p.PrintStrong();
// => *Hello*
// 実行が一瞬で終わって確認できないので、キーの入力を待ちます
Console.ReadLine();
}
}
// このクラスは既に提供されているものとします
// Adaptee
public class Banner
{
private string str;
public Banner(string str)
{
this.str = str;
}
public void ShowWithPattern()
{
Console.WriteLine($"({str})");
}
public void ShowWithAster()
{
Console.WriteLine($"*{str}*");
}
}
// Target
public abstract class Print
{
public abstract void PrintWeak();
public abstract void PrintStrong();
}
// Adapter
public class PrintBanner : Print
{
private Banner banner;
public PrintBanner(string str)
{
this.banner = new Banner(str);
}
public override void PrintWeak()
{
this.banner.ShowWithPattern();
}
public override void PrintStrong()
{
this.banner.ShowWithAster();
}
}
}
効能
- 既存のよくテストされて枯れたモジュールを使いまわせる。
- 必要とするメソッド群を素早く準備できる。
- バグが出た場合の修正箇所をadapter部分に絞り込める。
使用上の注意
- clientから使用するときは必要な分だけ定義したIFを使う
- サンプルだと
Print p = new PrintBanner("Hello");
であって、PrintBanner p = new PrintBanner("Hello");
ではない
①Class Adapterパターンと②Object Adapterパターンのどちらを使うのか?について『C#実践開発手法』に記述がありました。
Class Adapterパターンは、それほど使用されないAdapterパターンです。これは主に、開発者が継承よりも合成を優:先するように教えられているためです。継承はホワイトボックスの再利用であり、サブクラスをそのインターフェイスだけでなくクラスの実装にも依存させます。合成はブラックボックスの再利用であり、依存関係はインタイフェースに限定されるため、クライアントに悪影響をおよぼさずに実装を変更することが可能です。
まとめると、
- 意図しない挙動を避けるため、モジュールの結合度を下げるためにClass AdapterパターンよりもObject Adapterパターンを使用した方がよいケースが多そう
ということでしょうか。
関連しているパターン
感想や疑問
- 既存のメソッドを容易に変更し難い状況(internalなAPIでいろんなプラットフォームのクライアントから使ってて影響範囲がよく見えない、publicなAPI)だと確かにほしくなるのかなぁ。
- どっちがましかは状況に依るのかなぁ。
- APIのオプションでメソッド内がififしてしまう
- APIをいじらないようにする前提でclientが増えてAdapterが乱立する
C#で学ぶデザインパターン入門
①Iterator
②Adapter
③Template Method
④Factory Method
⑤Singleton
⑥Prototype
⑦Builder
⑧AbstractFactory
⑨Bridge
⑩Strategy
⑪Composite Pattern
⑫Decorator Pattern