Day 46 のゴールと全体像
Day 46 のテーマは 「依存関係を考える」です。
キーワードはこの3つです。
- クラス同士の依存
- 疎結合
- 依存を減らす考え方
ここまでで、クラスを作り、継承し、インターフェースで役割を分けるところまで来ました。 Day 46 では、もう一歩設計寄りに踏み込んで、
「クラス同士がベタベタにくっつきすぎないようにする」
という、とても大事な視点を身につけていきます。
クラス同士の「依存」とは何か
まずはざっくりイメージから
「依存」という言葉は少し堅いですが、 プログラムの世界では、
「あるクラスが、別のクラスに強く頼りすぎている状態」
だと思ってください。
たとえば、
OrderServiceがSqlOrderRepositoryにベタッとくっついていて、 他のデータ保存方法に変えられないUserServiceがConsoleLoggerを直接newしていて、 ログの出し方を差し替えにくい
といった状態は、 「依存が強い(密結合)」と言えます。
これが進むと、
- ちょっと仕様を変えたいだけなのに、あちこちのクラスを直さないといけない
- テストがしづらい
- 拡張がしづらい
という「重たいコード」になっていきます。
密結合の例を見てみる
ログ出力を直接クラスに埋め込んでしまうパターン
まずは、あえて「よくない例」を見てみます。
using System;
// 密結合なサービスクラスの例
class UserService
{
public void CreateUser(string name)
{
// ユーザー作成処理(ここでは簡略化)
Console.WriteLine($"ユーザー「{name}」を作成しました。");
// ログ出力を直接 Console.WriteLine で書いている
Console.WriteLine($"[LOG] ユーザー作成: {name}");
}
}
C#このコードの問題点は、
- ログの出し方が
Console.WriteLineにべったり依存している - ログをファイルに出したくなったら、
UserServiceの中身を直接書き換えないといけない - ログを無効化したいときも、やはり
UserServiceを触る必要がある
というところです。
重要ポイント:
- クラスの中で具体的な実装(
Console.WriteLineなど)を直接書いてしまうと、 そのクラスは「その実装」に強く依存することになります。
疎結合とは何か
「お互いを意識しすぎない関係」にする
疎結合(そけつごう)という言葉は、
「クラス同士が、できるだけゆるくつながっている状態」
を指します。
- 具体的なクラス名にべったり依存しない
- インターフェースや抽象クラスを介してやりとりする
- 「何をしてほしいか」だけを意識し、「どうやるか」は相手に任せる
という状態を目指します。
これができると、
- 実装を差し替えやすい
- テストしやすい
- 拡張しやすい
という「軽やかなコード」になっていきます。
依存を減らすための第一歩:インターフェースを挟む
ログ出力をインターフェースで抽象化する
Day 44 で登場した ILogger を使って、 依存を減らす考え方を実践してみます。
// ログ出力のインターフェース(契約)
interface ILogger
{
void Log(string message);
}
C#実装クラス:ConsoleLogger
using System;
// コンソールにログを出す実装
class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[Console] {message}");
}
}
C#疎結合な UserService
// 疎結合なサービスクラスの例
class UserService
{
private readonly ILogger logger;
// コンストラクタで ILogger を受け取る
public UserService(ILogger logger)
{
this.logger = logger;
}
public void CreateUser(string name)
{
Console.WriteLine($"ユーザー「{name}」を作成しました。");
// 具体的なログの出し方は知らない
// 「ログを出してほしい」という依頼だけをする
logger.Log($"ユーザー作成: {name}");
}
}
C#ここでの変化はこうです。
UserServiceは、もはやConsole.WriteLineに依存していない- 依存しているのは「ILogger という契約」だけ
- ログの出し方は、
ILoggerを実装したクラスに任せている
重要ポイント:
- 具体クラスではなく、インターフェース(役割)に依存することで、 クラス同士の結びつきをゆるくできます。
実装の差し替えを試してみる
FileLogger を追加する
using System;
using System.IO;
// ファイルにログを出す実装
class FileLogger : ILogger
{
private readonly string filePath;
public FileLogger(string filePath)
{
this.filePath = filePath;
}
public void Log(string message)
{
var line = $"[{DateTime.Now}] {message}{Environment.NewLine}";
File.AppendAllText(filePath, line);
}
}
C#呼び出し側で「どの実装を使うか」を選ぶ
class Program
{
static void Main(string[] args)
{
// 1. コンソールにログを出す UserService
ILogger consoleLogger = new ConsoleLogger();
UserService consoleUserService = new UserService(consoleLogger);
consoleUserService.CreateUser("山田");
// 2. ファイルにログを出す UserService
ILogger fileLogger = new FileLogger("log.txt");
UserService fileUserService = new UserService(fileLogger);
fileUserService.CreateUser("佐藤");
Console.ReadLine();
}
}
C#ここで注目してほしいのは、
UserServiceのコードは一切変えていない- ログの出し方を変えたいときは、 「どの
ILoggerを渡すか」を変えるだけでよい
というところです。
これが、疎結合の力です。
依存を減らす考え方をステップで整理する
ステップ1:具体クラスへの直接依存を見つける
まずは、自分のコードの中で、
new ConsoleLogger()new SqlOrderRepository()new SomeConcreteClass()
のように、 「特定のクラス名にべったり依存している場所」を探します。
ステップ2:そのクラスが果たしている「役割」を言葉にする
次に、そのクラスが何をしているのかを、 一言で言い換えてみます。
- ログを出している →
ILogger - データを保存している →
IRepository - メールを送っている →
IMailer
など、 「役割の名前」を考えます。
ステップ3:役割をインターフェースとして切り出す
その役割を、インターフェースとして定義します。
interface IMailer
{
void Send(string to, string subject, string body);
}
C#ステップ4:クラスは「役割(インターフェース)」に依存するように書き換える
class NotificationService
{
private readonly IMailer mailer;
public NotificationService(IMailer mailer)
{
this.mailer = mailer;
}
public void Notify(string userEmail)
{
mailer.Send(userEmail, "お知らせ", "こんにちは!");
}
}
C#このように、
- 具体クラスではなく、インターフェースに依存する
- 実装の差し替えは「どのインスタンスを渡すか」で行う
という形にしていくと、 コード全体が疎結合になっていきます。
疎結合のメリットをもう少し深掘りする
1. テストしやすくなる
インターフェースに依存していると、 テスト用の「ダミー実装」を簡単に作れます。
// テスト用のダミーロガー
class DummyLogger : ILogger
{
public void Log(string message)
{
// 何もしない、またはテスト用の記録だけする
}
}
C#これを UserService に渡せば、
- ログ出力の副作用を気にせずテストできる
- 「ログがちゃんと呼ばれているか」だけを確認することもできる
という状態になります。
2. 実装の変更に強くなる
- ログをクラウドサービスに送るようにしたい
- データ保存先をファイルからデータベースに変えたい
といったときも、
- 新しい実装クラスを作る
- インターフェースを実装する
- 呼び出し側で渡すインスタンスを差し替える
だけで済みます。
3. セキュリティやパフォーマンスの改善もしやすい
セキュリティやパフォーマンスの観点から、
- ログのフォーマットを変えたい
- 保存方法を暗号化したい
- 通信方法を変えたい
といったときも、 「実装クラスを差し替える」だけで対応できるようになります。
依存を減らすための簡易テンプレート
最後に、 「依存を減らす」ための基本テンプレートをまとめておきます。
役割のインターフェース
// 役割:通知を送る
interface INotifier
{
void Notify(string message);
}
C#実装A:コンソール通知
using System;
class ConsoleNotifier : INotifier
{
public void Notify(string message)
{
Console.WriteLine($"[Console] {message}");
}
}
C#実装B:メール通知(イメージ)
class MailNotifier : INotifier
{
public void Notify(string message)
{
// 実際のメール送信処理は省略
Console.WriteLine($"[Mail] メールで通知: {message}");
}
}
C#依存を減らしたサービスクラス
class AlertService
{
private readonly INotifier notifier;
public AlertService(INotifier notifier)
{
this.notifier = notifier;
}
public void SendAlert(string text)
{
notifier.Notify($"アラート: {text}");
}
}
C#使う側
class Program
{
static void Main(string[] args)
{
// コンソール通知版
INotifier consoleNotifier = new ConsoleNotifier();
AlertService consoleAlert = new AlertService(consoleNotifier);
consoleAlert.SendAlert("コンソールに出すアラートです。");
// メール通知版
INotifier mailNotifier = new MailNotifier();
AlertService mailAlert = new AlertService(mailNotifier);
mailAlert.SendAlert("メールで送るアラートです。");
Console.ReadLine();
}
}
C#ここでも、
AlertServiceは「INotifier という役割」にだけ依存している- 実際にどう通知するかは、外から渡される実装クラスに任せている
という疎結合な構造になっています。
Day 46 のまとめ
Day 46 では、
- クラス同士の依存 → あるクラスが、別のクラスに強く頼りすぎている状態
- 疎結合 → クラス同士の結びつきをゆるくし、役割(インターフェース)に依存する状態
- 依存を減らす考え方 → 具体クラスではなく、インターフェースや抽象クラスに依存するように設計すること
を、ログ出力や通知の例を通して学びました。
依存関係を意識し始めると、
「このクラスは、何に依存しているんだろう?」 「この依存は、もっとゆるくできないかな?」
という視点が自然と生まれてきます。
それは、 コードを「動けばいい」から一歩進めて、
「長く育てていけるコード」にしていくための、とても大事な一歩
でもあります。
ぜひ、
- 自分のコードの中で「具体クラスにべったり依存している場所」を探してみる
- そこにインターフェースを挟んでみる
- 実装を差し替える小さな実験をしてみる
という練習を通して、 依存関係と疎結合を 「設計のキーワード」ではなく、 自分のコードを軽やかにするための実践的な道具として、 少しずつ体に馴染ませていってください。
