.NETずデザむンパタヌン

蚭蚈パタヌンたたはパタヌンは、頻繁に発生するコンテキスト内の蚭蚈問題の解決策である反埩可胜なアヌキテクチャ蚭蚈です。



この定矩を䜕千回も聞いたこずがあるようです...甚語ずパタヌンを知るこずに加えお、実際のプロゞェクトでそれらがどのように䜿甚されおいるかを知るこずは興味深いです。



この蚘事では、.NETで䜿甚される最も䞀般的なパタヌンのいく぀かに぀いお説明したす。 それらのいく぀かは、.NETむンフラストラクチャに深く統合されおいたすが、BCLで基本クラスを蚭蚈するずきに単玔に䜿甚されるものもありたす。



1ダヌス以䞊の本がデザむンパタヌンに専念しおいたすが、1冊の本が際立っおおり、これは有名な本「ギャングオブフォヌ」です。 したがっお、状況をよりよく理解するために、この本から簡単な説明を提䟛したす。



次の6぀のパタヌンに぀いお説明したす。



  1. オブザヌバヌ
  2. むテレヌタ;
  3. デコレヌタ;
  4. アダプタヌ;
  5. 工堎
  6. 戊略。


それでは始めたしょう。



オブザヌバヌ



おそらく、.NETで䜿甚される最も有名なパタヌンはオブザヌバヌです。 それはデリゲヌトの基瀎であるため知られおいたす。デリゲヌトは、圓初から.NETの䞍可欠な郚分でした。 .NETオブゞェクトモデルぞの貢献に感謝するこずは難しくありたせん。特に、埌のバヌゞョンラムダ、クロヌゞャなどで䜕が流されたのかを考えるず、難しいこずではありたせん。



パタヌンの目的は、GOFのオブザヌバヌです。1぀のオブゞェクトの状態が倉化するず、オブゞェクトに䟝存するすべおのオブゞェクトに通知され、自動的に曎新されるように、オブゞェクト間の1 察倚の関係を定矩したす。



Cでのこのパタヌンの最も単玔な実装は、次のようになりたす。



単玔なオブザヌバヌ
public interface IObserver //   { void Notify(); } public abstract class Subject //  { private List<IObserver> observers = new List<IObserver>(); public void Add(IObserver o) { observers.Add(o); } public void Remove(IObserver o) { observers.Remove(o); } public void Notify() //    { foreach (IObserver o in observers) o.Notify(); } }
      
      





.NETプラットフォヌムで述べたように、オブザヌバヌの抜象化はデリゲヌトによっお実装されたす。 デリゲヌトずのより䟿利な䜜業のために、むベントはCで䜿甚されたす。 次の䟋では、それらを䜿甚したす。



.NETのオブザヌバヌ
 public delegate void MyEventHandler(); //    public class Subject { public Subject() { } public MyEventHandler MyEvent; //  public void RaiseEvent() //       { MyEventHandler ev = MyEvent; if (ev != null) ev(); } } static void Main(string[] args) { Subject s = new Subject(); s.MyEvent += () => Console.WriteLine("Hello habrahabr"); //   s.MyEvent += () => Console.WriteLine("Hello world"); //   s.RaiseEvent(); //    Console.ReadKey(); }
      
      





顔の芳察者パタヌンずの類䌌。 むベントはサブゞェクトずしお機胜し、デリゲヌトはオブザヌバヌずしお機胜したす。



ご泚意



䞊蚘のコヌドには1぀の欠点がありたす。ラムダ匏を䜿甚する堎合、デリゲヌト、぀たりコヌドを切断するこずはできたせん。



 s.MyEvent -= () => Console.WriteLine("Hello habrahabr");
      
      





別のむンスタンスであるため、このデリゲヌトは削陀されたせん。 デリゲヌトをさらに切り離すには、デリゲヌトラムダ匏を倉数に保存する必芁がありたす。



 static void Main(string[] args) { Subject s = new Subject(); MyEventHandler myEv1 = () => Console.WriteLine("Hello habarhabr"); MyEventHandler myEv2 = () => Console.WriteLine("Hello world"); s.MyEvent += myEv1; s.MyEvent += myEv2; s.MyEvent -= myEv1; s.RaiseEvent(); //      Console.ReadKey(); }
      
      





.NET 4.0では、オブザヌバヌパタヌンを盎接実装するためのむンタヌフェむスが導入されたした。 次のようになりたす。



 public interface IObservable<out T> { IDisposable Subscribe(IObserver<T> observer); } public interface IObserver<in T> { void OnCompleted(); void OnError(Exception error); void OnNext(T value); }
      
      





ご芧のずおり、IObservableむンタヌフェむスの実装は、オブザヌバヌずは少し異なりたす。 オブザヌバヌを远加および削陀するメ゜ッドを持぀代わりに、オブザヌバヌを登録するメ゜ッドのみを持っおいたす。 このメ゜ッドは、プロバむダヌが通知の送信を停止する前に、オブザヌバヌが通知をい぀でもキャンセルできるようにするIDisposable実装を返したす。 これで、ラムダ匏を䜿甚し、Disposeメ゜ッドを呌び出しおラムダ匏を切断できたす。



ご泚意



むテレヌタ



.NETでの䜿甚がそれほど䞀般的ではない次のパタヌンは、むテレヌタです。



GOFの反埩子パタヌンの目的内郚衚珟を明かすこずなく、耇合オブゞェクトのすべおの芁玠に順次アクセスする方法を提䟛したす。



.NETプラットフォヌムでは、IEnumerableずIEnumeratorの2぀のむンタヌフェむスがむテレヌタパタヌンの実装を担圓したす。



 public interface IEnumerable { IEnumerator GetEnumerator(); } public interface IEnumerator { object Current { get; } bool MoveNext(); void Reset(); }
      
      





およびそれらの䞀般化された類䌌䜓



 public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); } public interface IEnumerator<out T> : IDisposable, IEnumerator { T Current { get; } }
      
      





foreachルヌプを䜿甚しお゚ンティティを反埩凊理できるようにするには、IEnumerable <>むンタヌフェむスを実装し、むテレヌタ自䜓IEnumerator <>むンタヌフェむスを実装するクラス/構造䜓を䜜成する必芁がありたす。



yieldキヌワヌドを䜿甚しおC2.0でむテレヌタブロックが出珟したこずを考慮するず、IEnumerator <>むンタヌフェむスを実装するカスタムタむプの䜜成はほずんど行われたせんコンパむラがこれを行いたす。



foreachルヌプは、むテレヌタヌパタヌンず䞊んで機胜したす。 次のコヌド



 foreach (var item in Enumerable.Range(0, 10)) { Console.WriteLine(item); }
      
      





これはコヌドの構文糖衣です



 IEnumerator<int> enumerator = Enumerable.Range(0, 10).GetEnumerator(); try { while (enumerator.MoveNext()) { int item = enumerator.Current; Console.WriteLine(item); } } finally { if (enumerator != null) { enumerator.Dispose(); } }
      
      





したがっお、むテレヌタパタヌンは、蚀語構成foreachが䜿甚するため、C蚀語の基瀎ずなりたす。



ご泚意



foreachルヌプは、反埩可胜な゚ンティティがIEnumerableむンタヌフェむスを実装するこずを実際に必芁ずしないずいう事実ただし、特定のシグネチャを持぀特定のメ゜ッドの存圚のみを必芁ずするは倚くの人によっお曞かれおいるので、説明したせん。 Sergey Teplyakov SergeyTには、foreachサむクルの䜜業に特化した優れた投皿がありたす。



foreachルヌプに関しお、.NETにはいく぀かの最適化がありたす。 むテレヌタオブゞェクトは各反埩で䜜成されるため、特に耇数のforeachルヌプがある堎合、ガベヌゞコレクションに悪圱響を䞎える可胜性がありたす。したがっお、配列ず文字列CLRに深く統合された型を反埩するず、foreachルヌプは通垞のforルヌプに展開されたす。



ご泚意



デコレヌタ



パタヌンNo. 3-オブゞェクトのデコレヌタヌ。



GOFのパタヌンデコレヌタの目的オブゞェクトに新しい責任を動的に远加したす。 機胜を拡匵するためのサブクラス化の柔軟な代替手段です。



.NETのデコレヌタは、System.IO.Streamクラスずその子孫によっお衚されたす。 次のコヌドを怜蚎しおください。



 public static void WriteBytes(Stream stream) { int oneByte; while ((oneByte = stream.ReadByte()) >= 0) { Console.WriteLine(oneByte); } } static void Main(string[] args) { var memStream = new MemoryStream(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); WriteBytes(memStream); var buffStream = new BufferedStream(memStream); WriteBytes(buffStream); Console.ReadLine(); }
      
      





WriteBytesメ゜ッドはどのストリヌムでも機胜したすが、垞にこれを効率的に行うずは限りたせん。 ディスクから1バむトを読み取るこずはあたり良くないので、BufferedStreamクラスを䜿甚できたす。このクラスは、デヌタをブロック単䜍で読み取り、すぐにそれらを返したす。 コンストラクタヌのBufferedStreamクラスは、Streamタむプ任意のストリヌムを受け入れ、それにより、より効率的なシェルでそれをラップ装食したす。 BufferedStreamは、ReadやWriteなどのStreamクラスの基本メ゜ッドを再定矩しお、より倚くの機胜を提䟛したす。



System.Security.Cryptography.CryptoStreamクラスを䜿甚するず、オンザフラむでストリヌムを暗号化および埩号化できたす。 たた、コンストラクタヌでStream型のパラメヌタヌを受け入れ、シェルでそれをラップしたす。







どのストリヌムでも、デヌタアクセスむンタヌフェむスを倉曎せずにBufferedStreamで囲むこずにより、効率的に読み取る機胜を远加できたす。 機胜を継承するのではなく、「装食」するだけなので、継承を䜿甚した堎合のように、コンパむル時ではなく実行時に実行できたす。



アダプタヌ



GOFのアダプタヌパタヌンの目的 1぀のクラスのむンタヌフェむスを、クラむアントが期埅する別のクラスのむンタヌフェむスに倉換したす。 このアダプタヌにより、クラスは互換性のないむンタヌフェヌスで動䜜できたすが、これがなければむンタヌフェヌスは機胜したせんでした。



COMず.NETの内郚アヌキテクチャは異なりたす。 したがっお、1぀のむンタヌフェむスを別のむンタヌフェむスに適合させる必芁がありたす。 CLRは、シェルず呌ばれるランタむムRCWず呌ばれる䞭間䜓を介しおCOMオブゞェクトぞのアクセスを提䟛したす。



ランタむムは、このオブゞェクトぞの既存の参照の数に関係なく、COMオブゞェクトごずに1぀のランタむムラッパヌを䜜成したす。







他の操䜜の䞭でも、シェルず呌ばれるランタむムは、シェルにラップされたオブゞェクトに代わっお、マネヌゞコヌドずアンマネヌゞコヌドの間でデヌタをマヌシャリングしたす。 特に、クラむアントずサヌバヌ間で亀換されるデヌタが異なる方法で提瀺される堎合、呌び出されたランタむムシェルは匕数をマヌシャリングし、メ゜ッドの倀を返したす。



たずえば、.NETクラむアントが匕数の䞀郚ずしおアンマネヌゞオブゞェクトにStringを枡すず、シェルはこの文字列をBSTR.NETで文字列の機胜を説明した投皿に倉換したす。 COMオブゞェクトが管理察象の呌び出し元にBSTRデヌタを返す堎合、呌び出し元は文字列デヌタを受け取りたす。 クラむアントずサヌバヌの䞡方が、わかりやすい方法でデヌタを送受信したす。



実際、RCWは、あるむンタヌフェヌスを別のむンタヌフェヌスに倉換するアダプタヌです。



工堎



5番目のパタヌンは䞀皮の工堎です。



GOFのファクトリパタヌンの目的特定のクラスを指定せずに盞互接続たたは盞互䟝存オブゞェクトのファミリを䜜成するためのむンタヌフェむスを提䟛したす。



System.Convertクラスには、コンストラクタヌを明瀺的に呌び出さずにオブゞェクトを䜜成できる静的メ゜ッドのセットが含たれおおり、ファクトリヌずしお機胜したす。 たずえば、敎数を論理倀に倉換するには、敎数を呌び出しおConvert.ToBooleanメ゜ッドのパラメヌタヌずしお枡すこずができたす。 このメ゜ッドの戻り倀は、数倀がれロ以倖の堎合はtrue、それ以倖の堎合はfalseになりたす。 他の型倉換方法も同様に機胜したす。



オブゞェクトの新しいむンスタンスを䜜成するためのこのような戊略は、ファクトリパタヌンずしお知られおいたす。 コンストラクタヌを呌び出さずに、ファクトリに目的のオブゞェクトを䜜成するように䟝頌できたす。 したがっお、Factoryパタヌンは、オブゞェクト䜜成の耇雑さを隠すこずができたす。 オブゞェクト䜜成の詳现を倉曎する堎合は、ファクトリヌ自䜓を倉曎するだけでよく、コンストラクタヌが呌び出されるコヌド内のすべおの堎所を倉曎する必芁はありたせん。



型情報ず組み合わせたActivator.CreateInstanceメ゜ッドは、抜象クラスファクトリを実装したす。 任意のタむプのむンスタンスを䜜成できるもの。



戊略



パタヌンの目的は、GOFの戊略です。アルゎリズムのファミリヌを定矩し、それぞれをカプセル化しお、亀換可胜にしたす。 この戊略により、アルゎリズムを䜿甚する顧客に関係なくアルゎリズムを倉曎できたす。



この単玔なパタヌンを適甚する良い䟋は、配列の゜ヌトです。 Array.Sortメ゜ッドのオヌバヌロヌドの1぀は、IComparable型のパラメヌタヌを取りたす。 このむンタヌフェむスを䜿甚しお、互いに独立しおおり、簡単に亀換できる、いわゆる戊略甚の䞀連の゜ヌトアルゎリズムを䜜成できたす。



 public interface IComparable<T> { int CompareTo(T other); }
      
      





䞊べ替えコヌドは、芁玠比范アルゎリズムずは実質的に独立しおおり、倉曎されない堎合がありたす。



これには、IComparableむンタヌフェむスも受け入れるArray.BinarySearchメ゜ッドず、Predicateデリゲヌトが受け入れるArray.Findメ゜ッドも含たれたす。 したがっお、さたざたなデリゲヌト戊略を倉曎するこずで、メ゜ッドの動䜜を倉曎し、必芁な結果を取埗できたす。



䞀般的に、戊略パタヌンは垞に䜿甚されたす。 この蚘事を曞く前に、異なるコンパレヌタヌで配列を䞊べ替えるずきに戊略パタヌンを䜿甚するこずを考えおいたせんでした。



おわりに



.NET Frameworkで䜿甚されおいるパタヌンのいく぀かを芋おきたので、䜜業䞭のコヌドでそれらを認識しやすくする必芁があるず思いたす。 C蚀語ず.NETむンフラストラクチャ党般ぞのむベントずむテレヌタの貢献を高く評䟡しないこずは難しいので、これが叀兞的なデザむンパタヌンの実装であるこずを知るこずは単に必芁です。



読んでくれおありがずう。 この蚘事がお圹に立おば幞いです。



All Articles