設計の「におい」:一時的な接続

これは、 ポカヨケ設計に関するシリーズの最初の投稿です。 カプセル化とも呼ばれます



APIの設計でよく知られている問題は、クライアントからの正しい呼び出しシーケンスを必要とする2つ以上のメンバー間の隠された関係がクラスに含まれている場合に取得される一時的な接続です。 これは、時間の経過とともにクラスのメンバーを密接に結び付けます。



典型的な例は、Initializeメソッドの使用ですが、BCL(FCL)など、さらに多くの例があります。 BCLの例として、 EndpointAddressBuilderクラスの次の使用はコンパイルされますが、実行時にクラッシュします。

var b = new EndpointAddressBuilder(); var e = b.ToEndpointAddress();
      
      





EndpointAddressを構築するには、少なくともURIを提供する必要があることがわかります。 次の例は、正常にコンパイルおよび実行されます。

 var b = new EndpointAddressBuilder(); b.Uri = new UriBuilder().Uri; var e = b.ToEndpointAddress();
      
      





APIは、URIを使用する必要があるというヒントを提供しません。 ここでは、URIプロパティの設定とToEndpointAddressメソッドの呼び出しの間に一時的な接続が表示されます。

次に、より完全な例を見て、Poka-yoke向けのAPIの改善に関するガイダンスを提供します。



「におい」の例。

この例では、Smellクラスに示されているより抽象的な「匂い」について説明します。 オープンAPIは次のようになります。

 public class Smell { public void Initialize(string name) public string Spread() }
      
      





意味的に、Initializeメソッドの名前がキーですが、構造レベルでは、このAPIは一時的な接続の存在を示しません。 したがって、次のコードはコンパイルされますが、実行中にクラッシュします。

 var s = new Smell(); var n = s.Spread();
      
      





Smellオブジェクトが名前で初期化されていないため、SpreadメソッドがInvalidOperationExceptionをスローすることがわかりました。 Smellクラスの問題は、不変式を正しく保護できないことです。 言い換えれば、カプセル化が壊れています。

この問題を解決するには、Spreadメソッドを呼び出す前に、Initializeメソッドを呼び出す必要があります。

 var sut = new Smell(); sut.Initialize("   "); var n = sut.Spread();
      
      





Smellクラスの動作を調べることができる単体テストを作成できる場合は、デザインでコンパイル段階でフィードバックを得ることができればさらに良いでしょう



修正:コンストラクターを介した注入

カプセル化では、クラスが競合状態にならないようにする必要があります。 名前がSmellクラスに必要であることを考えると、その存在の保証をクラスに組み込む必要があります。 名前にデフォルト値を提供できない場合は、Smellクラスのコンストラクターで名前を要求する必要があります。

 public class Fragrance : IFragrance { private readonly string name; public Fragrance(string name) { if (name == null) { throw new ArgumentNullException("name"); } this.name = name; } public string Spread() { return this.name; } }
      
      





これは、クラスのすべてのインスタンスで名前が常に使用可能であるという効果的な保証です。 他の肯定的な効果もここにあります:



ただし、クラスの元のバージョンが一時的な接続を引き起こすインターフェースを実装することが時々起こります。 以下に例を示します。

 public interface ISmell { void Initialize(string name); string Spread(); }
      
      





多くの場合、注入される値は実行時まで不明のままであり、この場合、コンストラクターの単純な使用はいくぶん禁止されているように思われます 。結局のところ、 コンストラクターは実装の詳細であり、疎結合APIの一部ではありません 。 インターフェイスレベルでプログラミングする場合、コンストラクターを呼び出すことはできません。

この問題の解決策もあります。



修正:抽象ファクトリー

ISmellインターフェイスでメソッドを分離するには(ハハ)、Initializeメソッドを新しいインターフェイスに移動できます。 (一貫性のない)クラスの状態を変更する代わりに、Create(以前のInitialize)メソッドはIFragranceインターフェイスの新しいインスタンスを返します。

 public interface IFragranceFactory { IFragrance Create(string name); }
      
      





簡単な実装:

 public class FragranceFactory : IFragranceFactory { public IFragrance Create(string name) { if (name == null) { throw new ArgumentNullException("name"); } return new Fragrance(name); } }
      
      





FragranceFactoryとFragranceの両方のクラスが不変式を保護するため、これによりカプセル化が提供されます。 矛盾した状態になることはありません。 以前にISmellインターフェイスと対話していたクライアントは、IFragranceFactoryとIFragranceの組み合わせを使用して同じ機能を取得できるようになりました。

 var f = factory.Create(name); var n = f.Spread();
      
      





APIの誤用は実行時ではなくコンパイル時に決定されるため、これは非常に優れています。 静的に宣言された相互作用構造に向かって進む際の興味深い副作用は、クラスが不変になる傾向があることです。 不変クラスは自動的にスレッドセーフになります。これは、マルチコアの新しい(比較的)時代にますます重要な品質です。



All Articles