コンテンツにスキップ

デザインパターンとは?C#・.NETでよく使うものを実例で

2026-10-27 更新約8分で読めます

最終確認: 2026-10-27

30秒で復習

  • デザインパターン=よくある設計の悩みに対する、名前の付いた「定番の解き方」。 有名な23個(GoFのパターン)は、生成・構造・振る舞いの3つに分けられる。
  • 覚える価値があるのは、名前で会話できるから。「ここは Strategy にしよう」で設計の意図が伝わる。
  • .NET の中にはパターンがたくさん使われている:StringBuilder(Builder)、IHttpClientFactory(Factory)、Stream の重ね掛け(Decorator)、ASP.NET Core のミドルウェア(Chain of Responsibility)、BackgroundService(Template Method)、event(Observer)、yield return(Iterator)。
  • C# では、多くのパターンを インターフェース+DI で書く。特に Singleton は自分で作らず、DI の AddSingleton に任せる。
  • 使いすぎ注意:パターンは道具。困っていないのに当てはめると、クラスが増えて読みにくくなる。
  • 覚え方:「作り方(生成)・組み合わせ方(構造)・やり取りの仕方(振る舞い)」

質問:デザインパターンとは?

答え: プログラムを作っていると、「作り方を切り替えたい」「古い部品を今の形に合わせたい」「処理の順番を差し込みたい」といった同じような悩みが何度も出てくる。その悩みと定番の解き方に名前を付けて整理したもの。1994年の書籍で紹介された23個(Gang of Four、GoF)が有名。

生成 作り方を整理する Factory Method IHttpClientFactory Builder StringBuilder Singleton DIの AddSingleton 構造 組み合わせ方を整理する Adapter 古いAPIを今の形に Decorator Stream の重ね掛け Facade 窓口を1つに Proxy Lazy<T> 振る舞い やり取りの仕方を整理する Strategy IComparer<T> Observer C# の event Template Method BackgroundService Chain of Resp. ミドルウェア Iterator yield return
分類 何の悩み? C#・.NETでよく使うもの
生成 オブジェクトの作り方を切り替えたい・隠したい Factory Method、Builder、Singleton
構造 クラスを組み合わせて、形を合わせたり機能を足したりしたい Adapter、Decorator、Facade、Proxy
振る舞い オブジェクト同士のやり取りや処理の流れを整理したい Strategy、Observer、Template Method、Chain of Responsibility、Command、Iterator

生成のパターン:作り方を整理する

Factory Method(ファクトリ):作り方を1か所に閉じ込める

悩み: new があちこちに散らばると、作るクラスを変えたいときに全部直すことになる。

public interface INotifier { Task SendAsync(string message); }

public class NotifierFactory
{
    public INotifier Create(string channel) => channel switch
    {
        "mail"  => new MailNotifier(),
        "teams" => new TeamsNotifier(),
        _ => throw new ArgumentException($"未対応の通知先: {channel}")
    };
}
  • .NETの実例: IHttpClientFactory(HttpClient の作り方と使い回しを任せる)、ILoggerFactory。

Builder(ビルダー):少しずつ組み立てる

悩み: 設定項目が多いオブジェクトを、コンストラクターの長い引数で作ると読みにくい。

var sb = new StringBuilder();
sb.Append("こんにちは、").Append(name).AppendLine("さん");

var builder = WebApplication.CreateBuilder(args);   // 設定を足していって…
builder.Services.AddControllers();
var app = builder.Build();                          // 最後に組み立てる
  • .NETの実例: StringBuilder、WebApplication.CreateBuilder、UriBuilder。

Singleton(シングルトン):1つだけにする

悩み: 設定やキャッシュなど、アプリ全体で1つだけにしたいものがある。

  • 昔は static なインスタンスを自分で作っていたが、テストで差し替えにくい。
  • 今は DI の AddSingleton に任せるのが定番。「1つだけ」の管理は DI コンテナがやってくれる。
builder.Services.AddSingleton<IClock, SystemClock>();   // アプリ全体で1つ

構造のパターン:組み合わせ方を整理する

Adapter(アダプター):形の違う部品を合わせる

悩み: 古いライブラリや外部のAPIが、今のコードが期待する形(インターフェース)と違う。

public interface IOrderReader { Order? Find(string orderNo); }

// 古いDBアクセス部品を、今のインターフェースに合わせる
public class LegacyOrderAdapter(LegacyDbClient legacy) : IOrderReader
{
    public Order? Find(string orderNo)
    {
        var row = legacy.SelectOrder(orderNo.PadLeft(10, '0'));   // 古い形式に変換
        return row is null ? null : new Order(row.ORD_NO.Trim(), row.SHK_KBN == "3");
    }
}

Decorator(デコレーター):包んで機能を足す

悩み: 元のクラスを書き換えずに、ログ・キャッシュ・リトライなどの機能を足したい。

// 圧縮しながらファイルに書く:FileStream を GZipStream で包む
using var file = File.Create("log.gz");
using var gzip = new GZipStream(file, CompressionLevel.Optimal);
// 同じインターフェースで包んで、ログを足す
public class LoggingNotifier(INotifier inner, ILogger<LoggingNotifier> log) : INotifier
{
    public async Task SendAsync(string message)
    {
        log.LogInformation("送信します: {Message}", message);
        await inner.SendAsync(message);
    }
}
  • .NETの実例: Stream の重ね掛け(GZipStream・BufferedStream・CryptoStream)、HttpClient の DelegatingHandler。

Facade(ファサード):窓口を1つにまとめる

悩み: 「注文する」だけなのに、在庫・決済・配送・メールのクラスを順番に呼ぶ必要がある。

  • 呼び出す側には OrderService.PlaceOrder() だけを見せて、中で各クラスを呼ぶ。窓口係のイメージ。

Proxy(プロキシ):代理人を立てる

悩み: 重い処理を必要になるまで遅らせたい、呼び出しの前にチェックを挟みたい。

  • .NETの実例: Lazy<T>(最初に使うときに作る)、EF Core の遅延読み込み。

振る舞いのパターン:やり取りの仕方を整理する

Strategy(ストラテジー):やり方を差し替える

悩み: 「計算方法」「並べ方」「送料の決め方」などを、条件で切り替えたい。if / switch が増えていく。

public interface IShippingFee { decimal Calc(Order order); }
public class NormalFee  : IShippingFee { public decimal Calc(Order o) => 800m; }
public class MemberFee  : IShippingFee { public decimal Calc(Order o) => o.Total >= 5000m ? 0m : 500m; }

public class Checkout(IShippingFee fee)        // どの計算方法かは外から渡す
{
    public decimal Total(Order o) => o.Total + fee.Calc(o);
}
  • .NETの実例: IComparer<T>(並べ方を渡す)、List<T>.Sort(comparer)。
  • DI と組み合わせると、設定や条件で計算方法を切り替えられる(.NET 8 からの キー付きサービスも便利)。

Observer(オブザーバー):起きたことを知らせる

悩み: 「注文が確定したら、メールも在庫も更新したい」。注文クラスが全部を知っていると、関係がこんがらがる。

public class OrderService
{
    public event EventHandler<Order>? Placed;            // 知らせたい相手は登録してもらう
    public void Place(Order o) { /* … */ Placed?.Invoke(this, o); }
}
  • .NETの実例: C# の event、IObservable<T> / IObserver<T>。

Template Method(テンプレートメソッド):流れは親、中身は子

悩み: 処理の大きな流れは同じで、一部だけ違う。

public class NightlyJob : BackgroundService          // 開始・停止の流れは親クラスが持つ
{
    protected override async Task ExecuteAsync(CancellationToken stop)   // 中身だけ書く
    {
        while (!stop.IsCancellationRequested)
        {
            await DoWorkAsync();
            await Task.Delay(TimeSpan.FromHours(1), stop);
        }
    }
}
  • .NETの実例: BackgroundService(ExecuteAsync だけを書く)、Stream を継承したクラス。

Chain of Responsibility(責任の連鎖):順番に回す

悩み: 「ログ → 認証 → 例外処理 → 本処理」のように、処理を順番に通したい。途中で止めることもある。

利用者 ログ 時間を測る 1 認証 だれ? 2 認可 見てよい? 3 エンドポイント 本処理 リクエスト → ← レスポンス(逆の順に戻る) 途中で止めることもある(例:認可できなければ 403 を返して、本処理まで行かない)
app.Use(async (context, next) =>
{
    var start = Stopwatch.GetTimestamp();
    await next(context);                          // 次の担当へ回す(回さなければ、ここで止まる)
    var ms = Stopwatch.GetElapsedTime(start).TotalMilliseconds;
    app.Logger.LogInformation("{Path} {Ms}ms", context.Request.Path, ms);
});
app.UseAuthentication();
app.UseAuthorization();
  • .NETの実例: ASP.NET Core のミドルウェア、HttpClient の DelegatingHandler。Kestrelが受けたリクエストは、このミドルウェアの列を順番に通る。

Command(コマンド):操作を「物」にする

悩み: 操作を、あとで実行したい・取り消したい・記録したい。

  • .NETの実例: WPF の ICommand(ボタンと処理をつなぐ)、キューに積むジョブ。

Iterator(イテレーター):中身を1つずつ取り出す

IEnumerable<string> ReadLines(string path)
{
    foreach (var line in File.ReadLines(path))
        if (!string.IsNullOrWhiteSpace(line)) yield return line;   // 1行ずつ返す
}
  • .NETの実例: IEnumerable<T>・foreach・yield return、LINQ。

質問:.NETでよく出てくる、GoF以外のパターンは?

パターン ひとことで .NETでの例
依存性の注入(DI) 使う部品を外から渡す builder.Services.AddScoped<…>()(→ DIとは)
Options 設定を型付きのクラスで受け取る IOptions<T>、appsettings.json
Repository データの読み書きを1か所に集める EF Core の DbContext の上に置くことが多い
Mediator クラス同士が直接呼ばず、仲介役を通す MediatR などのライブラリ(ライセンス条件は要確認)

質問:使うときの注意は?

  • 困ってから使う:「将来切り替えるかも」だけでインターフェースや Factory を増やさない。2つ目の実装が必要になったときに入れても遅くない。
  • 名前を合わせる:〜Factory・〜Adapter・〜Decorator のように、パターン名をクラス名に入れると、読む人に意図が伝わる。
  • C# の機能で済むなら、それを使う:Observer は event、Iterator は yield return、Strategy は小さければ Func<T>(ラムダ)でも十分。
  • Singleton は自作しない:テストで差し替えにくくなる。DI の AddSingleton を使う。

関連メモ

確認したいこと

  • 自分のコードで if / switch が増え続けている場所はどこか(Strategy の候補)
  • 古いライブラリや外部APIを直接呼んでいる場所はどこか(Adapter の候補)
  • ログ・リトライ・キャッシュを、いろいろなクラスに書き散らしていないか(Decorator・ミドルウェアの候補)

参考資料