契約インターフェースと継承





権力の暗黒面、つまり契約を使用する際の問題について話しましょう。 ライブラリの開発者が慎重に管理し、小さな蜂蜜の入った軟膏にこのようなかなり大きなハエを持ち込むかわいい小さなものについて。





エントリー。



従来のMS Contractsドキュメントを読んでいることを前提としているので、すぐにコントラクトプログラミング用のインターフェイスの仕様などの便利なことに移ります。



http://research.microsoft.com/en-us/projects/contracts/

http://download.microsoft.com/download/C/2/7/C2715F76-F56C-4D37-9231-EF8076B7EC13/userdoc.pdf



複数のパブリックフィールドまたはメソッドがある場合、インターフェイス内でこれらのパラメーターの仕様を指定すると、追加の記述なしで、実際のクラスに同様のコントラクトを使用できます。 契約を説明した後、それを便利な魔法の杖として使用できます。



たとえば、反対側の代表者を作成します。



public class TheDarkLord { public string Name { get; set; } private string SoName { get; set; } public TheDarkLord(string name, string soName) { this.Name = name; this.SoName = soName; } public string ModifyBaseName(string updatedName) { this.Name = updatedName; return this.Name; } }
      
      







将来の暗いパドバのために、記述されたクラスを基本クラスとして使用することが必要になる可能性があります。 そして、不必要なチェックに負担をかけないように、パブリックセクションの仕様を設定するとともに、チェックとプライベートを同時に補完します。



a)インターフェイスを説明する

b)契約仕様を組み込む抽象実装を作成することを忘れないでください

c)privateセクションのチェックをサポートしてメインクラスを拡張します



実装:



a)インターフェースと契約の実装へのリンク



  [ContractClass(typeof(ITheDarkLordContract))] public interface ITheDarkLord { string Name { get; set; } string ModifyBaseName(string updatedName); }
      
      







b)契約仕様



  [ContractClassFor(typeof(ITheDarkLord))] public abstract class ITheDarkLordContract : ITheDarkLord { public string Name { get { Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "[ITheDarkLordContract] Name of Dark Lord is null or empty."); return default(string); } set { Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(value), "[ITheDarkLordContract] Name of Dark Lord is null or empty."); } } public string ModifyBaseName(string updatedName) { Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(updatedName), "[ITheDarkLordContract.ModifyBaseName] The new name of Dark Lord is null or empty."); Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "[ITheDarkLordContract.ModifyBaseName] The updated name of Dark Lord is null or empty."); return default(string); } }
      
      







c)変更されたキークラス。インターフェイスとプライベートチェックをフックします。



  public class TheDarkLord : ITheDarkLord { public string Name { get; set; } private string SoName { get; set; } [ContractInvariantMethod] private void DarkLordVerification() { Contract.Invariant(!string.IsNullOrEmpty(SoName)); } public TheDarkLord(string name, string soName) { Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(name)); Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(soName)); this.Name = name; this.SoName = soName; } public string ModifyBaseName(string updatedName) { this.Name = updatedName; return this.Name; } }
      
      







プロットの陰謀 、これは最も予想外です...



この瞬間まですべてが順調でした。 次に、ステップ番号2を取ります-記述されたクラスを派生物として使用します:



  [ContractClass(typeof(ITheWhiteLordContract))] public interface ITheWhiteLord : ITheDarkLord { string UpdateAllNames(string name, string soName); } [ContractClassFor(typeof(ITheWhiteLord))] public abstract class ITheWhiteLordContract : ITheDarkLordContract, ITheWhiteLord { public string UpdateAllNames(string name, string soName) { return default(string); } }
      
      







すべてが美しいようです-彼らはインターフェースの相続人を作成し、新しい抽象クラスを完成させ、コンパイルしようとしました。 それから、まったく予想外に、彼らは契約のライブラリの回数の制限を思いつきました:Object ... Clap以外の人から契約クラスを継承することはできません-そして、契約インターフェースの継承の概念は崩壊しました...



さて、開発者は私たちに何を勧めますか? そして、彼らは素晴らしい方法で行動します:「抽象クラスを取得し、そのクラスに祖先のメソッドの仕様を記述します」...



  [ContractClass(typeof(ITheWhiteLordContract))] public interface ITheWhiteLord : ITheDarkLord { string UpdateAllNames(string name, string soName); } [ContractClassFor(typeof(ITheWhiteLord))] public abstract class ITheWhiteLordContract : ITheWhiteLord { public string Name { get; set; } public string ModifyBaseName(string updatedName) { return default(string); } public string UpdateAllNames(string name, string soName) { Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), "[ITheWhiteLordContract.UpdateAllNames] The new name of White Lord is null or empty."); Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), "[ITheWhiteLordContract.UpdateAllNames] The new soname of White Lord is null or empty."); Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "[ITheWhiteLordContract.UpdateAllNames] The updated name of White Lord is null or empty."); return default(string); } }
      
      







ここで、基本クラスに「ドラッグ」する必要がある10〜20個のフィールドがあることを想像してください。 階層は1つの層からではなく、クラスのグループを通じて継承される場合があります。 その後、提案された勧告はいじめに似ています。



何を得たの?



将来の注入のポイントとしてインターフェースを使用するバリアントは、すでに失敗しています。 コントラクトインターフェイスの継承を削除し、必要なオブジェクトへの呼び出しを実際のクラスとして記述する必要があります。



a)子孫の契約を指定します



  [ContractClass(typeof(ITheWhiteLordContract))] public interface ITheWhiteLord { string UpdateAllNames(string name, string soName); } [ContractClassFor(typeof(ITheWhiteLord))] public abstract class ITheWhiteLordContract : ITheWhiteLord { public string UpdateAllNames(string name, string soName) { Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), "[ITheWhiteLordContract.UpdateAllNames] The new name of White Lord is null or empty."); Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), "[ITheWhiteLordContract.UpdateAllNames] The new soname of White Lord is null or empty."); Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "[ITheWhiteLordContract.UpdateAllNames] The updated name of White Lord is null or empty."); return default(string); } }
      
      







b)実際のクラスの継承を変更する



  public class TheWhiteLord : TheDarkLord, ITheWhiteLord { public string UpdateAllNames(string name, string soName) { return this.Name; } }
      
      







そして、インターフェースを使用する代わりに、クラス仕様へのアクセスを使用します。



だった



  public class SpaceSheep { public ITheDarkLord AbstractLord { get; set; } }
      
      







クラス実装



  public class SpaceSheep { public TheDarkLord AbstractLord { get; set; } }
      
      







システムの操作性の観点からは、何も失われていません。 TheWhiteLordクラスがある場合、必要なプロパティへのアクセスを取得して、ダークサイドから継承するのは簡単です。 同時に、IOCで動作する使用済みフレームワークの操作性は変わりません。 実際、インターフェイス、抽象クラス、または実際のものなど、依存関係を指定するための実装を実行できます。



しかし、堆積物は残った。 Contracts.Libraryの作成者に干渉して、有効なパブリックプロパティをチェックするだけでなく、一貫性のある継承アーキテクチャを構築するためのインターフェイスを操作する機会を与えてくれた人 悲しいかな...



おやつに。



覚えておくのが望ましいもう一つの小さな不便。 ContractInvariantMethodによるプライベートプロパティの特殊化には、わずかな制限があります。 不変メソッドで解析されるフィールドは、コンストラクターで指定する必要があります。 クラスは次のとおりです。



  public class TheDarkLord : ITheDarkLord { public string Name { get; set; } private string SoName { get; set; } [ContractInvariantMethod] private void DarkLordVerification() { Contract.Invariant(!string.IsNullOrEmpty(SoName)); } ... }
      
      







作成の例を次に示します。



  TheDarkLord lord = new TheDarkLord("James", "Bond");
      
      







空のデフォルトコンストラクターを追加すると、警告が表示されます。 コンストラクターを終了した後の不変メソッドは条件をチェックして例外を返すためです。



  CodeContracts: invariant is false: !string.IsNullOrEmpty(SoName)
      
      







また、「遅延」割り当てによってクラスプロパティを初期化するかなり便利な方法は機能しません。



  TheDarkLord secondLord = new TheDarkLord() { Name = "James" };
      
      







追加の変数を使用してプライベートプロパティを書き換え、契約の助けを借りて不変条件を破棄することで確認できますが、これによりコードの量が増加し、かなり強力で便利な事後条件検証ツールを使用できなくなります...



PS 私は契約によって表示されるメッセージについてもst音を立てません。 コールスタック逆アセンブリハンドラーと名前の生成を自動的に追加するのをやめているようです。 しかし、作成者は静的な行のみを許可し、この種のコードを置き換える機会を奪います



  Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), "[ITheWhiteLordContract.UpdateAllNames] The new name of White Lord is null or empty.");
      
      







似たようなもの



  Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name), string.Format("{0} The new name of White Lord is null or empty.",GetCurrentMethod()));
      
      






All Articles