開発をしていると、こんな瞬間がありませんか。
- 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:アプリ全体で共有したいものを安全に扱う
デザインパターンは「覚えるもの」ではなく、
困ったときに思い出す引き出しのような存在だと思っています。