0

总结

对象 A 具有抛出异常的方法 { m1, m2, ... };验证后,其中一些方法将不再为人所知throw。在 OO 中对验证阶段的这种进展进行建模,其中,当检查运行并返回肯定结果时,对象被“提升”到对其方法的可靠性更高的置信度,并且不再对客户端进行异常检查



完整版

您能否建设性地批评这种设计选择:


  • 接口描述对象“类”的能力。要“成为”一个这样的对象,实例支持接口协议就足够了,该协议仅建立“尝试”一定数量的事物的能力(实现抛出异常的方法)

  • 上述例子的一个简单的存在形式是所有操作都可以尝试但预计会有很多失败的阶段(未受过教育的野蛮人是一个人,因为他可以尝试人类的所有任务,但没有人会在他们中的任何一个的成功上打赌)

  • 基本接口的子类型保持相同的功能,但表示高级验证的完成,以便保证其某些操作始终成功。这种“提升”和演员阵容遵循对内部状态和合同先决条件的检查,以“升级”对象的顺序


您能否指出相应的模式或反模式(如果您想阻止我采用这个想法),它们捕获了通过验证阶段进行的对象的相同概念,有关基本操作可靠性的信息随着渐进式检查?

我试图通过接口层次结构对此进行建模,其中操作都在具有相关检查异常的基本接口中,但存在异常从方法签名中消失的子类型(子接口)。

在这里发布之前,我考虑了装饰器模式,但它在许多层面上都无法将耳朵标记对象的原理建模为“在某一点上得到验证”。我还考虑了“组合而不是继承”,其中有关验证阶段的元数据(枚举?)被组合到对象中并switch继续编辑。

主要目标是:


  • 当对对象的给定“样本”一无所知时,让客户检查异常

  • 当对象已发送到验证层并以“已检查且正常工作”的形式返回时,客户不必承担检查异常的责任。使用高阶接口意味着:“这使用起来既快速又安全”

  • 允许客户选择立即尝试使用该对象,但处理可能发生的奇怪情况,或者在尝试调用其方法之前将其转发给验证缓慢的委托。当然,委托返回对象的高阶接口转换以表示可靠性


接下来是幽默的演绎,即使设计困境对我来说实际上是最重要的:


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它是否经过验证?您提到了“升级”到经过验证的级别和一些特殊的子界面。这意味着您将不得不将接口数量增加一倍,从而引入大量重复。

  • 使用检查的异常。避开他们。实际上,在您的情况下,它们很好,因为它们是预期的-但您不应该期望异常(它们是……意外)


那么我推荐什么设计呢?就用老办法吧。界面不应该表明“尝试”的能力——它应该表明能力。如果你的对象实现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验证/提升是自动的 - 一旦您检查并从to向下转换CivilisedMan,对象就会被验证。
  • 如果需要,您可以使用装饰器模式回退到以前的设计
  • 您可以使用动态组合不同的功能java.lang.reflect.Proxy

缺点

  • instanceof并被Object使用
  • 动态性较低,对象是否CivilisedMan存在必须在编译时确定,而不是在运行时
于 2012-10-07T14:56:49.717 回答
0

恕我直言,走你的路会很痛苦。

以免假设 Man 有 3 种方法,每种方法都会引发 3 个异常……对于每种方法,您需要 8 (2^4) 种变体(不抛出,抛出一个,抛出两个,...)。因此,您将需要 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 ...
    }
  }
}

扩展此示例,您可以创建要执行的操作:

SomethingToDo 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

优点

  • 向上转型是通过测试能力来完成的
  • 你可以定义复杂的动作(如果他能做到并且不能那样做)
  • 易于阅读人应该具备的能力
  • SomeToDo.act 的干净和简单的实现
  • 促销是自动的,您可以或不能完成该操作。
  • 你不需要考虑所有的可能性。您的用户将为您执行此操作(他们将实现接口 SomethingToDo)
  • 无论您的用户是否会受到异常的困扰,这是您/他们的决定(SomethingToDo 的实施方式如何)

缺点

  • 错误处理可能很复杂,尤其是在需要回滚时。
  • 可以生成大量实现SomethingToDo的类
  • Man界面中每个方法的附加方法
于 2012-10-07T16:11:06.017 回答