1. ビジネスロジックでのインターフェース:ボタンとアクション
これはインターフェースの最後のレクチャーだから、実例多めで、実際にどう使うかをしっかり掴んでほしい!
インターフェースは特に大規模アプリで超便利。だから、ここではちょっと難しめのケースも紹介するね。(たまにカリキュラムより先の内容もあるよ) 興味あればぜひ!なければ次のレベルへどうぞ :P
一番シンプルな例 — ボタンとクリック
自作のコンソールアプリを進化させてみよう。例えば、コンソールメニューにボタンやテキストボックス、ラジオボタンみたいなユーザー要素があると想像してみて。
一部の要素(ボタン)はクリックに反応してほしいけど、他の要素(例えば静的なタイトル)は反応しなくていい。インターフェースを使えば、こういうのもスマートに実装できるよ。
ステップ1: インターフェースを作る
// 「クリックできる」コンポーネント:
public interface IClickable
{
void Click();
}
ステップ2: いろんな要素タイプにインターフェースを適用
// 全メニュー要素のベースクラス
public class MenuItem
{
public string Title { get; set; }
public MenuItem(string title)
{
Title = title;
}
public virtual void Display()
{
Console.WriteLine(Title);
}
}
// ボタン:クリックできる
public class Button : MenuItem, IClickable
{
public Button(string title) : base(title) {}
public void Click()
{
Console.WriteLine($"[ボタン] {Title} が押されたよ!");
}
}
// ラベル:クリック不可
public class Label : MenuItem
{
public Label(string title) : base(title) {}
}
ステップ3: インターフェースのポリモーフィズムを使う
IClickable[] clickableItems = new IClickable[]
{
new Button("保存"),
new Button("終了")
// Labelはここに追加できない — IClickableじゃないから!
};
foreach (var item in clickableItems)
{
item.Click();
}
ね、すごくない?リストには「Clickできる」やつしか入らないから、間違ってクリックできないオブジェクトをクリックしようとしても実行時エラーにならないんだ。
2. インターフェースと「ストラテジ」パターン:アルゴリズムの切り替え
例えば、レポートをいろんな方法で保存できるアプリを作ってるとしよう。ファイル、データベース、「クラウド」、あるいはTelegramで上司に送るとか(理由は聞かないで、色々あるよね)。
ステップ1: 課題を整理
ReportGeneratorクラスが、保存方法の詳細を知らなくてもどんな保存方法でも使えるようにしたい。
ステップ2: ストラテジ用インターフェースを定義
public interface IDataSaver
{
void Save(string reportData);
}
ステップ3: いろんなバリエーションを実装
public class FileDataSaver : IDataSaver
{
public void Save(string reportData)
{
Console.WriteLine("[FileDataSaver] ファイルに保存中...\n" + reportData);
// ここにFile.WriteAllText(...)のコードが入るかも
}
}
public class DatabaseDataSaver : IDataSaver
{
public void Save(string reportData)
{
Console.WriteLine("[DatabaseDataSaver] データベースに保存中...\n" + reportData);
// ここにDB書き込みのコードが入るかも
}
}
ステップ4: インターフェースで保存戦略を差し替え
public class ReportGenerator
{
private readonly IDataSaver _dataSaver;
public ReportGenerator(IDataSaver dataSaver)
{
_dataSaver = dataSaver;
}
public void GenerateReport()
{
string report = "これは大事なレポート!";
Console.WriteLine("レポートを生成中...");
_dataSaver.Save(report);
}
}
柔軟性のデモ
// ReportGeneratorを書き換えずに保存方法を変えられる!
IDataSaver fileSaver = new FileDataSaver();
ReportGenerator fileReport = new ReportGenerator(fileSaver);
fileReport.GenerateReport();
IDataSaver dbSaver = new DatabaseDataSaver();
ReportGenerator dbReport = new ReportGenerator(dbSaver);
dbReport.GenerateReport();
実用的な意味: 明日から新しい超最新サービスにレポートを書き出したくなったら、新しいクラスでインターフェースを実装して、既存コードを一切変えずに差し替えればOK!
3. .NETのインターフェース:IDisposableとusing
.NETで一番よく使われるインターフェースの一つがIDisposable。ファイルやストリーム、ネットワーク接続など、管理外リソースを扱うクラスは全部これを実装してる。
IDisposableは何のため?
絶対に解放しなきゃいけないリソース(例えばファイルを閉じるとか)を扱うときは、IDisposableを実装してDisposeメソッドを書く。するとusing構文で使えて、ブロックを抜けるときに必ずDisposeが呼ばれるんだ。
例:ファイル操作のシミュレーション
public class FakeFile : IDisposable
{
public string FileName { get; }
public FakeFile(string fileName)
{
FileName = fileName;
Console.WriteLine($"ファイルを開いた: {fileName}");
}
public void Dispose()
{
Console.WriteLine($"ファイルを閉じた: {FileName}");
}
}
// メインプログラムで:
using (var file = new FakeFile("otchet.txt"))
{
Console.WriteLine("ファイルに書き込み中...");
// usingの後で自動的に「閉じる」
}
出力例:
ファイルを開いた: otchet.txt
ファイルに書き込み中...
ファイルを閉じた: otchet.txt
めっちゃ便利: ファイルを閉じ忘れる心配なし!インターフェースとC#の仕組みが守ってくれる。
4. コレクションとLINQでのインターフェース
リストや配列、辞書を使うとき、実はもうインターフェースを使ってるんだよね。意識してなくても。
List<int> list = new List<int> { 1, 2, 3 };
IEnumerable<int> enumerable = list; // 問題なし!
// こうやって要素をループできる:
foreach(var x in enumerable)
{
Console.WriteLine(x);
}
LINQのメソッドのほとんどはIEnumerable<T>インターフェース経由でコレクションを扱う。だから、コレクションの型に依存しないコードが書ける!
なぜ必要?
List<T>をT[]やHashSet<T>、自作コレクションに変えても、コードはそのまま動く!
5. テスト用インターフェース(Mockオブジェクト)
テストでは、外部依存を切り離すのが超大事。実DBや本物のファイルを触らずにテストしたい。インターフェースがあれば、モック(ダミー)オブジェクトで本物を使わずにテストできる!
public class FakeDataSaver : IDataSaver
{
public bool WasCalled { get; private set; } = false;
public void Save(string reportData)
{
WasCalled = true;
Console.WriteLine("モックオブジェクトでデータ保存したよ!");
}
}
// テストで
FakeDataSaver saver = new FakeDataSaver();
ReportGenerator generator = new ReportGenerator(saver);
generator.GenerateReport();
Console.WriteLine($"Saveは呼ばれた? {saver.WasCalled}");
結果:テストは現実世界に依存せず、必要なメソッドが呼ばれたかだけをチェックできる!
6. インターフェースの明示的実装で衝突を解決
たまに、同じメソッド名を持つ2つのインターフェースを1つのクラスで実装しなきゃいけないことがある。でも、そのメソッドのロジックは違うんだよね。
public interface IFlyable
{
void Move();
}
public interface ISwimmable
{
void Move();
}
public class Duck : IFlyable, ISwimmable
{
// 明示的実装
void IFlyable.Move()
{
Console.WriteLine("カモが飛んでる!");
}
void ISwimmable.Move()
{
Console.WriteLine("カモが泳いでる!");
}
}
Duck duck = new Duck();
// duck.Move(); // エラー:そんなメソッドはない!
((IFlyable)duck).Move(); // "カモが飛んでる!"
((ISwimmable)duck).Move(); // "カモが泳いでる!"
ちょっとクセ強いけど、仕様でこうしなきゃいけない時もあるんだよね!
7. イベントモデルでのインターフェース:INotifyPropertyChanged
.NETにはイベント対応用の標準インターフェースがある。例えば、データモデルの何かが変わったらUIに通知したいとき(WPFやMAUIなどのGUIで超よく使う)。
using System.ComponentModel;
public class Person : INotifyPropertyChanged
{
private string name;
public string Name
{
get => name;
set
{
if (name != value)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
}
ポイント: データバインディング対応フレームワークは、クラスがこのインターフェースを実装してることを期待してる。だから、プロパティが変わると自動でUIが更新される!
8. 「ファクトリー」パターンでのインターフェース
インターフェースはファクトリー(いろんな互換オブジェクトを作るクラス)を作るのにもピッタリ。
public interface ITransport
{
void Move();
}
public class Bicycle : ITransport
{
public void Move() => Console.WriteLine("自転車で走るよ!");
}
public class Car : ITransport
{
public void Move() => Console.WriteLine("車で走るよ!");
}
public class TransportFactory
{
public static ITransport Create(string type)
{
return type switch
{
"bike" => new Bicycle(),
"car" => new Car(),
_ => throw new ArgumentException("未知の乗り物タイプ")
};
}
}
ITransport transport = TransportFactory.Create("bike");
transport.Move(); // "自転車で走るよ!"
9. イベント用インターフェース:自作イベントの例
「イベントリスナー」インターフェースを定義してみよう:
public interface ILoginListener
{
void OnLogin(string userName);
}
// イベントを発火するクラス
public class LoginManager
{
private List<ILoginListener> listeners = new();
public void Subscribe(ILoginListener listener) => listeners.Add(listener);
public void Login(string userName)
{
Console.WriteLine($"ユーザー {userName} がログインした。");
foreach (var listener in listeners)
listener.OnLogin(userName);
}
}
// インターフェース実装クラス
public class WelcomeMessage : ILoginListener
{
public void OnLogin(string userName)
{
Console.WriteLine($"ようこそ、{userName}さん!");
}
}
LoginManager manager = new();
manager.Subscribe(new WelcomeMessage());
manager.Login("バシャ");
// ユーザー バシャ がログインした。
// ようこそ、バシャさん!
面接で: 「自作のイベントシステムをどう作る?」って聞かれたら、リスナー用インターフェースの話をすればOK!
10. プラグイン用インターフェース(アプリ拡張性)
大規模アプリはプラグイン対応が多い。インターフェースがあれば、アプリは中身を知らなくても新しいモジュールを「その場で」読み込める!
public interface IPlugin
{
string Name { get; }
void Run();
}
// アプリ本体:
public class PluginLoader
{
public void LoadAndRun(IEnumerable<IPlugin> plugins)
{
foreach (var plugin in plugins)
{
Console.WriteLine($"プラグイン起動中: {plugin.Name}");
plugin.Run();
}
}
}
プラグインはサードパーティが作ってもOK。インターフェースさえ守れば、アプリはどんどん拡張できる!
GO TO FULL VERSION