Java中状态切换的最佳实现是什么?

时间:2018-07-22 01:04:53

标签: java oop

对于Java中的交替状态切换,最佳的(就灵活性而言)面向对象的实现是什么?我列出的实现只是我想出的,并不详尽。

注意:此问题的答案不是主观的。根据面向对象编程的原理,此实现的使用上下文应该无关紧要。

[编辑]这里的重点是代码的结构。显然,实际功能是如此简单,以至于甚至都不保证专门的实现需要付出努力。


public class ImpureToggle<T> implements Supplier<T> {
    //false represents state a, true represents state b
    private boolean state;
    private final T a;
    private final T b;

    public ImpureToggle(T a, T b) {
        this.a = a;
        this.b = b;
    }

    // returns a different reference depending on internal state
    @Override
    public T get() {
        return state ? b : a;
    }
    public void toggle() {
        state = !state;
    }
}

public class ConsumerToggle<T> implements Consumer<Consumer<T>> {
    private final T a;
    private final T b;
    //false represents state a, true represents state b
    private boolean state;

    public ConsumerToggle(T a, T b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void accept(Consumer<T> t) {
        t.accept(state ? b : a);
    }

    public void toggle() {
        state = !state;
    }
}

public interface ImpureStaticToggle {
    // reassigns parameter 'state'
    static <T> void toggle(T state, T a, T b) {
        state = state == a ? b : a;
    }
}

public interface PureStaticToggle {
    // returns a different reference depending exclusively on external input
    static <T> T toggle(boolean state, T a, T b) {
        //false represents state a, true represents state b
        return state ? b : a;
    }
}

/*
Just as an example of an unarguably bad implementation:
*/
public class MutableToggle<T> implements Supplier<T> {
    private T state;
    private final T a;
    private final T b;

    public MutableToggle(T a, T b) {
        state = a;
        this.a = a;
        this.b = b;
    }

    // exposes a mutable reference, which could completely break the logic of this
    // object and others
    @Override
    public T get() {
        return state;
    }

    public void toggle() {
        state = state == a ? b : a;
    }
}

[Edit]用于反转布尔值的三元数(为了一致性而完成),按照@gargkshitiz替换为逻辑补码运算符。

2 个答案:

答案 0 :(得分:0)

您的第一个实现(ImpureToggle)看起来还不错。只需将切换方法更改为:

state = !state

但是使用公共切换方法设置这样的Toggler似乎有点过分。要么将整个类与适当的访问修饰符一起使用,要么使用局部方法来限制范围和潜在的错误。

答案 1 :(得分:-1)

  

根据面向对象编程的原理,此实现的使用上下文应该无关紧要。

不确定这意味着什么,并且您似乎坚决不提供背景信息,但我会尽力提供更深刻的见解,以了解为什么我觉得您在做什么没有多大意义。


不要将布尔值作为参数传递 1

  

广义上讲,如果有一个参数传递到选择要执行的特定行为的函数中,则需要进一步的逐步优化;将此功能分解为较小的功能将产生更高凝聚力的功能

     
     

如您所描述的那样传入参数的问题在于该函数在做两件事以上;它可能会也可能不会检查用户的访问权限,具体取决于布尔参数的状态,然后根据该决策树执行一项功能。

     
     

最好将访问控制的关注与任务,操作或命令的关注分开。

例如,String#regionMatches。忽略大小写有很多负担。

public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) {
    if (!ignoreCase) {
        return regionMatches(toffset, other, ooffset, len);
    }
    // Note: toffset, ooffset, or len might be near -1>>>1.
    if ((ooffset < 0) || (toffset < 0)
            || (toffset > (long)length() - len)
            || (ooffset > (long)other.length() - len)) {
        return false;
    }
    byte tv[] = value;
    byte ov[] = other.value;
    if (coder() == other.coder()) {
        return isLatin1()
          ? StringLatin1.regionMatchesCI(tv, toffset, ov, ooffset, len)
          : StringUTF16.regionMatchesCI(tv, toffset, ov, ooffset, len);
    }
    return isLatin1()
          ? StringLatin1.regionMatchesCI_UTF16(tv, toffset, ov, ooffset, len)
          : StringUTF16.regionMatchesCI_Latin1(tv, toffset, ov, ooffset, len);
}

在标准库中,这是一个清楚的示例,说明了为什么应避免使用boolean参数作为行为。

请注意boolean如何确定应使用哪种实现:一种忽略大小写,一种不忽略大小写。

这是一个廉价的技巧,通常用于使实现的选择不太冗长:

for(int i = 0; i < 100; i++) {
    boolean even = i % 2 == 0;
    boolean matches = text.regionMatches(even, ...);

    // use matches
}

但是,乍一看,目前尚不清楚该情况正在确定什么。我们被迫打开文档(或更糟糕的是,实现)。

将其与以下内容进行比较:

for(int i = 0; i < 100; i++) {
    boolean even = i % 2 == 0;
    boolean matches = false;

    if(even)
        matches = text.regionMatchesIgnoreCase(...);
    else
        matches = text.regionMatches(...);

    // use matches
}

for(int i = 0; i < 100; i++) {
    boolean even = i % 2 == 0;
    boolean matches = even ? text.regionMatchesIgnoreCase(...) : text.regionMatches(...);

    // use matches
}

它比较冗长,但是对于条件是什么更明确:确定是否应忽略大小写。

regionMatchesIgnoreCase一目了然,而不是需要阅读文档来确定boolean代表什么。

理解对于避免在修复关键错误时的时间浪费很重要。假设您想盲目地应用原理,这将淘汰PureStaticToggle


不要将接口用作实用程序类 1 (易于修复)

  

在我看来,这是为了减少界面的限制。人们必须四处查看API,以确定没有实现此接口的类,并且没有该接口的生产者或使用者。

     
     

如果您查看新的Java 8 API,尽管可以在接口上添加静态方法,但仍会使用最终的类惯用法。

这将同时删除两个接口。可以很容易地解决它:您可以忽略此原理,或使用常规的class

但是,该实用程序类型有什么用?实用程序类型还有哪些其他方法?每个实用工具实现一种类型似乎过多,bloats名称空间。

使用interface不会使您的代码成为OOP。通常,接口不是OOP概念。但是,它们的初始/主要功能(在staticprivate方法之前)是 OOP。 Java支持多种范例,因此在接口中公开了static方法。


按合同设计 1

  

软件设计人员应为软件组件定义正式,精确和可验证的接口规范,该规范应使用前提条件,后置条件和不变式来扩展抽象数据类型的普通定义。

假设您想要实现坚固的接口,则应公开合同。

如果您不熟悉合同,则它们是一组规则,代码的客户端和代码本身都遵循这些规则。如果代码无法根据合同中规定的内容运行,则认为该代码已被窃听。

在Java中,they're typically defined by JavaDocs。但是,无论您选择如何向用户公开合同,这里的要点是客户应该知道该段代码将要做什么和不该做什么,并且代码应定义用户应如何使用该代码。

您的合同将如何查找您建议的类型?

合同是根据需求建立的。从显示的代码中,要求不清楚。实际上,界面接近


就OOP而言,吸气剂违反封装 1

  

这不是封装,[使用] Lombok [生成getter和setter]只是使使用过程代码更轻松

     
     

数据结构不是对象

     
     

您应该封装状态和实现的详细信息,以便对象可以对此进行完全控制。逻辑将集中在对象内部,而不会散布在整个代码库中

字母是程序性的,而不是面向对象的。

在OOP中,对象通过行为进行通信。公开吸气剂时,就是在公开对象的属性。

OOP喜欢隐藏对象属性的原因可能有所不同,其中有些显而易见:属性最终在逻辑某处中使用,而依赖于该属性的逻辑/行为将不会如果暴露出来很容易。

使用回调来处理属性的逻辑,特别是您在ConsumerToggle中的操作方式,与公开获取方法没有太大区别。


ImpureStaticToggle无法正常工作(轻松修复)

Java is pass by value

String s = "first";
toggle(s, "second", "third");
System.out.println(s); // prints "first"

s的值将保持不变。调用函数时,可以使用return语句和赋值对其进行修复:

<T> T toggle(T state, T a, T b) {
    return state == a ? b : a;
}

T value = toggle(value, a, b);

但是,由于上述某些部分中提到的原因,这种方法仍然存在缺陷。

最后的笔记

ImpureStaticTogglePureStaticToggle不同。

  • 前者根据引用的类型确定返回值
  • 后者根据 any 条件的结果确定返回值。

您可以使用PureStaticToggle来完成ImpureStaticToggle的工作。但是,您无法使用ImpureStaticToggle来完成PureStaticToggle可以做的事情。它们并不完全可以互换,这些细节应该会影响您的选择。

您最终使用所显示的代码要做的是根据条件更改实现。这就是这里要做的全部。

我讨厌这样说,但是如果您的目标是通过在代码中“扔书”来遵循OOP原则,那么您的所有替代方法都违反了常用的OOP原则。

不要使事情复杂化。我认为封装/隐藏三元组不会带来任何好处。必要时按原样使用三元。将本来应该在此设计上投入的时间投入到重要的事情上。

另外,对于您的界面,toggle也不是最好的名称,因为该行为实际上并没有切换任何内容-a better name would be chooseValuedetermineValue,例如这就是方法的实际作用。