デュアルスケジューリング

少し前まで、私はこのサービスで非常に興味深い仕事に出会いました。 しかし、月の下で新しいものは何もありません-そして、タスクはあなたに長い間親しまれています:静的型付けの観点からC#での二重ディスパッチ。 以下で詳しく説明しますが、すでにすべてを理解している人のために、「訪問者」を使用しますが、それはかなり珍しいことです。



問題をより厳密に定式化する前に、いくつかの注意点があります。動的が私に適していない理由、キャストとリフレクションによる明示的な型チェックについては触れません。 これには2つの理由があります:1)目標はランタイム例外を取り除くことです2)上記のツールに頼らず厳密なタイピングのフレームワーク内に留まらない場合でも、言語が非常に表現力があることを示したいと思います。



ステージング



言い換えれば、かなり一般的で内容の少ないインターフェースを持つさまざまなタイプのオブジェクトの特定のコレクションを手に持っていると仮定します。 そのうちの2つを取り上げて、それぞれのタイプを理解し、特別な方法で処理したいと考えています。



例:





このような問題を解決するには、フォームのクラスが1つあれば十分だと思います。



internal class Cell { public Cell(string color) { Color = color; } public string Color { get; private set; } }
      
      





もちろん、実際には、どのような方法でも共通インターフェースにプッシュできないオブジェクトの特定のプロパティにアクセスする必要がありますが、複雑なドメインドメインでサンプルを散らかすことはできません。そのため、共通インターフェースには色に関する言葉はなく、すべてのセルはこの赤のように見えます以下:



  internal interface ICell { //Some code } internal class RedCell : ICell { public string Color { get { return ""; } } //Some code }
      
      





クラシックビジター



1つの要素に関するこのような問題の解決策は、すでに古典的なものになっています。 これについては詳しく説明しません。たとえば、 Wikipediaでこれについて詳しく知ることができます。 この場合、ソリューションは次のようになります。 このモデルでは(このコードを[1]として指定します。以下で思い出します):



  internal interface ICell { T AcceptVisitor<T>(ICellVisitor<T> visitor); } internal interface ICellVisitor<out T> { T Visit(RedCell cell); T Visit(BlueCell cell); T Visit(GreenCell cell); } internal class RedCell : ICell { public string Color { get { return ""; } } public T AcceptVisitor<T>(ICellVisitor<T> visitor) { return visitor.Visit(this); } } internal class BlueCell : ICell { public string Color { get { return ""; } } public T AcceptVisitor<T>(ICellVisitor<T> visitor) { return visitor.Visit(this); } } internal class GreenCell : ICell { public string Color { get { return ""; } } public T AcceptVisitor<T>(ICellVisitor<T> visitor) { return visitor.Visit(this); } }
      
      





単純な訪問者は問題を簡単に解決します。



 internal class Visitor : ICellVisitor<string> { public string Visit(RedCell cell) { //        // ICell,   RedCell      //    Color return cell.Color; } public string Visit(BlueCell cell) { return cell.Color; } public string Visit(GreenCell cell) { return cell.Color; } }
      
      





訪問者をタスクに適用する



それでは、2つの要素の場合に利用可能なソリューションを一般化してみましょう。 最初に頭に浮かぶのは、各セルをビジターに変えることです。彼女はすでに自分のタイプを知っているので、製品を訪れたときに自分のタイプを知っているので、問題を解決します。 次のような結果になります(簡単にするために、赤のセルに対してのみソリューションを作成します。そうでない場合、多くのコードがあります)。



  internal interface ICell { T AcceptVisitor<T>(ICellVisitor<T> visitor); } internal class RedCell<T> : ICell, ICellVisitor<T> { private readonly IProcessor<T> _processor; public RedCell(IProcessor<T> processor) { _processor = processor; } public TVisit AcceptVisitor<TVisit>(ICellVisitor<TVisit> visitor) { return visitor.Visit(this); } public string Color { get { return "red"; } } public T Visit(RedCell<T> cell) { return _processor.Get(this, cell); } public T Visit(BlueCell<T> cell) { return _processor.Get(this, cell); } public T Visit(GreenCell<T> cell) { return _processor.Get(this, cell); } } interface IProcessor<T> { T Get(RedCell<T> a, RedCell<T> b); T Get(RedCell<T> a, BlueCell<T> b); T Get(RedCell<T> a, GreenCell<T> b); }
      
      





あとは、単純なプロセッサを追加するだけで、問題は解決しました!



  internal class Processor : IProcessor<string> { public string Get(RedCell<string> a, RedCell<string> b) { return "  "; } public string Get(RedCell<string> a, BlueCell<string> b) { return GeneralCase(a, b); } public string Get(RedCell<string> a, GreenCell<string> b) { return GeneralCase(a, b); } private string GeneralCase(ICell a, ICell b) { return a.Color + " --> " + b.Color; } }
      
      





見つかった解決策に対する批判



まあ、確かに、解決策が見つかりました。 好きですか しません その理由は次のとおりです。



上記が私の意見を確認するのに十分であることを願っています-決定はまあまあですが、より詳細に説明したい別の重要なポイントがあります。 IProcessorに含まれるメソッドの数を考えますか? N x N つまり、たくさん。 しかし、結局のところ、 Nに線形の非常に少数のケースのために特別な処理が必要になる可能性が高いです それにもかかわらず、どれが私たちに役立つかを事前に知ることはできません(そして、私たちはフレームワークを書いていますよね?クラス構造、誰もが後で使用し、アセンブリをソリューションに接続する基礎)。



どのように改善できますか? 明らかなステップは、訪問者からモデルを分離することです。 はい、以前と同様に、各セルにAcceptVisitor(...)を使用できますが、すべてのVisitメソッドは別々のクラスになります。 必要なもの、この場合はN + 1クラス、それぞれがN Visitメソッドを含むものを理解するのは簡単です。 弱くないよね? さらに、新しいセルは、メソッドによって既存の各クラスに新しいクラスを追加します。



最高のソリューションが見つかりました



つまり、いくつかのクラスが必要です(流haveなインターフェイスのような異なる構文の可愛さから、私は抵抗できず、この数に追加しますが、それらを使用するかどうかは正確ではありません)味)、およびクラスの数はNに依存します; 新しいセルを追加する場合、これらのクラスにN個の独立したメソッドを追加する必要があります。



(よくわからないが)まだ読んでいる場合は、しばらく考えて、これらの要件を満たすソリューションを提供できますか?



はい、それから素晴らしい場合、私に書いてください、しかし、ここで私のもの:



私たちのモデルはまだ[1]の形式であるため、(患者の読者に少し興味を持たせるために)前の例の特定のプロセッサーの類似物は次のようになります。



 internal class ConcreteDispatcherFactory { public ICellVisitor<ICellVisitor<string>> CreateDispatcher() { return new PrototypeDispatcher<string>(Do) .TakeRed.WithRed(Do) .TakeGreen.WithBlue(Do); } private string Do(ICell a, ICell b) { var colorRetriever = new ColorRetriever(); var aColor = a.AcceptVisitor(colorRetriever); var bColor = b.AcceptVisitor(colorRetriever); return aColor + "\t-->\t" + bColor; } private string Do(GreenCell a, BlueCell b) { return ""; } private string Do(RedCell a, RedCell b) { return "  "; } }
      
      





ColorRetrieverは単純な「単一」訪問者です。



 internal class ColorRetriever : ICellVisitor<string> { public string Visit(RedCell cell) { return cell.Color; } public string Visit(BlueCell cell) { return cell.Color; } public string Visit(GreenCell cell) { return cell.Color; } }
      
      





[実際、決定自体によって、この最後のColorRetrieverについて少し余談があります -線自体が

  var colorRetriever = new ColorRetriever(); var aColor = a.AcceptVisitor(colorRetriever); var bColor = b.AcceptVisitor(colorRetriever);
      
      





それでもタスクを解決しないでください。 彼らの助けを借りて、インターフェイスによって隠されていない明示的な型の形式で2つのセルのそれぞれに順番にアクセスするだけですが、両方に同時にアクセスする必要があります。 maxim_0_oの注意により修正が行われました。



ご覧のとおり、一般的なケースと2つの特定のケースを規定しており、これでソリューションを構築できます。

概して、1番目と2番目のビジターという2つのクラスが必要になります。 2番目は一般化され、1番目は特定のセルに使用するために型付きインスタンスを作成します。



これが最初のものです:



 class PrototypeDispatcher<TResult> : ICellVisitor<ICellVisitor<TResult>> { private readonly Builder<TResult, RedCell> _redBuilder; private readonly Builder<TResult, GreenCell> _greenBuilder; private readonly Builder<TResult, BlueCell> _blueBuilder; public PrototypeDispatcher(Func<ICell, ICell, TResult> generalCase) { _redBuilder = new Builder<TResult, RedCell>(this, generalCase); _blueBuilder = new Builder<TResult, BlueCell>(this, generalCase); _greenBuilder = new Builder<TResult, GreenCell>(this, generalCase); } public IBuilder<TResult, RedCell> TakeRed { get { return _redBuilder; } } public IBuilder<TResult, BlueCell> TakeBlue { get { return _blueBuilder; } } public IBuilder<TResult, GreenCell> TakeGreen { get { return _greenBuilder; } } public ICellVisitor<TResult> Visit(RedCell cell) { return _redBuilder.Take(cell); } public ICellVisitor<TResult> Visit(BlueCell cell) { return _blueBuilder.Take(cell); } public ICellVisitor<TResult> Visit(GreenCell cell) { return _greenBuilder.Take(cell); } }
      
      





次に2つ目を示します。



  internal class Builder<TResult, TA> : IBuilder<TResult, TA>, ICellVisitor<TResult> where TA : ICell { private Func<TA, RedCell, TResult> _takeRed; private Func<TA, BlueCell, TResult> _takeBlue; private Func<TA, GreenCell, TResult> _takeGreen; private readonly Func<ICell, ICell, TResult> _generalCase; private readonly PrototypeDispatcher<TResult> _dispatcher; private TA _target; public Builder(PrototypeDispatcher<TResult> dispatcher, Func<ICell, ICell, TResult> generalCase) { _dispatcher = dispatcher; _generalCase = generalCase; _takeRed = (a, b) => _generalCase(a, b); _takeBlue = (a, b) => _generalCase(a, b); _takeGreen = (a, b) => _generalCase(a, b); } public PrototypeDispatcher<TResult> WithRed(Func<TA, RedCell, TResult> toDo) { _takeRed = toDo; return _dispatcher; } public PrototypeDispatcher<TResult> WithBlue(Func<TA, BlueCell, TResult> toDo) { _takeBlue = toDo; return _dispatcher; } public PrototypeDispatcher<TResult> WithGreen(Func<TA, GreenCell, TResult> toDo) { _takeGreen = toDo; return _dispatcher; } public TResult Visit(RedCell cell) { return _takeRed(_target, cell); } public TResult Visit(BlueCell cell) { return _takeBlue(_target, cell); } public TResult Visit(GreenCell cell) { return _takeGreen(_target, cell); } public ICellVisitor<TResult> Take(TA a) { _target = a; return this; } }
      
      





そして、ビルダーを訪問者から分離するための美しさのための別のインターフェース(両方のクラスでマージされますが、呼び出し構文は美しいです):



  internal interface IBuilder<TResult, out TA> { PrototypeDispatcher<TResult> WithRed(Func<TA, RedCell, TResult> toDo); PrototypeDispatcher<TResult> WithBlue(Func<TA, BlueCell, TResult> toDo); PrototypeDispatcher<TResult> WithGreen(Func<TA, GreenCell, TResult> toDo); }
      
      





結論として、 「ウィザードと戦士について」の一連の記事を参照したいと思います。これは、C#のディスパッチの問題についても説明しています。



All Articles