前回の続きです。
クラスの依存関係の管理方法について解説しました。
今回は依存関係の定義について少し深掘りします。
インターフェースとは
突然ですが、以下はC#のコード例です。
interface ISampleInterface
{
void SampleMethod();
}
class ImplementationClass : ISampleInterface
{
// Explicit interface member implementation:
void ISampleInterface.SampleMethod()
{
// Method implementation.
}
static void Main()
{
// Declare an interface instance.
ISampleInterface obj = new ImplementationClass();
// Call the member.
obj.SampleMethod();
}
}
インタフェースとは実際の処理を定義せず、I/Oのみ定義することができるものです。
ここではISampleInterfaceを実装したImplementationClassという例です。これだけ見るとあまりメリットを感じませんがDIと結びつける事で真価を発揮します。
Pythonのインタフェース
DIとの結びつきを解説する前に、悲しいお知らせです。
Pythonの言語仕様にはインタフェースはありません。
しかし代替クラスが用意されています。
さきほどのC#の例をPythonで書くと以下のようになります。
from abc import ABCMeta, abstractmethod
class ISampleInterface
@abstractmethod
def sample_method(self) -> None:
raise NotImlementdError()
class ImplementationClass(ISampleInterface) -> None:
def sample_method(self):
pass
DIでの活用方法
前回の解説で使用したサンプルを少し変えます。
from abc import ABCMeta, abstractmethod
from injector import Injector, inject, Module, singleton , provider
class IUserRepository(ABCMeta):
@abstractmethod
def get_user(self, user_id):
raise NotImlementdError()
class UserService:
# このデコレータによって依存関係がinjectorによって解決されます
@inject
def __init__(self, repository: IUserRepository):
self.repository = repository
def find_user(self, user_id):
return self.repository.get_user(user_id)
class UserRepository(IUserRepository):
def get_user(self, user_id):
# データソースからユーザー情報を取得する処理
pass
class AppModule(Module):
# クラスの依存関係を定義する
@singleton
@provider
def provide_user_repository(self,user_repository : UserRepository) -> IUserRepository:
return user_repository
def handler(event, context):
injector = Injector(AppModule())
# injectorがクラスの依存関係を解決したうえで、インスタンスを提供する
user_service = injector.get(UserService)
user_id = event["user_id"]
user = user_service.find_user(user_id)
return user
IUserRepositoryがインタフェースで、UserRepositoryが実装クラスです。そして、UserServiceはIUserRepositoryに依存し、IUserRepositoryはUserRepositoryで解決するように定義しました。
メリット
インタフェースで定義することで実装クラスをModuleの変更で切り替える事ができます。Repositoryクラスのように外部リソースの依存があるクラスをモッククラスに切り替えることで単体テストの実行が楽になります。
上記メリットだけだとpytestで解決できてしまうのですが、他にもlayerとの付き合い方にもメリットがあります。それはinjector(DIコンテナ)の隠蔽という形で今後解説します。