はじめに
FastAPIを学習している最中、APIキー認証を実装しようとしたところ、依存関係、依存性注入といった単語が飛び出してきて、事故にあった気分になりました。知ってはいたものの全く理解していなかったので、GPTと一緒に学びましたので、まとめてみます!
また個人的に依存関係の理解を妨げていたもの正体を発見したのでそちらについても触れていきます。
対象者
この記事は下記のような人を対象にしています。
- 依存関係、依存性注入何それといった人
内容は技術的なものというよりかは、日本語の意味的な部分や概念に関するものから始め、最後にコードで例を示します。
目次
結論~理解を妨げていたもの~
一般的な日本語としての依存関係と、ソフトウェア関連の依存関係との意味の間に若干の乖離がある。これこそが私の依存関係の理解を妨げているものの正体でした。
具体例
日本語において一般に○○関係といった単語の意味は大きく以下の二つに分けられます。(私が勝手に分けただけですが、、)
- 友人関係、家族関係~等。
関連、系と言い換えられるもの。 - 対立関係、協力関係~等。
相互に関係しあうもの。
一般的な単語としての依存関係は恐らく後者の意味で使われるのではないでしょうか。
例:彼と彼女は依存関係にあるなど
ソフトウェアなどの文脈では
Aという関数を実行するために、B(クラス、関数など)が必要であること,
すなわちAがBに依存している状態、この関係を依存関係と呼びます。
このようにソフトウェア開発の文脈では、依存側、被依存側(依存オブジェクト)が存在します。
このような理解が欠落していたため私は依存関係という単語に拒否反応を示してしまいました💦
依存性注入についても同様です。注入という単語から特定の動作を指す単語のように聞こえますが、実際にはソフトウェア開発における設計パターンの事を指します。
(依存性注入 = 依存関係を解決するためのパーツ(依存オブジェクト)を外から注入す様に設計する、設計パターンの事)
依存関係と依存性注入に関する解説
依存関係とは、あるコンポーネントが他のコンポーネントの機能やデータ(依存オブジェクト)に依存している関係を指します。依存性注入では依存オブジェクトの実装をクラス内部で行わず、外部から注入して依存関係の解決を行います。
では、なぜそのように実装するのでしょうか?
依存性注入の目的
GPTに質問したところ以下の様な回答が得られました。
・結合度の低下: コンポーネント間の結合度が低下するため、変更に強くなります。
・再利用性の向上: 同じコンポーネントを異なる環境で再利用しやすくなります。
・テストの容易さ: モックオブジェクトを使用して簡単にテストできます。
・設定と実装の分離: 設定を中央で管理しやすくなります。
これだとまだ少し難解であったので車とエンジンの関係に例えてもらったところわかりやすい回答が得られたのでご紹介いたします。(画像も生成!(^^♪)
依存性注入を行わない場合
車を走らせるためにはエンジンが必要不可欠であるため、これは依存関係といえます。依存性注入を行わない場合以下の画像のような状態となります。
これは車が特定のエンジン(依存オブジェクト)に強く依存していることを表します。この場合、別のエンジンに容易に変更することはできません。また車そのものが特定のエンジンに強く依存しているため、エンジンの仕様変更の影響を強く受けたり、再利用性が低下するといえます。
コードでの実装例は以下の通りです。
class Engine:
def start(self):
return "エンジンが始動しました"
class Car:
def __init__(self):
self.engine = Engine() # エンジンのインスタンスを直接生成
def start(self):
return self.engine.start()
my_car = Car()
print(my_car.start()) # "エンジンが始動しました"
依存性注入を行う場合
依存性注入の設計パターンを採用した車は以下の画像のようになります。
車とエンジンは依存関係にありますが、この車は依存オブジェクトを外部から注入されるように設計されています。こうすることにより車は特定のエンジンに依存せず柔軟性を獲得します。
コードの実装例は以下の通りです.
class Engine:
def start(self):
return "エンジンが始動しました"
class ElectricEngine:
def start(self):
return "電気エンジンが始動しました"
class Car:
def __init__(self, engine):
self.engine = engine # エンジンのインスタンスを外部から注入
def start(self):
return self.engine.start()
# ガソリンエンジンを使用する車
gasoline_engine = Engine()
gasoline_car = Car(gasoline_engine)
print(gasoline_car.start()) # "エンジンが始動しました"
# 電気エンジンを使用する車
electric_engine = ElectricEngine()
electric_car = Car(electric_engine)
print(electric_car.start()) # "電気エンジンが始動しました"
この例ではCarクラスは引数でエンジンクラスを受け取り、コンストラクタでエンジンが注入されます。Car クラスはエンジンの具体的なタイプに依存せず、任意の Engine インターフェースを実装するオブジェクトを使用できます。これにより、Car クラスはより柔軟で再利用可能になり、異なるタイプのエンジンで簡単に動作させることができます。
依存性注入を使用することで、クラス間の結合度が低下し、コードの再利用性とテストのしやすさが向上します。
終わりに
依存関係や依存巻毛解決といったワードはうっすら知りつつも日本語的な意味での拒否反応を示してしまっていました(-_-;)
今回改めてまとめたことにより、アレルギーを克服した気持ちです!しこたま結合度下げていきたいですね(^^♪