8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

株式会社ゆめみAdvent Calendar 2024

Day 12

9歳娘「パパ、具体じゃなくインターフェースに依存して?」

Last updated at Posted at 2024-12-12

2112年、ある日の我が家

ワイ「ぐぅっ!肝臓がァッ!」

娘「パパ、大丈夫!?」

ワイ「さ、酒の飲み過ぎで肝臓がァッ!」

娘「大変!」
娘「パパの肝臓って人工臓器だよね!?」
娘「新しい人工肝臓に交換したら!?」

ワイ「う〜ん・・・それがな?」
ワイ「ワイの肝臓は、株式会社ISONっていうメーカーはんの製品なんやけど」
ワイ「ISON社の人工臓器って、独自規格なんよ」
ワイ「せやから、他のメーカーの人工臓器とは接続端子の形状が違ってて」
ワイ「付け替えることができひんのや・・・」

娘「え・・・、そうなの?」
娘「最近では、どこのメーカーの人工臓器も」
娘「DS-1規格っていう世界的な規格に則って作られているから」
娘「付け替えできるって聞いたことがあるけど・・・」

ワイ「ワイの人工肝臓は、古いやつやから独自規格なんや・・・」

娘「じゃあ、早くISON社の人工肝臓を買ってこないと!」

ワイ「ISON社は、3年前に倒産したわ・・・」
ワイ「倒産したから、父さんの肝臓はもうダメや・・・」

娘「ああっ、早めに肉体をリファクタして」
娘「世界的なDS-1規格に対応した人工肝臓に換えておけば良かったのに・・・」

ワイ「せやな・・・具体的なメーカーの肝臓に依存するんじゃなくて」
ワイ「肝臓として働くもの、という規格
ワイ「つまりインターフェースに依存すべきだった、ってことやな・・・」

娘「そうだね・・・」
娘「そうしておけば、メーカーが倒産しても大丈夫だった」
娘「だって、他のメーカーの人工臓器に付け替えられるもの・・・」

ワイ「なるほどなぁ・・・」
ワイ「疎結合っていうやつやな・・・」

目が覚め、現代

ワイ「ハッ!」
ワイ「夢か・・・」
ワイ「でも何か、大切なことに気づかされたような・・・」

娘「パパ、やっと起きたの?」
娘「もうお仕事の時間だよ!」

ワイ「おお、ほんまや!」

リモートワーク開始

ワイ「さて今日は、メール通知機能を開発していくでぇ」
ワイ「ユーザーさんからの注文が入ったら、お店にメールでお知らせするんや」

TypeScript
// ユーザーからの注文を処理するクラス
class OrderController {
    private emailClient: EmailClient;

    constructor() {
        // メール送信ライブラリに直接依存している
        this.emailClient = new EmailClient();
    }

    /**
     * 途中省略
     */

    // 通知メソッド
    sendNotification(userId: number, order: Order): void {
        // メール通知を送信
        this.emailClient.sendEmail({
          to: "shop@example.com",
          subject: `注文が入りました`,
          body: /* 省略 */,
        });
    }
}

ワイ「ん・・・?」
ワイ「これって・・・」
ワイ「なんか、夢で見たワイの肝臓に似てるな・・・」
ワイ「メールという具体的な通知方法に依存してしまってるやん」
ワイ「もし、クライアントはんから──」

「やっぱり、メールではなくLINEEで通知してほしい」

ワイ「なんて言われてしまった場合に」
ワイ「このEmailClientを使っている箇所を、全て書き換えなアカン・・・」
ワイ「その場合、コードの修正量が多くなりそうやなぁ」

ワイ「ほな、夢で学んだように」
ワイ「インターフェースに依存するように書き直してみよか」
ワイ「まずは、通知の規格を定義したインターフェースを作るでぇ」
ワイ「通知をする時に必要なのは、ユーザー情報と、タイトルと、通知内容やから──」

TypeScript
// 通知の規格を定義するインターフェース
interface NotificationInterface {
    send(user: User, title: string, message: string): void;
}

ワイ「↑こうやな!」
ワイ「これが、人工臓器でいうDS-1規格みたいなもんやな」
ワイ「通知機能というものは、この規格に従わなアカンで〜!っていう感じや」

娘「へぇ〜」
娘「じゃあ、メール通知機能は、そのNotificationInterfaceに則って作るの?」

ワイ「せやで!」
ワイ「こんな感じや」

TypeScript
// メール通知
class EmailNotification implements NotificationInterface {
    private emailClient: EmailClient;

    constructor() {
        // メールクライアントを内部で保持
        this.emailClient = new EmailClient();
    }

    // メールクライアントのsendEmail()を、
    // send()メソッドでラップする
    send(user: User, title: string, message: string): void {
        this.emailClient.sendEmail({
            to: user.email,
            subject: title,
            body: message
        });
    }
}

娘「そっか」
娘「さっき決めたインターフェースに従って」
娘「メール送信ライブラリをラップしてやるんだね!」

ワイ「その通りや」
ワイ「そして、さっきのOrderControllerも、このインターフェースを使うように書き直すんや」

TypeScript
class OrderController {
    private notification: NotificationInterface;

    // コンストラクタで NotificationInterface を受け取る
    constructor(notification: NotificationInterface) {
        this.notification = notification;
    }

    sendNotification(userId: number, order: Order): void {
        // インターフェースで定義されている send() を呼ぶ
        this.notification.send(user, title, message);
    }
}

娘「へぇ〜!」
娘「ここでは具体的な通知方法は分からないけど」
娘「とにかく、通知の規格に従ってメッセージを送信するんだね!」

ワイ「せやで」
ワイ「そんで、OrderControllernew()するときに」
ワイ「EmailNotificationのインスタンスを渡してやるんや」

TypeScript
const orderController = new OrderController(new EmailNotification());

娘「なるほど〜」
娘「クラスをnew()する時に、具体的な通知方法を指定してやるんだね」
娘「依存性の注入ってやつだね」

ワイ「おお、よう知っとるな」

娘「てへへ」
娘「これなら確かに、具体に依存していない気がするね」
娘「でも、実際に他の通知方法に切り替えたくなった場合にはどうすればいいの?」
娘「たとえば、LINEE通知に切り替えたくなったら?」

ワイ「おう、その場合はやな?」
ワイ「今度は、LINEE通知のライブラリを、NotificationInterfaceに合わせてラップしてあげるんや」

TypeScript
// LINEE通知
class LineeNotification implements NotificationInterface {
    private lineeSDK: LineeSDK;

    constructor() {
        // LINEEのSDKを内部で保持
        this.lineeSDK = new LineeSDK();
    }

    // LINEE SDKが持つsendMessage()を、
    // send()メソッドでラップする
    send(user: User, title: string, message: string): void {
        this.lineeSDK.sendMessage(user.lineeId, title, message);
    }
}

ワイ「↑こんな感じや!」

娘「そっか」
娘「人工臓器で例えると」
娘「色々なメーカーが、DS-1規格に合わせて製品を作るみたいな感じだね」

ワイ「その通りやでぇ」
ワイ「そして、OrderControllernew()するときに」
ワイ「LineeNotificationのインスタンスを渡すように修正するんや」

TypeScript
const orderController = new OrderController(new LineeNotification());

ワイ「これで、メール通知もLINEE通知も」
ワイ「サクッと切り替えられるようになったで」

娘「へぇ〜」
娘「だいぶ差し替えやすくなったね」
娘「インターフェースが同じだから!」

ワイ「せやせや」
ワイ「いわゆる疎結合ってやつや」
ワイ「メール送信ライブラリと密結合になってると」
ワイ「ライブラリを剥がしたい時に大変やからな」

娘「なるほどね〜」

まとめ

具体的な実装に依存すると、後からの変更が難しくなる

  • 人工臓器の例
    • ISON社の独自規格だと、他のメーカーの製品に付け替えるのが大変
  • コードの例
    • EmailClientに直接依存すると、他の通知方法に変更する場合のコストが高い

インターフェースに依存することで、交換が容易になる

  • 人工臓器の例
    • DS-1規格に則っていれば、どのメーカーの製品でも使える
  • コードの例:
    • NotificationInterfaceを実装していれば、どの通知方法でも使える

娘「↑こういうことだね!」

ワイ「そうや!」

娘「通知方法を変更しやすいのもあるけど」
娘「テストコードも書きやすくなりそうだね!」

ワイ「へ?そうなん?」

娘「うん!」
娘「テストの時には、実際の通知方法を使わないで」
娘「通知の規格に従ったモッククラスを渡せばいいんだよ!」

ワイ「おお〜、そうやな!」
ワイ「ワイはテストコードなんて書かへんから、関係ないけどな!」

よめ太郎「いや書かんかい」

〜おしまい〜

追記

※普通に肝臓は大事にしましょう。休肝日を作りましょう。

8
1
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
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?