13
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#で理解するデザインパターン入門:Strategy / Decorator / Adapter / Singleton

13
Posted at

開発をしていると、こんな瞬間がありませんか。

  • if/else が増えすぎて読みにくい
  • クラス同士の依存が強くてテストしづらい
  • ちょっと機能を追加したいだけなのに既存コードを触りたくない
  • 外部 API の仕様変更の影響範囲が広すぎる
  • 設定やログなど「アプリ全体で1つだけあればいいもの」が散らばっている

私自身、実務で何度もこうした悩みにぶつかってきました。
そしてそのたびに助けられたのが デザインパターン です。

この記事では、現場で特に「効いた」と感じたパターンを、
“どう使うのか” が一目で分かる C# 実装例つきで紹介します。


1. Strategy(ストラテジー)

if/else 地獄を抜け出すためのパターン

料金計算、認証方式、フィルタリングなど、
アルゴリズムを切り替えたい場面は実務で本当に多いです。


❌ Strategy を使わないとどうなるか

public decimal CalculatePrice(decimal price, string discountType)
{
    if (discountType == "None")
        return price;
    if (discountType == "Seasonal")
        return price * 0.9m;
    if (discountType == "Member")
        return price * 0.8m;

    throw new ArgumentException("Unknown discount type");
}
  • 条件分岐が増えるたびにメソッドが肥大化
  • テストしづらい
  • 新しい割引ロジック追加時に既存コードを触る必要がある

⭕ Strategy を使うとこうなる

アルゴリズムをクラスとして分離

public interface IDiscountStrategy
{
    decimal Apply(decimal price);
}

public class NoDiscount : IDiscountStrategy
{
    public decimal Apply(decimal price) => price;
}

public class SeasonalDiscount : IDiscountStrategy
{
    public decimal Apply(decimal price) => price * 0.9m;
}

public class MemberDiscount : IDiscountStrategy
{
    public decimal Apply(decimal price) => price * 0.8m;
}

実際の使い方

IDiscountStrategy strategy = new MemberDiscount();
var calculator = new PriceCalculator(strategy);

var result = calculator.Calculate(1000m);
Console.WriteLine(result); // 800

🎯 Strategy のメリット

  • 条件分岐が消えて読みやすい
  • 新しいロジック追加時に既存コードを触らなくていい
  • 各 Strategy を単体でテストできる
  • DI と相性が良い

2. Decorator(デコレーター)

「既存の処理にちょい足ししたい」を綺麗に実現

ログ追加、キャッシュ追加、認可チェックなど、
既存の処理に機能を後付けしたい場面は本当に多いです。


❌ よくある「ちょい足し地獄」

public class UserService
{
    public void Save(User user)
    {
        Console.WriteLine("Start log");
        // 保存処理
        Console.WriteLine("End log");
    }
}

⭕ Decorator を使うとこうなる

元の処理はシンプルなまま

public interface IUserService
{
    void Save(User user);
}

public class UserService : IUserService
{
    public void Save(User user)
    {
        Console.WriteLine("Saving user...");
    }
}

デコレーターで「ちょい足し」

public class LoggingUserService : IUserService
{
    private readonly IUserService _inner;

    public LoggingUserService(IUserService inner)
    {
        _inner = inner;
    }

    public void Save(User user)
    {
        Console.WriteLine("Start log");
        _inner.Save(user);
        Console.WriteLine("End log");
    }
}

実際に使ってみる

IUserService service = new UserService();
service = new LoggingUserService(service);

service.Save(new User("Taro"));

🎯 Decorator のメリット

  • 元のクラスを一切変更せずに機能追加できる
  • ログ・キャッシュ・認可などを組み合わせて使える
  • 継承より柔軟

3. Adapter(アダプター)

外部 API やレガシーコードとの“つなぎ込み”に必須

実務では「使いたいけどインターフェースが合わない」問題が頻発します。


❌ Adapter がないとどうなる?

public class SlackApi
{
    public void PostMessage(string text) { ... }
}

public class NotificationService
{
    public void Notify(string message)
    {
        var slack = new SlackApi();
        slack.PostMessage(message);
    }
}

⭕ Adapter を使うと柔軟になる

アプリ側のインターフェース

public interface INotification
{
    void Send(string message);
}

Slack 用のアダプター

public class SlackAdapter : INotification
{
    private readonly SlackApi _slack;

    public SlackAdapter(SlackApi slack)
    {
        _slack = slack;
    }

    public void Send(string message)
    {
        _slack.PostMessage(message);
    }
}

アプリ側からの使い方

var slackApi = new SlackApi();
INotification notifier = new SlackAdapter(slackApi);

notifier.Send("Hello from Adapter!");

🎯 Adapter のメリット

  • 外部 API の仕様変更に強い
  • テストしやすい(モック差し替え可能)
  • レガシーコードの置き換えがスムーズ

4. Singleton(シングルトン)

「アプリ全体で1つだけあればいいもの」を安全に共有する


❌ シングルトンを使わないとどうなる?

public class AppConfig
{
    public string ConnectionString { get; }

    public AppConfig()
    {
        ConnectionString = "Server=localhost;Database=Sample;";
    }
}

⭕ シングルトンを使うとこうなる

public sealed class AppConfig
{
    private static readonly Lazy<AppConfig> _instance =
        new Lazy<AppConfig>(() => new AppConfig());

    public static AppConfig Instance => _instance.Value;

    public string ConnectionString { get; }

    private AppConfig()
    {
        ConnectionString = "Server=localhost;Database=Sample;";
    }
}

インスタンスの取得方法

var config = AppConfig.Instance;
Console.WriteLine(config.ConnectionString);

🎯 Singleton のメリット

  • インスタンスが1つだけなので状態が安定
  • 設定やログなど「アプリ全体で共有したいもの」を安全に扱える
  • Lazy による遅延初期化で無駄な処理を避けられる

まとめ:パターンは“綺麗に書くための道具”ではなく“問題を解決するための道具”

今回紹介したパターンは、どれも実務で本当に役立つものばかりです。

  • Strategy:条件分岐をなくして拡張しやすく
  • Decorator:既存コードを汚さずに機能追加
  • Adapter:外部 API やレガシーとの橋渡し
  • Singleton:アプリ全体で共有したいものを安全に扱う

デザインパターンは「覚えるもの」ではなく、
困ったときに思い出す引き出しのような存在だと思っています。


13
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?