0

要約すると

オブジェクト A には、例外をスローするメソッド { m1、m2、... } があります。検証後、これらのメソッドのいくつかはもはや知られなくなりthrowます。OO でこの検証段階の進行をモデル化します。チェックが実行され、肯定的な結果が返されると、オブジェクトはそのメソッドの信頼性についてより高いレベルの信頼に「昇格」され、例外チェックはクライアントに強制されなくなります。



フルバージョン:

このデザインの選択を建設的に批判していただけませんか。


  • インターフェイスは、オブジェクトの「クラス」の機能を記述します。そのようなオブジェクトの 1 つに「なる」には、インスタンスがインターフェイス プロトコルをサポートしていれば十分です。インターフェイス プロトコルは、特定の数のことを「試す」機能を確立するだけです (例外をスローするメソッドを実装する)。

  • 上記の例の単純化された存在形態は、すべての操作を試すことができる段階ですが、それらのかなりの数で失敗が予想される段階です (教育を受けていない野蛮人は人間であり、すべてのタスクに手を出す可能性があります)。 、しかし、誰もそれらの成功にあまり賭けません)

  • 基本インターフェースのサブタイプは同じ機能を保持しますが、高度な検証の完了を示すため、その操作の一部が常に成功することが保証されます。この「プロモーション」とキャストは、オブジェクトの順序を「アップレベル」するための内部状態と契約の前提条件の検査に従います。


対応するパターンまたは実際にアンチパターン(アイデアを採用するのを思いとどまらせたい場合) を教えてください。これは、検証の段階を経て進行するオブジェクトの同じ概念をキャプチャし、基本操作の信頼性に関する情報は、プログレッシブチェック?

操作はすべてベース インターフェイスにあり、関連するチェック済みの例外がありますが、メソッド シグネチャから例外が消えるサブタイプ (サブインターフェイス) が存在するインターフェイス階層を介してこれをモデル化しようとしています。

ここに投稿する前に Decorator パターンを検討しましたが、多くのレベルで、オブジェクトに「特定のポイントまで検証済み」として耳にタグを付けるという原則をモデル化できません。switchまた、検証ステージに関するメタデータ (Enum?) がオブジェクトに合成されて編集される「継承よりも合成」についても検討しました。

主な目標は次のとおりです。


  • オブジェクトの特定の「標本」について何も知られていない場合に、クライアントに例外をチェックさせる

  • オブジェクトが検証レイヤーに送信され、「チェック済みで動作中」として返されたときに、例外をチェックする責任からクライアントを解放します。高次インターフェースを使用するということは、「これは高速で安全に使用できる」ことを意味します。

  • オブジェクトをすぐに使用しようとするが、発生する可能性のある奇妙なケースを処理するか、オブジェクトのメソッドを呼び出そうとする前に検証の遅いデリゲートに転送するかをクライアントが選択できるようにします。もちろん、デリゲートはオブジェクトの高次インターフェイス キャストを返し、信頼性を示します。


デザインのジレンマが実際に私にとって最も重要であるにもかかわらず、ユーモラスな表現が続きます。


interface CivilisedMan extends Man {
  @Override
  void act();
}
interface Man {
  void act() throws UnreasonableBehaviourException;
}
class UnreasonableBehaviourException extends Exception {
  public UnreasonableBehaviourException(String embarrassingCircumstance) {
    super(embarrassingCircumstance);
  }
}
public class StackOverflow {
  public static void main(String[] args) {
    Man williamConnollyJr = new Man() {
      @Override
      public void act() throws UnreasonableBehaviourException {
        throw new UnreasonableBehaviourException("F*rt!");
      }
    };
    CivilisedMan harryPotter = new CivilisedMan() {
      @Override
      public void act() {
        System.out.println("Swish and flick.");
      }
    };
    try {
      williamConnollyJr.act();
    } catch (UnreasonableBehaviourException unreasonableBehaviourException) {
      System.out.println(unreasonableBehaviourException.getMessage());
    }
    harryPotter.act();
  }
}

このデザインを破棄して、必要に応じて最初からやり直すことは問題ありませんが、そのためには参照をバックアップする必要があります...

注: この行動パターンは、生活の中で頻繁に発生します。あなたは新しい物を手に取り、それについて何も知らず、それをどのように投げたり、渦を巻かせたり、変形させたりできるかについてほとんど期待していません。想定される各アクションに対してオブジェクトがどの程度適しているかについて...

4

3 に答える 3

1

すでに述べたように、例外ではないものに例外を使用するのは得策ではありません。私には、Guava のOptionalまたは独自の同様のクラスのようなものの明確なケースのように見えます。私はもう少し、多分何かのようなことができます

@lombok.RequiredArgsConstructor(access=AccessLevel.PRIVATE)
class Result {
    // the computed value if case of a success
    private final Value value;
    // the failure reason  if case of a failure
    private final Problem problem;

    public static success(Value value) {
        return new Result(checkNonNull(value), null);
    }
    public static fail(Problem problem) {
        return new Result(null, problem);
    }

    public boolean isSuccess() {
        return value!=null;
    }
    public Value value() {
        if (!isSuccess()) throw new SomeRuntimeException();
        return value;
    }
    public Value problem() {
        if (isSuccess()) throw new SomeRuntimeException();
        return problem;
    }
}

を返すことResultで、問題は解決されませんが、うまく回避されます。

  • Resultクライアントは例外をチェックしませんが、メソッドが直接使用できるものを返さないため、成功をチェックするのを忘れたことによるエラーは防止されvalueますisSuccess

  • これらのチェックは高速で簡単なので、クライアントを解放しようとはしません。本当に必要な場合は、クラス階層を作成し、Value actDirectly()(スーパークラスに加えて) サブクラスのようにメソッドを実装しますResult act()。しかし (既に述べたように)、これはクラスの爆発につながる可能性があり、ダウンキャストも読み取り可能なコードにはなりません。

  • 遅い検証と高速パスに関しては、これはクライアントから隠され、クラスによって内部的に処理され、検証結果などをキャッシュする必要があります。uncheckedクライアントの選択に関しては、あなたが何を意味するのかわかりませんが、メソッド(または追加のクラスなどのようなもっと凝ったもの)を提供して引数にすることができるかもしれません。


宣伝する価値はわかりませんが、もしそうなら、次のようなことをします

class Man {
    private Boolean isCivilized; // cache

    public boolean isCivilized() {
        if (isCivilized==null) isCivilized = isCivilizedInternal();
        return isCivilized; // unboxed
    }
    public Result act() {
        return isCivilized() ? newValue() : newProblem();
    }
    public CivilizedMan asCivilizedMan() throws UncivilizedException {
        if (!isCivilized()) throw new UncivilizedException();
        return new CivilizedMan(this);
    }
}

class CivilizedMan implements ManInterface {
    private final Man delegate;

    public Value actDirectly() {
        // just this, no optimizations and no additional logic here
        // as this all gets done in Man itself
        return delegate.act().value();

        // actually it could be
        // return delegate().newValue();
        // but that's something JIT can do instead of me
    }
}
于 2012-10-07T18:55:48.523 に答える
0

正直なところ、私はいくつかの理由であなたのデザインが好きではありません:

  • アクションが予期しない場所で例外を使用します。微妙な違いに注目してください。例外は、異常で不規則な動作を示すために使用する必要があります。不正な状態、間違った参照、欠落しているファイル。例外が定期的に頻繁に発生することが予想される場合は、別の構造を使用してください。

  • 安全でないオブジェクトと検証済みのオブジェクトの違いを示していません。CivilisedMan検証されたかどうかを実装するオブジェクトのインスタンスを持っている場合、どうすればわかりますか? 検証済みレベルへの「昇格」といくつかの特別なサブインターフェースについて言及しています。これは、インターフェースの数を 2 倍にする必要があり、多くの重複が発生することを意味します。

  • チェック例外の使用。それらを避けてください。実際にあなたの場合、それらは期待されているので良いですが、例外を期待すべきではありません(それらは...予期しないものです)


では、代わりにどのデザインをお勧めしますか? 昔ながらのやり方でやってください。インターフェースは「試す」能力を示すべきではありません - それは能力を示すべきです。オブジェクトが を実装している場合CivilisedMan、それは文明人です。そうでなければ、それは実行できず、act()文明化されていません。限目。

Object williamConnollyJr = //...
Object harryPotter = //...
if(williamConnollyJr instanceof CivilisedMan) {
    ((CivilisedMan)williamConnollyJr).act();
} else {
  System.out.println(unreasonableBehaviourException.getMessage());
}
if(harryPotter instanceof CivilisedMan) {
    ((CivilisedMan)harryPotter).act();
}

利点:

  • ダウンキャストにもかかわらず、よりクリーンではるかに読みやすい
  • 検証/昇格は自動です - からObjectにチェックしてダウンキャストするCivilisedManと、オブジェクトが検証されます。
  • 必要に応じて、デコレータパターンを使用して以前のデザインに戻すことができます
  • を使用して、さまざまな機能を動的に構成できますjava.lang.reflect.Proxy

短所

  • instanceofそしてObject使われている
  • あまり動的ではなく、オブジェクトがCivilisedMan実行時ではなくコンパイル時に決定される必要があるかどうか
于 2012-10-07T14:56:49.717 に答える
0

私見、あなたの道を行くのは苦痛です。

Man に 3 つのメソッドがあり、それぞれが 3 つの例外をスローすると仮定しないでください...メソッドごとに 8 (2^4) のバリエーションが必要です (スローしない、1 つをスローする、2 つをスローする、...)。したがって、24 (3*8) の特殊なインターフェイスが必要になります。

Java では、オーバーヘッドが大きくなければ昇格自体ができません。

私は戦略を試してみて、それが十分でない場合は、その上に責任の連鎖のいくつかのバリエーションを追加します.

したがって、次のようになります。

class UnreasonableBehaviourException extends RuntimeException {}

interface Man {
  boolean canAct();
  void act();
}

誰かがメソッドを使用したい場合、彼はそれを行うためのクリーンな方法を持っています。彼が気にしないなら、それは彼の問題です。

Man man = new Man() {
  ...
}

if(man.canAct()) man.act();

インターフェイスが単純な場合 (メソッドが多くない場合)、ここで終了します。インターフェースが複雑な場合:

interface SomethingToDo {
  public void act(Man man);
}

class CanAct {
  private final Man man;
  private SomethingToDo next;

  public CanAct(SomethingToDo next) {
    this.next = next;
  }

  public void act(Man man) {
    if(man.canAct()) {
      man.act();
    }
    if(next == null) return;
    next.act();
  }
}

class MustAct {
  private SomethingToDo next;


  public CanAct(SomethingToDo next) {
    this.next = next;
  }

  public void act(Man man) {
    if(man.canAct()) {
      man.act();
      if(next == null) return;
      next.act();
    }
    else {
      ... some error handling ...
    }
  }
}

このサンプルを拡張すると、実行するアクションを作成できます。

何かToDo toBeDone = new MustDo1(new MustDo2(new CanDo3(new MustDo2(null))));

toBeDone.act(new SavageMan()) <- will fail due to something
toBeDone.act(new Nobleman())  <- will succeed

長所

  • アップキャスティングはテスト能力によって行われます
  • 複雑なアクションを定義することができます (彼がうまくできて、他のことができない場合)
  • 人間ができるべきことを読みやすい
  • SomethingToDo.act のクリーンでシンプルな実装
  • プロモーションは自動的に行われ、アクションを完了できるかできないかを示します。
  • すべての可能性について考える必要はありません。ユーザーがこれを行います (ユーザーはインターフェイスの SomethingToDo を実装します)。
  • ユーザーが例外によって追跡されるかどうかは、あなた/彼らの決定です (SomethingToDo の実装がどのように動作するか)

短所

  • 特にロールバックが必要な場合は、エラー処理が複雑になる可能性があります。
  • SomethingToDo を実装するクラスを大量に生成できる
  • Man インターフェイスの各メソッドの追加メソッド
于 2012-10-07T16:11:06.017 に答える