プログラムの様々な部分で状態を共有する必要がある場合、グローバル変数を使用するのは簡単な方法です。
しかし、これはコードの再利用性を損なう可能性があります。
本記事では、グローバル状態の問題点と、依存性注入(DI)を用いた解決策について解説します。
グローバル状態の問題点
グローバル状態は、プログラム内のすべてのコンテキストから参照できる変数です。
例えば、以下のようなコードを考えてみましょう。
class UserManager: users = [] @staticmethod def add_user(user): UserManager.users.append(user) @staticmethod def get_users(): return UserManager.users
このコードでは、users変数がクラス変数として定義されています。
add_userメソッドとget_usersメソッドは、ともにこのグローバル状態にアクセスしています。
一見すると便利そうに見えますが、このようなグローバル状態の使用には問題があります。
再利用性の低下
グローバル状態に依存するコードは、他の用途で再利用することが難しくなります。
予期せぬ副作用
グローバル状態を変更すると、プログラムの他の部分に予期せぬ影響を与える可能性があります。
テストの困難さ
グローバル状態を使用するコードは、テストが難しくなります。
依存性の注入による解決策
これらの問題を解決するために、依存性の注入(DI)を使用することができます。
DIは、クラスが必要とする依存関係をコンストラクタや関数の引数として受け取る手法です。
先ほどのコードを、DIを用いて書き換えてみましょう。
class UserManager: def __init__(self): self.users = [] def add_user(self, user): self.users.append(user) def get_users(self): return self.users class UserService: def __init__(self, user_manager): self.user_manager = user_manager def create_user(self, name): user = {"name": name} self.user_manager.add_user(user) def list_users(self): return self.user_manager.get_users()
この書き換えられたコードでは、以下の点が改善されています。
インスタンス変数の使用
UserManagerクラスは、インスタンス変数usersを使用しています。
依存性の注入
UserServiceクラスは、コンストラクタでUserManagerのインスタンスを受け取ります。
そして、それを使用してユーザーの作成と一覧取得を行います。
これにより、UserServiceクラスはUserManagerクラスに依存することになります。
その依存関係はコンストラクタを介して注入されています。
この設計により、以下のようなメリットがあります。
再利用性の向上
UserServiceクラスは、UserManagerクラスに直接依存していないため、異なる実装のUserManagerを注入することで再利用できます。
副作用の制御
UserManagerのインスタンスは、UserServiceクラスごとに独立しているため、あるインスタンスでの変更が他のインスタンスに影響を与えることはありません。
テストの容易さ
UserServiceクラスのテストでは、モックオブジェクトを注入することで、UserManagerクラスに依存することなくテストできます。
まとめ
グローバル状態の使用は、コードの再利用性を損ない、予期せぬ副作用を引き起こす可能性があります。
これらの問題を避けるために、依存性の注入(DI)を使用することが推奨されます。
DIを用いることで、コードの再利用性を高め、副作用を制御し、テストを容易にすることができます。
プログラムの状態を共有する必要がある場合は、グローバル変数の使用を避けましょう。
その代替案として、DIを用いて制御可能な方法で共有することを検討してください。
これにより、より保守性の高いコードを書くことができるでしょう。