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

「なぜインターフェースを使わないといけないんですか?」という疑問に頑張って回答する

Last updated at Posted at 2024-05-03

はじめに

「オブジェクト指向のインターフェースは知っているが,重要性や具体的な使い所がわからない」という方に向け,私なりにインターフェースの重要性について説明します。

インターフェースの意義

インターフェースはコードの可読性を高め、将来のメソッド実装を保証することで、変更しやすいコードベースの作成に貢献します.

以下2点の具体的なメリットについて解説していきます.

  1. 条件分岐(if文, switch文)の削減による可読性の向上
  2. 将来のメソッド実装を保証

if文やswitch文の削減による可読性の向上

コードが増える場合,if文やswitch文の削減が重要になってきます.
ソフトウェアは,様々な場所で状態や設定に応じた処理の分岐が発生します.
しかし,コードは分岐が増えるほど,複雑で理解しづらくなります.

そこで,インターフェースを使えば,if文やswitch文を書かずに処理を分岐できます.
インターフェース型の変数に,異なるクラスのインスタンスを代入することで,異なる処理を呼び出すことができます.

例として,ポケモンの「わざ」を発動する処理について考えてみましょう.
今回実装する「わざ」は次の3つです.

  • ひのこ
    • タイプ: ほのおタイプ
    • 威力:40
    • 追加効果: 10%の確率でやけど状態にする
  • あわ
    • タイプ: みずタイプ
    • 威力:40
    • 追加効果: なし
  • すいとる
    • タイプ: くさタイプ
    • 威力:20
    • 追加効果: 与えたダメージ分HPを回復する

今回の主題は「追加効果の処理をどう分岐するか」です.

まずはSwitch文で追加効果について分岐して,「わざ」を取り扱うクラス(Move)クラスを実装してみます.
以下の折りたたみブロックに実装例を示します.

Switch文で実装した例
package main

import (
	"fmt"
	"math/rand"
	"time"
)

type Move struct {
	Name       string
	Type       string
	Power      int
	Additional string
}

func (m Move) execute() {
	fmt.Printf("%s (%s) with power %d is activated.\n", m.Name, m.Type, m.Power)

	switch m.Additional {
	case "burn":
         // 乱数生成
    	rand.Seed(time.Now().UnixNano())
		if rand.Intn(100) < 10 { // 10% の確率
			fmt.Println("The target is burned!")
		} else {
			fmt.Println("No additional effect.")
		}
	case "drain":
		healedAmount := m.Power // 単純化のため,攻撃力と回復量を一緒にしている
		fmt.Printf("The user recovers %d HP!\n", healedAmount)
	case "none":
		fmt.Println("No additional effect.")
	default:
		fmt.Println("Unknown additional effect.")
	}
}

func main() {
	moves := []Move{
		{"ひのこ", "ほのおタイプ", 40, "burn"},
		{"あわ", "みずタイプ", 40, "none"},
		{"すいとる", "くさタイプ", 20, "drain"},
	}

	for _, move := range moves {
		fmt.Println("Using move:", move.Name)
		move.execute()
	}
}

このコードは動作しますが,executeメソッドにswitch文の分岐があり,「ひのこ」の処理について知りたい人の目にも,「あわ」「すいとる」の処理も入ってきます.
「わざ」の数が増えるにつれて目に入る処理の数は増えていくでしょう.
こういった場合,インターフェースを使って可読性を向上できます.

では,追加効果インターフェース(EffectApplier)とそれを実装したBurnクラス,Drainクラス,Noneクラスを実装してみましょう.
以下の折りたたみブロックにインターフェースを使用した実装例を示します.

インターフェースで実装した例
package main

import (
	"fmt"
	"math/rand"
	"time"
)

// 追加効果インターフェース
type EffectApplier interface {
	Activate()
}

// やけど
type Burn struct{}

func (b Burn) Activate() {
	rand.Seed(time.Now().UnixNano())
	if rand.Intn(100) < 10 { // 10%でやけど状態にする
		fmt.Println("The target is burned!")
	} else {
		fmt.Println("No additional effect.")
	}
}

// HP吸収
type Drain struct {
	Amount int
}

func (d Drain) Activate() {
	fmt.Printf("The user recovers %d HP!\n", d.Amount)
}

// 何もしない
type None struct{}

func (n None) Activate() {
	fmt.Println("No additional effect.")
}

// 「わざ」クラス
type Move struct {
	Name       string
	Type       string
	Power      int
	Effect     EffectApplier
}

// 「わざ」の発動
func (m Move) execute() {
	fmt.Printf("%s (%s) with power %d is activated.\n", m.Name, m.Type, m.Power)
	m.Effect.Activate()
}

func main() {
	moves := []Move{
		{"ひのこ", "ほのおタイプ", 40, Burn{}},
		{"あわ", "みずタイプ", 40, None{}},
		{"すいとる", "くさタイプ", 20, Drain{Amount: 20}},
	}

	for _, move := range moves {
		fmt.Println("Using move:", move.Name)
		move.execute()
	}
}

インターフェースを使用することで,分岐が減って,executeメソッドの見通しがよくなりました.
記事の都合上,全てのクラス,インターフェースを一箇所に記述しているためコード量は増えています.
しかし,各クラスを別ファイルに分ければ,一つの「わざ」の処理を理解するために読むコード量は減ります.

インターフェースの使用はコードの単純化と可読性を向上させ,ソフトウェアの変更を容易にし,作業効率の向上とコスト削減に寄与します.

将来のメソッド実装を保証

インターフェースを使うことで,将来のメソッド実装を保証できます.

ポケモンの「わざ」に「10まんボルト」が追加された場合について考えてみましょう.
10まんボルトの仕様は以下のようなものです.

  • 10まんボルト
    • タイプ: でんきタイプ
    • 威力:90
    • 追加効果: 10%の確率で相手をまひ状態にする

実装担当者が追加効果の実装を忘れた場合に,インターフェースを使用した実装はエラーを発生させることができます.
インターフェースを使用した実装では,EffectApplierの実装(Activateメソッドの実装)を忘れるとコンパイルエラーが発生し,コンパイル時に実装ミスが検出されます.
一方,switch文を使用すると,default処理が実行されてエラーが表示されないため,実装ミスが検出されにくいです.

このように,インターフェースはバグを予防する効果があり,ビジネスのリスクを削減できます.

さらなる勉強におすすめな書籍

インターフェースの重要性についてより詳しくなるために,おすすめの書籍を最後に紹介します.
この記事もこちらの書籍の受け売りな部分があるので,この記事に興味を持っていただけた方なら,楽しく学べるでしょう.

良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方

私の記事よりもifやswitchで分岐する実装の悪い点について丁寧に説明しており,インターフェースの重要性がより詳しく記載されています.
さらに,ここで述べた以外にも変更が容易なコードベースに貢献するためのスキルにも触れられています.
設計の本は理解が難しいものが多い中で,初学者にもおすすめの一冊です.

amazonリンク

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