私の状況
私は現在、ソフトウェア アーキテクチャについてもう少し学習するために使用したい Python のプロジェクトについて書いています。私はいくつかのテキストを読み、依存性注入に関するいくつかの講演を見て、コンストラクター注入がオブジェクトの依存性をどのように明確に示すかが好きであることを学びました。ただし、依存関係をオブジェクトに渡す方法に苦労しています。次の理由から、DI フレームワークを使用しないことにしました。
- 要件を指定するのに十分な DI の知識がないため、フレームワークを選択できません。
- めったに使用されないフレームワークを導入すると可読性が大幅に低下すると感じているため、コードから「魔法のような」ものを排除したいと考えています。(ほんの一部しか使用されていないコードを読む必要があります)。
したがって、カスタム ファクトリ関数を使用してオブジェクトを作成し、その依存関係を明示的に渡します。
# Business and Data Objects
class Foo:
def __init__(self,bar):
self.bar = bar
def do_stuff(self):
print(self.bar)
class Bar:
def __init__(self,prefix):
self.prefix = prefix
def __str__(self):
return str(self.prefix)+"Hello"
# Wiring up dependencies
def create_bar():
return Bar("Bar says: ")
def create_foo():
return Foo(create_bar())
# Starting the application
f = create_foo()
f.do_stuff()
あるいは、Foo
多数のBar
s 自体を作成する必要がある場合は、そのコンストラクターを介して渡された作成者関数を取得します。
# Business and Data Objects
class Foo:
def __init__(self,create_bar):
self.create_bar = create_bar
def do_stuff(self,times):
for _ in range(times):
bar = self.create_bar()
print(bar)
class Bar:
def __init__(self,greeting):
self.greeting = greeting
def __str__(self):
return self.greeting
# Wiring up dependencies
def create_bar():
return Bar("Hello World")
def create_foo():
return Foo(create_bar)
# Starting the application
f = create_foo()
f.do_stuff(3)
コードに関する改善提案を聞きたいのですが、これはこの投稿の本当の目的ではありません。ただし、理解するにはこの紹介が必要だと思います
私の質問
上記はかなり明確で、読みやすく、理解しやすいように見えBar
ますが、各オブジェクトのコンテキストで の接頭辞の依存関係が同一である必要があり、オブジェクトの有効期間Foo
に結び付けられている場合に問題が発生します。Foo
例として、カウンターを実装するプレフィックスを考えてみましょう (実装の詳細については、以下のコード例を参照してください)。これを実現する方法は 2 つありますが、どれも完璧ではないようです。
1) Foo を介してプレフィックスを渡す
最初のアイデアは、コンストラクター パラメーターを追加して、各インスタンスFoo
にプレフィックスを格納するようにすることです。Foo
明らかな欠点は、 の責任を混同することですFoo
。ビジネス ロジックを制御し、依存関係の 1 つを に提供しBar
ます。Bar
依存関係が不要になったら、Foo
変更する必要があります。私には無理のようです。これが解決策になるとは思えないので、ここにはコードを投稿しませんでしたが、非常に興味のある読者のためにペーストビンで提供しました;)
2) 状態で関数を使用する
Prefix
オブジェクトをこのアプローチ内に配置する代わりにFoo
、関数内にカプセル化しようとしていcreate_foo
ます。Prefix
オブジェクトごとに1 つ作成し、 をFoo
使用して名前のない関数で参照することにより、詳細 (別名、プレフィックス オブジェクトがあります)をワイヤリング ロジックlambda
から遠ざけて内部に保持します。Foo
もちろん、名前付き関数も機能します (ただし、ラムダの方が短い)。
# Business and Data Objects
class Foo:
def __init__(self,create_bar):
self.create_bar = create_bar
def do_stuff(self,times):
for _ in range(times):
bar = self.create_bar()
print(bar)
class Bar:
def __init__(self,prefix):
self.prefix = prefix
def __str__(self):
return str(self.prefix)+"Hello"
class Prefix:
def __init__(self,name):
self.name = name
self.count = 0
def __str__(self):
self.count +=1
return self.name+" "+str(self.count)+": "
# Wiring up dependencies
def create_bar(prefix):
return Bar(prefix)
def create_prefix(name):
return Prefix(name)
def create_foo(name):
prefix = create_prefix(name)
return Foo(lambda : create_bar(prefix))
# Starting the application
f1 = create_foo("foo1")
f2 = create_foo("foo2")
f1.do_stuff(3)
f2.do_stuff(2)
f1.do_stuff(2)
このアプローチは、私にとってはるかに便利なようです。ただし、一般的な慣行についてはよくわからないため、関数内に状態を持つことは実際には推奨されないのではないかと心配しています。Java/C++ のバックグラウンドから来た私は、関数がそのパラメーター、そのクラス メンバー (メソッドの場合)、または何らかのグローバル状態に依存することを期待しています。したがって、グローバル状態を使用しないパラメーターなしの関数は、呼び出されるたびにまったく同じ値を返す必要があります。ここではそうではありません。返されたオブジェクトが変更されると ( counter
inprefix
が増加したことを意味します)、関数は最初に返されたときとは異なる状態のオブジェクトを返します。この仮定は、Python での私の制限された経験によって引き起こされたものですか?また、考え方を変える必要がありますか?つまり、関数については考えず、呼び出し可能なもの?それとも、ラムダの意図しない誤用である状態で関数を供給していますか?
3) 呼び出し可能なクラスの使用
ステートフル関数に対する私の疑問を克服するために、create_foo
アプローチ 2 の関数が次のように置き換えられる呼び出し可能なクラスを使用できます。
class BarCreator:
def __init__(self, prefix):
self.prefix = prefix
def __call__(self):
return create_bar(self.prefix)
def create_foo(name):
return Foo(BarCreator(create_prefix(name)))
これは私にとっては有効な解決策のように思えますが、非常に冗長です。
概要
この状況をどのように処理すればよいか、まったくわかりません。私は2番の方が好きですが、まだ疑問があります。さらに、誰かがよりエレガントな方法を考え出すことを願っています。
漠然としすぎている、または誤解されている可能性があると思われるものがあれば、コメントしてください。私の能力が許す限り、質問を改善します:) すべての例はpython2.7およびpython3で実行する必要があります-問題が発生した場合は、コメントで報告してください。コードを修正します。