对于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替换为逻辑补码运算符。
答案 0 :(得分:0)
您的第一个实现(ImpureToggle)看起来还不错。只需将切换方法更改为:
state = !state
但是使用公共切换方法设置这样的Toggler似乎有点过分。要么将整个类与适当的访问修饰符一起使用,要么使用局部方法来限制范围和潜在的错误。
答案 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
。
在我看来,这是为了减少界面的限制。人们必须四处查看API,以确定没有实现此接口的类,并且没有该接口的生产者或使用者。
如果您查看新的Java 8 API,尽管可以在接口上添加静态方法,但仍会使用最终的类惯用法。
这将同时删除两个接口。可以很容易地解决它:您可以忽略此原理,或使用常规的class
。
但是,该实用程序类型有什么用?实用程序类型还有哪些其他方法?每个实用工具实现一种类型似乎过多,bloats名称空间。
使用interface
不会使您的代码成为OOP。通常,接口不是OOP概念。但是,它们的初始/主要功能(在static
和private
方法之前)是 OOP。 Java支持多种范例,因此在接口中公开了static
方法。
软件设计人员应为软件组件定义正式,精确和可验证的接口规范,该规范应使用前提条件,后置条件和不变式来扩展抽象数据类型的普通定义。
假设您想要实现坚固的接口,则应公开合同。
如果您不熟悉合同,则它们是一组规则,代码的客户端和代码本身都遵循这些规则。如果代码无法根据合同中规定的内容运行,则认为该代码已被窃听。
在Java中,they're typically defined by JavaDocs。但是,无论您选择如何向用户公开合同,这里的要点是客户应该知道该段代码将要做什么和不该做什么,并且代码应定义用户应如何使用该代码。
您的合同将如何查找您建议的类型?
合同是根据需求建立的。从显示的代码中,要求不清楚。实际上,界面接近
这不是封装,[使用] Lombok [生成getter和setter]只是使使用过程代码更轻松
数据结构不是对象
您应该封装状态和实现的详细信息,以便对象可以对此进行完全控制。逻辑将集中在对象内部,而不会散布在整个代码库中
字母是程序性的,而不是面向对象的。
在OOP中,对象通过行为进行通信。公开吸气剂时,就是在公开对象的属性。
OOP喜欢隐藏对象属性的原因可能有所不同,其中有些显而易见:属性最终在逻辑某处中使用,而依赖于该属性的逻辑/行为将不会如果暴露出来很容易。
使用回调来处理属性的逻辑,特别是您在ConsumerToggle
中的操作方式,与公开获取方法没有太大区别。
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);
但是,由于上述某些部分中提到的原因,这种方法仍然存在缺陷。
ImpureStaticToggle
和PureStaticToggle
不同。
您可以使用PureStaticToggle
来完成ImpureStaticToggle
的工作。但是,您无法使用ImpureStaticToggle
来完成PureStaticToggle
可以做的事情。它们并不完全可以互换,这些细节应该会影响您的选择。
您最终使用所显示的代码要做的是根据条件更改实现。这就是这里要做的全部。
我讨厌这样说,但是如果您的目标是通过在代码中“扔书”来遵循OOP原则,那么您的所有替代方法都违反了常用的OOP原则。
不要使事情复杂化。我认为封装/隐藏三元组不会带来任何好处。必要时按原样使用三元。将本来应该在此设计上投入的时间投入到重要的事情上。
另外,对于您的界面,toggle
也不是最好的名称,因为该行为实际上并没有切换任何内容-a better name would be chooseValue
或determineValue
,例如这就是方法的实际作用。