0

私の状況

私は現在、ソフトウェア アーキテクチャについてもう少し学習するために使用したい Python のプロジェクトについて書いています。私はいくつかのテキストを読み、依存性注入に関するいくつかの講演を見て、コンストラクター注入がオブジェクトの依存性をどのように明確に示すかが好きであることを学びました。ただし、依存関係をオブジェクトに渡す方法に苦労しています。次の理由から、DI フレームワークを使用しないことにしました。

  1. 要件を指定するのに十分な DI の知識がないため、フレームワークを選択できません。
  2. めったに使用されないフレームワークを導入すると可読性が大幅に低下すると感じているため、コードから「魔法のような」ものを排除したいと考えています。(ほんの一部しか使用されていないコードを読む必要があります)。

したがって、カスタム ファクトリ関数を使用してオブジェクトを作成し、その依存関係を明示的に渡します。

    # 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多数のBars 自体を作成する必要がある場合は、そのコンストラクターを介して渡された作成者関数を取得します。

# 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++ のバックグラウンドから来た私は、関数がそのパラメーター、そのクラス メンバー (メソッドの場合)、または何らかのグローバル状態に依存することを期待しています。したがって、グローバル状態を使用しないパラメーターなしの関数は、呼び出されるたびにまったく同じ値を返す必要があります。ここではそうではありません。返されたオブジェクトが変更されると ( counterinprefixが増加したことを意味します)、関数は最初に返されたときとは異なる状態のオブジェクトを返します。この仮定は、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で実行する必要があります-問題が発生した場合は、コメントで報告してください。コードを修正します。

4

1 に答える 1

1

呼び出し可能なオブジェクトを注入したいが、複雑な設定をしたくない場合 - あなたの例のように、それが本当に単一の入力値にバインドされているだけの場合 -関数を提供するためにfunctools.partialを使用してみることができます<> 値のペア:

def factory_function(arg):
    #processing here
    return configurted_object_base_on_arg

class Consumer(object):
   def __init__(self, injection):
      self._injected = injection

   def use_injected_value():
      print self._injected()

injectable = functools.partial(factory_function, 'this is the configuration argument')
example = Consumer(injectable)
example.use_injected_value() # should return the result of your factory function and argument

余談ですが、オプション 3 のような依存性注入セットアップを作成している場合は、ここで行っているようにインラインで行うのではなく、構成をファクトリ クラスに行う方法に関する知識を入れたいと思うでしょう。そうすれば、戦略を選択したい場合に工場を交換できます。機能的にはそれほど違いはありません (作成がこの例よりも複雑で、永続的な状態が含まれる場合を除きます) が、コードが次のように見える場合、将来的にはより柔軟になります。

factory = FooBarFactory()
bar1 = factory.create_bar()
alt_factory = FooBlahFactory(extra_info)
bar2 = alt_factory.create_bar()
于 2013-01-18T22:37:43.930 に答える