numanicloud / deptorygen Goto Github PK
View Code? Open in Web Editor NEW静的コード生成で依存関係を解決する、DIコンテナ的なアナライザー+CodeFix。
License: MIT License
静的コード生成で依存関係を解決する、DIコンテナ的なアナライザー+CodeFix。
License: MIT License
コレクションにインスタンスを注入する際の記法は少々煩雑だ。
// Service抽象クラスを継承する ServiceA, ServiceB, ServiceC があるとして
[Factory]
interface IFactory
{
ServiceA ResolveServiceA();
ServiceB ResolveServiceB();
ServiceC ResolveServiceC();
[Resolution(typeof(ServiceA))]
[Resolution(typeof(ServiceB))]
[Resolution(typeof(ServiceC))]
IEnumerable<Service> ResolveServices();
Client ResolveClient();
}
複数のクラスをコレクションに注入したいので、ServiceA
, ServiceB
といったクラスを個別に生成したいわけではないシナリオも多いはず。
以下に示すものくらい簡潔に書きたい。
[Factory]
interface IFactory
{
[Resolution(typeof(ServiceA))]
[Resolution(typeof(ServiceB))]
[Resolution(typeof(ServiceC))]
Service ResolveServiceC();
IEnumerable<Service> ResolveServices();
Client ResolveClient();
}
現状アナライザーがnetstandardで書いてあるので、Visual Studioで動かすためにユーザーがDLLを手動で配置する必要がある。
netframeworkで書けばこの手順が要らなくなるかもしれない。
依存関係を解決するとき、現状では以下の選択肢がある
これらのうち、解決に使用できるオブジェクトが複数ある状況下でどれが選ばれるのかが成り行きで決まっているので、きちんと優先順を決めたい。
以下のように、ある具象クラスを解決するファクトリーIScopedFactory
と、その具象クラスを生成してほしい解決メソッドを持つIFactory
がある。
[Factory]
interface IScopedFactory
{
EventManager ResolveEventManager();
}
[Factory]
interface IFactory
{
IScopedFactory Scoped { get; }
[Resolution(typeof(EventManager))]
IEventHandler ResolveEventHandler();
[Resolution(typeof(EventManager))]
IEventSource ResolveEventSource();
}
現状ではIFactory
は、キャプチャした解決メソッドを使わないように生成される。Resolution
属性に指定した型に対してもキャプチャを考慮してほしい。
以下のようなシナリオで、戻り値の型の異なる複数の解決メソッドで同じインスタンスを返したいことがある:
[Factory]
interface IFactory
{
// 1つのクラスが2つのインターフェースを持っていて、
// それぞれの型についてインスタンスを解決したい
[Resolution(typeof(EventManager))]
IEventHandler ResolveEventHandler();
[Resolution(typeof(EventManager))]
IEventSource ResolveEventSource();
}
class EventManager : IEventHandler, IEventSource
{
// 他の場所から来たイベントをプロキシする処理
}
同じResolution
属性をつけたメソッドでは同じインスタンスを返すようにもできるが、異なるインスタンスを返すようにしたい場合はどうする?
同じResolution
属性をつけたメソッドでは同じインスタンスを返すようにした場合、異なるインスタンスを生成したい場合はキャッシュをしない解決メソッドとして定義する方法がある。
[Factory]
interface IFactory
{
[Resolution(typeof(EventManager))]
IEventHandler ResolveEventHandlerAsTransient();
[Resolution(typeof(EventManager))]
IEventSource ResolveEventSourceAsTransient();
}
とはいえ、これでは解決メソッドごとにキャッシュを持ちたい場合に役に立たない。
Resolution
属性に何らかのIDを持たせると良いかもしれない。
[Factory]
interface IFactory
{
[Resolution(typeof(EventManager), Id = 0)]
IEventHandler ResolveEventHandler();
[Resolution(typeof(EventManager), Id = 1)]
IEventSource ResolveEventSource();
}
別々のIDであれば、別々のインスタンスが生成され、キャッシュもされる。
Resolution
属性ではなく戻り値の型だけでインスタンスが決定される場合もあるので、Resolution
属性ではなくてまた別の属性として用意するほうが良いかも?
[Factory]
interface IFactory
{
[Resolution(typeof(EventManager))]
[Unique(0)]
IEventHandler ResolveEventHandler();
[Resolution(typeof(EventManager))]
[Unique(1)]
IEventSource ResolveEventSource();
}
同一のインスタンスを返したいとき、他のファクトリーの解決メソッドに委譲する手がある。
[Factory]
interface IScopedFactory
{
EventManager ResolveEventManager();
}
[Factory]
interface IFactory
{
IScopedFactory Scoped { get; }
[Resolution(typeof(EventManager))]
IEventHandler ResolveEventHandler();
[Resolution(typeof(EventManager))]
IEventSource ResolveEventSource();
}
現状、Resolution
で指定されている型がキャプチャしたファクトリーの解決メソッドと一致していても、戻り値の型が一致しているのでなければその解決メソッドに委譲することはない。
仮に委譲するようにしたとして、これだけのためにファクトリーを1つ生成したりするのは直感に反するためあまり嬉しくなさそう。
ところで、Resolution
で指定されている型に対してキャプチャしたファクトリーへ委譲する機能自体は欲しいと思う。
Deptorygenドキュメントで使用する用語をきちんと決めて、それをまとめたドキュメントを書きたい
次のような定義があるとする。
using Deptorygen.Annotations;
namespace Sample
{
class TestProvidee
{
}
class TestService
{
}
[Factory]
interface ITestFactory
{
TestProvidee ResolveTestProvidee();
[Resolution(typeof(TestFactory))]
ITestFactory ResolveTestFactory();
}
}
生成されるコードは次のような感じ。(いちどTestFactory
を生成してから、もう一度生成する必要がある)
// <autogenerated />
#nullable enable
using System;
using System.Collections.Generic;
namespace Sample
{
internal partial class TestFactory : ITestFactory
, IDisposable
{
private TestProvidee? _ResolveTestProvideeCache;
private TestFactory? _ResolveTestFactoryCache;
public TestFactory()
{
}
public TestProvidee ResolveTestProvidee()
{
return _ResolveTestProvideeCache ??= new TestProvidee();
}
public ITestFactory ResolveTestFactory()
{
return _ResolveTestFactoryCache ??= new TestFactory();
}
public void Dispose()
{
_ResolveTestFactoryCache?.Dispose();
}
}
}
いま、TestProvidee
クラスのコンストラクタでTestService
を要求するようにする。
class TestProvidee
{
public TestProvidee(TestService service)
{
}
}
これでコード生成しなおすと、TestFactory
クラスのコンストラクタ引数が変化する。
public TestFactory(TestService testService)
{
_testService = testService;
}
ここで次に、TestProvidee
のコンストラクタがふたたび引数無しに変更されると、再コード生成されたときにTestFactory
クラスのコンストラクタも引数無しになって欲しいが、そうはならない。
なぜなら、TestFactory
自身がTestService
を要求していて、かつTestFactory
はTestFactory
自身を生成しなければならないから。
// こいつが悪い
public ITestFactory ResolveTestFactory()
{
return _ResolveTestFactoryCache ??= new TestFactory(_testService);
}
自分自身を生成するためだけに必要としている依存関係は依存関係と見なさないよう修正する必要がある。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.