6

SimpleInjectorをWebFormsMvpと組み合わせようとしています。

DIを容易にするために、WebFormsMvpはIPresenterFactoryインターフェースを提供します。
これには、解決するプレゼンタータイプビューインスタンスCreateを提供するメソッドが含まれています。ビューインスタンスをプレゼンターコンストラクター挿入する 必要があります。 プレゼンターには、コンテナーによる作成が必要な他の依存関係あります。

これは私がこれまでに得たものですが、理想的ではありません。問題の正しい解決策
は 何ですか?

プレゼンターコンストラクター:

public FooPresenter(IFooView view, IClientFactory clientFactory) : base(view)

工場:

public class SimpleInjectorPresenterFactory : IPresenterFactory
{
    private readonly Container _container;
    private IView _currentView;

    public SimpleInjectorPresenterFactory()
    {
        _container = new Container();

        Func<Type, bool> isIView = 
            type => typeof(IView).IsAssignableFrom(type);

        _container.ResolveUnregisteredType += (s, e) => {
            if (isIView(e.UnregisteredServiceType))
                e.Register(() => _currentView);
        };
    }

    public IPresenter Create(Type presenterType, Type viewType, IView viewInstance)
    {
        lock (_currentView)
        {
            _currentView = viewInstance;
            return _container.GetInstance(presenterType) as IPresenter;
        }
    }
}
4

1 に答える 1

5

WebFormsMvpは、プレゼンターのコンストラクターでビューを取得するように強制しますが、これにより循環参照がトリガーされます。さまざまなコンテナのファクトリ実装を見ると、コンテナごとに、設計のこの癖を回避するためにさまざまなトリックを実行していることがわかります。たとえば、ユニティを使用して、子コンテナを作成し、そのビューを子コンテナに登録し、その子コンテナを使用してプレゼンターを解決します。かなり奇妙でパフォーマンスが重い。

プレゼンターのコンストラクターでビューを取得する代わりに、WebFormsMvpの設計者は、ビューをIPresenterインターフェイス上の書き込み可能なプロパティにする必要があります。これにより、プレゼンターのビューを設定するのが恥ずかしいほど簡単になります。このようなもの:

public IPresenter Create(Type presenterType, IView view)
{
    var presenter = (IPresenter)_container.GetInstance(presenterType);
    presenter.View = view;
    return presenter;
}   

残念ながら、彼らはこれをしませんでした、そしてこれを可能にするためにデザインを拡張することは不可能です(反射を使って本当に厄介なことをしなければ)。

Simple Injectorは、メソッドへのコンストラクター引数の提供をサポートしていませんGetInstance()。これは通常、Service Locatorのアンチパターンにつながるため、正当な理由があります。これは、デザインを変更することでいつでも回避できます。あなたの場合、あなたはその風変わりなデザインを作らなかったので、それを変えることはできません。

あなたがしたことResolveUnregisteredTypeはかなり賢いです。私はこれについて自分で考えていなかっただろう。そして、私はSimple Injectorの主任開発者なので、あなたがしたことは本当に賢かったと言う立場にあります:-)

あなたについてのフィードバックのちょうど2つのポイントSimpleInjectorPresenterFactory

まず、コンストラクター引数としてasを指定する必要があります。これは、コンテナーに他の登録を追加する必要があり、内部Containerを登録したくないためです。ContainerSimpleInjectorPresenterFactory

次に、を使用してコードを改善できますSystem.Threading.ThreadLocal<IView>。これにより、グローバルロックを取り除くことができます。ロックにより、プレゼンターが同時に作成されるのを防ぎます。これにより、Webサイトの速度が低下する可能性があります。

したがって、リファクタリングされたバージョンは次のとおりです。

public class SimpleInjectorPresenterFactory : IPresenterFactory {
    private readonly Container _container;
    private ThreadLocal<IView> _currentView = new ThreadLocal<IView>();

    public SimpleInjectorPresenterFactory(Container container) {
        _container = container;

        _container.ResolveUnregisteredType += (s, e) => {
            if (typeof(IView).IsAssignableFrom(e.UnregisteredServiceType)) {
                e.Register(() => _currentView.Value);
            }
        };
    }

    public IPresenter Create(Type presenterType, Type viewType, 
        IView viewInstance)
    {
        _currentView.Value = viewInstance;

        try {
            return _container.GetInstance(presenterType) as IPresenter;
        } finally {
            // Clear the thread-local value to ensure
            // views can be disposed after the request ends.
            _currentView.Value = null;
        }
    }
}

の実装をUnityPresenterFactory見ると、そこで多くのキャッシングが行われていることがわかります。なぜそうなるのかわかりませんが、パフォーマンスの観点からは、SimpleInjectorにはそのようなものはまったく必要ありません。何かが足りないのかもしれませんが、なぜキャッシュが必要なのかわかりません。

しかし、さらに悪いことに、には同時実行のバグがありUnityPresenterFactoryます。この方法を見てください:

private Type FindPresenterDescribedViewTypeCached(Type presenter, 
    IView view) 
{
    IntPtr handle = presenter.TypeHandle.Value;
    if (!this.cache.ContainsKey(handle)) 
    {
        lock (this.syncLock)
        {
            if (!this.cache.ContainsKey(handle))
            {
                Type viewType = CreateType(presenter, view);
                this.cache[handle] = viewType;
                return viewType;
            }
        }
    }
    return this.cache[handle];
}

ダブルチェックロックが実装されているため、一見するとこのコードは問題ないように見えます。残念ながら、キャッシュ(ディクショナリ)はロックの外側から読み取られますが、ロックの内側で更新されます。これはスレッドセーフではありません。代わりに、開発者はすべてをロックでラップするか、ConcurrentDictionary(。net 4のみ)を使用するか、cache不変であると見なす必要があります。つまり、元の辞書のコピーを作成し、新しい値を追加して、古い辞書と新しい辞書。ただし、この場合、私はおそらくすべてをロックしていたでしょう。

これは少し話題から外れていましたが、伝えたかっただけです:-)

于 2013-02-28T00:22:39.843 に答える