特定の順序で関数を呼び出す必要があるインターフェース設計


24

タスクは、入力仕様に従って、デバイス内のハードウェアを構成することです。これは次のように達成する必要があります。

1)構成情報を収集します。これは、さまざまな時間と場所で発生する可能性があります。たとえば、モジュールAとモジュールBは両方とも(異なる時間に)私のモジュールにリソースを要求できます。これらの「リソース」は、実際には構成です。

2)これ以上要求が実現されないことが明らかになった後、要求されたリソースの要約を提供する起動コマンドをハードウェアに送信する必要があります。

3)その後のみ、前述のリソースの詳細な構成を行うことができます(およびする必要があります)。

4)また、2)の後でのみ、選択したリソースの宣言された呼び出し元へのルーティングを行うことができます(およびする必要があります)。


バグの一般的な原因は、それを書いた私でさえ、この順序を間違えていることです。コードを初めて見る人がインターフェイスを使用できるようにするために、どのような命名規則、設計、またはメカニズムを使用できますか?


ステージ1は、より良いと呼ばれていますdiscoveryhandshake
rwong

1
時間的結合はアンチパターンであり、避けるべきです。

1
質問のタイトルから、ステップビルダーパターンに興味があるかもしれません。
ジョシュアテイラー14

回答:


45

再設計ですが、多くのAPIの誤用を防ぐことができますが、呼び出すべきではないメソッドは使用できません。

たとえば、代わりに first you init, then you start, then you stop

コンストラクターは、init開始可能なオブジェクトをstart作成し、停止可能なセッションを作成します。

もちろん、一度に1つのセッションに制限がある場合は、誰かがすでにアクティブなセッションを作成しようとする場合に対処する必要があります。

次に、この手法を自分のケースに適用します。


zlibそしてjpeglib、初期化のためにこのパターンに従う2つの例です。それでも、開発者に概念を教えるには、多くのドキュメントが必要です。
rwong

5
これはまさに正解です。順序が重要な場合、各関数は次のステップを実行するために呼び出すことができる結果を返します。コンパイラー自体が設計制約を実施できます。

2
これは、ステップビルダーパターンに似ています。特定のフェーズで意味のあるインターフェースのみを提示します。
ジョシュアテイラー14

@JoshuaTaylor私の答えは、ステップビルダーパターンの実装です:)
Silviu Burcea 14

@SilviuBurcea あなたの答えはステップビルダーの実装ではありませんが、ここではなくコメントします。
ジョシュアテイラー14

19

スタートアップメソッドが、構成の必須パラメーターであるオブジェクトを返すようにすることができます。

リソース* MyModule :: GetResource();
MySession * MyModule :: Startup();
void Resource :: Configure(MySession * session);

あなたのMySession構造体が空の場合でも、型の安全性によりConfigure()、起動前にメソッドを呼び出すことはできません。


誰かがすることを止めるものは何module->GetResource()->Configure(nullptr)ですか?
svick 14

@svick:何もありませんが、明示的にこれを行う必要があります。このアプローチは、何を期待しているのかを示し、その期待を回避することは意識的な決定です。ほとんどのプログラミング言語と同様に、誰もあなたが足を踏み入れるのを妨げません。ただし、APIを使用すると、そうすることを明確に示すことは常に良いことです;)
マイケルクレメント14年

+1は素晴らしくシンプルです。しかし、私は問題を見ることができます。私はオブジェクトを持っている場合はa, b, c, d、その後、私が開始することができa、それを使用していMySession使おうとするb現実にはそうではないが、すでに開始オブジェクトとして。
ヴォラック14年

8

Cashcowの答えに基づいて-新しいインターフェイスを提示するだけで、呼び出し元に新しいオブジェクトを提示する必要があるのはなぜですか?ブランド変更パターン:

class IStartable     { public: virtual IRunnable      start()     = 0; };
class IRunnable      { public: virtual ITerminateable run()       = 0; };
class ITerminateable { public: virtual void           terminate() = 0; };

セッションを複数回実行できる場合は、ITerminateableにIRunnableを実装させることもできます。

あなたのオブジェクト:

class Service : IStartable, IRunnable, ITerminateable
{
  public:
    IRunnable      start()     { ...; return this; }
    ITerminateable run()       { ...; return this; }
    void           terminate() { ...; }
}

// And use it like this:
IStartable myService = Service();

// Now you can only call start() via the interface
IRunnable configuredService = myService.start();

// Now you can also call run(), because it is wrapped in the new interface...

この方法では、最初にIStartable-Interfaceだけがあり、start()を呼び出したときにのみアクセス可能なrun()メソッドを取得するため、正しいメソッドのみを呼び出すことができます。外部からは、複数のクラスとオブジェクトを持つパターンのように見えますが、基礎となるクラスは常に参照される1つのクラスのままです。


1
複数のクラスではなく、1つの基本クラスだけを持つことの利点は何ですか?これが私が提案した解決策との唯一の違いであるため、この特定の点に興味があります。
マイケルルバルビエグリューネ

1
@MichaelGrünewaldすべてのインターフェイスを1つのクラスで実装する必要はありませんが、構成タイプのオブジェクトの場合、インターフェイスのインスタンス間でデータを共有するのが最も簡単な実装手法である可能性があります(つまり、同じであるため、オブジェクト)。
ジョシュアテイラー14


@JoshuaTaylorインターフェースのインスタンス間でデータを共有する方法は2つあります。実装は簡単かもしれませんが、「未定義の状態」にアクセスしないように注意する必要があります(接続されていないサーバーのクライアントアドレスにアクセスするなど)。OPはインターフェースの使いやすさに重点を置いているため、2つのアプローチが等しいと判断できます。「ステップビルダーパターン」BTWを引用してくれてありがとう。
マイケルルバルビエグリューネヴァルド14

1
@MichaelGrünewald特定のポイントで指定された特定のインターフェイスを介してのみオブジェクトと対話する場合、その状態にアクセスする方法(キャストなどなし)はありません。
ジョシュアテイラー14

2

問題を解決するための有効なアプローチはたくさんあります。Basile Starynkevitchは、「官僚主義のない」アプローチを提案しました。これは、シンプルなインターフェイスを提供し、インターフェイスを適切に使用するプログラマーに依存しています。私はこのアプローチが好きですが、もう1つ、より多くのeingineeringがありますが、コンパイラーがいくつかのエラーをキャッチできるようにします。

  1. お使いのデバイスをにすることができ、様々な状態を識別しUninitialisedStartedConfiguredのように。リストは有限でなければなりません。¹

  2. 各状態について、定義structなど、その状態に必要な追加情報は、関連する保持DeviceUninitialisedDeviceStartedそしてそうで。

  3. DeviceStrategyメソッドが入力および出力として定義された構造を使用する場合、すべての処理を1つのオブジェクトにパックします。したがって、DeviceStarted DeviceStrategy::start (DeviceUninitalised dev)メソッド(またはプロジェクトの慣習に応じたもの)を使用できます。

このアプローチでは、有効なプログラムはメソッドプロトタイプによって強制されるシーケンスでいくつかのメソッドを呼び出す必要があります。

さまざまな状態は無関係なオブジェクトです。これは、置換の原則によるものです。これらの構造に共通の祖先を共有させることが有用な場合は、訪問者パターンを使用して、抽象クラスのインスタンスの具体的なタイプを回復できることを思い出してください。

3.独自のDeviceStrategyクラスについて説明しましたが、提供する機能をいくつかのクラスに分割したい場合があります。

それらを要約すると、私が説明した設計の重要なポイントは次のとおりです。

  1. 置換の原則により、デバイスの状態を表すオブジェクトは区別され、特別な継承関係を持たないようにする必要があります。

  2. 各デバイスまたはデバイスの状態はそれ自体のみを表示し、戦略はそれらすべてを表示し、それらの間の可能な遷移を表現するように、デバイス自体を表すオブジェクトではなく、スターティジーオブジェクトにデバイス処理をパックします。

これらの行に続くtelnetクライアントの実装の説明を見たことがありますが、それを見つけることはできませんでした。それは非常に便利なリファレンスだったでしょう!

¹:このためには、直観に従うか、リレーション「method₁〜method₂iff」の実際の実装でメソッドの等価クラスを見つけます。同じオブジェクトでそれらを使用することは有効です」-デバイス上のすべてのトリートメントをカプセル化した大きなオブジェクトがあると仮定します。状態をリストする両方の方法は素晴らしい結果をもたらします。


1
個別の構造体を定義するのではなく、各フェーズのオブジェクトが提示する必要のある必要なインターフェイスを定義するだけで十分です。それがステップビルダーパターンです。
ジョシュアテイラー14

2

ビルダーパターンを使用します。

上記のすべての操作のメソッドを持つオブジェクトがあります。ただし、これらの操作はすぐには実行されません。後で使用するために各操作を記憶しているだけです。操作はすぐには実行されないため、ビルダーに渡す順序は重要ではありません。

ビルダーですべての操作を定義したら、execute-method を呼び出します。このメソッドが呼び出されると、上記で保存した操作を使用して、上記のすべてのステップが正しい順序で実行されます。このメソッドは、ハードウェアに書き込む前に、いくつかの操作にまたがる健全性チェック(まだセットアップされていないリソースを構成しようとするなど)を実行するのにも適しています。これにより、無意味な構成でハードウェアが損傷するのを防ぐことができます(ハードウェアがこの影響を受けやすい場合)。


1

インターフェースの使用方法を正しく文書化し、チュートリアルの例を提供するだけです。

また、ランタイムチェックを行うデバッグライブラリバリアントもあります。

いくつかの命名規則を定義し、おそらく、正しく文書化(例えばpreconfigure*startup*postconfigure*run*....)

ところで、既存のインターフェイスの多くは同様のパターンに従っています(X11ツールキットなど)。


情報を伝えるには、Androidアプリケーションのアクティビティライフサイクルと同様の状態遷移図が必要になる場合があります。
rwong 14

1

これは、コンパイラが構文条件のみを強制できるのに対し、クライアントプログラムが「文法的に」正確である必要があるため、これは確かに一般的で潜行性のエラーです。

残念ながら、命名規則はこの種のエラーに対してほぼ完全に無効です。人々に非文法的なことをしないように本当に促したい場合は、順不同の手順を実行できないように、前提条件の値で初期化する必要がある何らかの種類のコマンドオブジェクトを渡す必要があります。


このようなことを意味していますか?
ヴォラック14

1
public class Executor {

private Executor() {} // helper class

  public void execute(MyStepsRunnable r) {
    r.step1();
    r.step2();
    r.step3();
  }
}

interface MyStepsRunnable {

  void step1();
  void step2();
  void step3();
}

このパターンを使用すると、実装者がこの正確な順序で実行されることが確実になります。さらに一歩進んで、カスタム実行パスを使用してエグゼキューターをビルドするExecutorFactoryを作成できます。


別のコメントあなたは、この実装ビルダーステップと呼ばれるが、それはありません。MyStepsRunnableのインスタンスがある場合、step1の前にstep3を呼び出すことができます。ステップビルダーの実装は、ideone.com / UDECgYに沿ったものになります。アイデアは、step1を実行してstep2で何かを取得することです。したがって、正しい順序でメソッドを呼び出す必要があります。たとえば、stackoverflow.com / q / 17256627/1281433を参照してください。
ジョシュアテイラー14

保護されたメソッド(またはデフォルト)で抽象クラスに変換して、使用方法を制限できます。executorの使用を余儀なくされますが、現在の実装には1つまたは2つの欠陥があるかもしれません。
シルビウブルセア14

それはまだステップビルダーになりません。コードでは、ユーザーが異なるステップ間でコードを実行するためにできることは何もありません。アイデアは、コードをシーケンスするだけではありません(パブリックかプライベートか、またはカプセル化されているかどうかは関係ありません)。コードが示すように、これは単にを実行するのに十分簡単step1(); step2(); step3();です。ステップビルダーのポイントは、いくつかのステップを公開するAPIを提供し、それらが呼び出される順序を強制することです。プログラマーがステップ間で他のことを行うのを妨げるべきではありません。
ジョシュアテイラー14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.