使用下面的代码,有没有办法让这两个极其相似的类从单个超类继承?它们在变量类型上几乎完全不同。如果没有,是否有一种设计模式似乎最适合这种情况?我一直在考虑使用工厂模式,但我不确定这是否最适合这种情况。
class stringCharacteristic {
String name = "";
String value = "";
public String getValue() {
return value;
}
public stringCharacteristic setValue(String value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public stringCharacteristic setName(String name) {
this.name = name;
return this;
}
}
class intCharacteristic {
String name = "";
int value = 0;
public int getValue() {
return value;
}
public intCharacteristic setValue(int value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public intCharacteristic setName(String name) {
this.name = name;
return this;
}
}
编辑:使用泛型找到了一个对我来说相当好的解决方案,可能不是最有效的,但它完成了任务。此外,我知道代码在流体界面和其他方面看起来有点不对劲,这并不是我实际上写的代码,只是一个简化的东西来显示问题,而不是复制粘贴一百行只会分散对主要问题的注意力的东西。
class characteristic<E> {
String name;
E value;
public E getValue() {
return value;
}
public characteristic<E> setValue(E value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public characteristic<E> setName(String name) {
this.name = name;
return this;
}
public characteristic<E> setMin(int min) {
this.min = min;
return this;
}
public characteristic<E> setMax(int max) {
this.max = max;
return this;
}
}
否(t真的)。
这里有两个基本问题。
基本体不玩动态打字游戏
例如,如果你有一个StringCharacteristic
(请注意,在java中我们是WriteTypesLikeThis
,而不是likeThis
)和LocalDateCharacteristic
,它都是对象引用,我们可以对此进行推广,生成一个超类型ObjectCharacteristic<T>
。但是,泛型不可能是基元的,也没有办法对基元进行泛化。
现在,也就是说。Project Valhalla正在进行大量的活跃开发(我敢肯定,它在很大程度上是OpenJDK目前活跃开发的各种语言相关项目中付出最大努力的),所以请密切关注JDK更新的功能列表。
你可以使用包装器类型(java.lang.Integer
)来解决这个问题,但这是一个非常糟糕的替代:它们可能是令人讨厌的null
(int
不可能),而且它们在CPU和内存方面的效率都要低一个数量级。它们也不一定兼容——自动开箱只适用于此。
你可以把这些方法推到一个超类中(如果你不想让它的那个方面成为公共API的一部分,就让它成为私有包),使用泛型:class Characteristic<T>
,在这个超类中的所有地方使用T
而不是int
/String
,然后这个超类就可以了。如果你真的坚持要有一个显式命名的类型,你可以只写public class IntCharacteristic extends Characteristic<Integer> {}
,也许添加一个构造函数,并在一天内调用它,剩下的就处理好了(甚至是字段,你会在超类中声明为private T value
。
但是,这为您提供了一个使用Integer
而不是int
的特性类。没有办法让它使用int。直到valhalla。
如果你不愿意接受这种低效率,你将不得不手动写出与int
相关的位。
自我类型的问题
如果您都接受使用Integer
而不是int
、和的负面影响,那么这个次要问题就不会出现——您可以只使用一个public class Characteristic<T>
,没有自定义子类。
类型层次结构和"fluent API"(返回自己类型的API方法)的问题在于它们在此处不起作用。
间奏——让我们来写吧
因为名称位完全相同,除了setName方法的返回类型之外,我们可以这样做:
private abstract class Characteristic {
private String name;
public Characteristic setName(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
}
然后你可以编写这样的子类型,你可以跳过名称的所有代码,因为你的超类会处理这个问题:
public class IntCharacteristic extends Characteristic {
private int value;
public IntCharacteristic setValue(int value) {
...
}
}
但是,问题
这里的一个问题是setName方法的返回类型错误。是的,它返回this
,在新的IntCharacteristic
实例的情况下,this
将是instanceof IntCharacteristic
,但setName
方法的返回类型没有声明它,因此javac不会这样对待它。因此,这将失败:
new IntCharacteristic().setName("Hello").setValue(5);
你可以让它成功,使用自己的泛型破解。
这是个坏主意,您需要继续阅读以获得更好的解决方案。我把它包括在这里是因为虽然在这种情况下有更好的解决方案可用,但有时没有,而这种破解是最好的解决方案:
public class Characteristic<S extends Characteristic<S>> {
private String name;
@SuppressWarnings("unchecked")
protected S self() {
return (S) this;
}
public S setName(String name) {
this.name = name;
return self();
}
}
public class IntCharacteristic extends Characteristic<IntCharacteristic> {
}
此代码确保setName
的返回类型是IntCharacteristic
,这正是您想要的。如果没有强制转换,编译器将发出一个警告,说明该操作实际上根本没有进行任何检查,但我们知道它将是"true",因为这个奇怪的签名(S extends Self<S>
)实际上意味着任何扩展它的东西都只能将自己放在<>
中。
更好的解决方案
大多数情况下,整个"回归自我"的事情都被夸大了。它不是特别的java风格,这表明它是如何导致问题的。更普遍地说,如果你坚持制作"现代"API,那么这就像你建造了一个厨房,厨房里有一个中世纪的烤箱和一个全新的微波炉。现代观点涉及不变性。你为什么允许在这里改名?难道它不应该在创造时就被钉在石头上吗?然后你就可以避免这一切了!
public class Characteristic {
private final String name;
public Characteristic(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class IntCharacteristic extends Characteristic {
private final int value;
public IntCharacteristic(String name, int value) {
super(name);
this.value = value;
}
public int getValue() {
return value;
}
}
问题解决了。
顺便说一句,别再回头了
从根本上讲,return this;
不是一个好主意,除非可能在final
类中。问题是,很明显您的意图只是让方法调用"连锁"到这件事,但从根本上讲,如果涉及层次结构,这是不可行的,除非您使用上述不能"隐藏"的破解(该泛型参数现在是您的公共API的一部分)。
你也没有真正使用它来返回计算结果,这就是返回值的目的
换句话说,当你这样做的时候,你就是在入侵语言。这样做是有原因的,但最好睁大眼睛:无论何时你对语言进行攻击,你都必须面对这样一个事实,即它最终会咬你的屁股。
REAL解决方案要么是让java语言发展"self-type"的概念,要么是让java语言发展"allow chaining"的概念。这个问题的更好的解决方案是这两个真正的解决方案之一:
语言方案A
chainable public void setName(String name) {
this.name = name;
}
在chainable
关键字都要求(如果不是编译器错误)返回类型为void
的情况下,AND允许调用方写入new Foo().setName("stuff").chainMoreMethodsHere();
。它纯粹是javac
语法糖,除了作为标记之外,这些都没有在类文件中存在,所以javac
知道该做什么。类文件中的方法签名保持其VOID返回类型,语言工具知道,例如,忽略返回类型在这里是完全可以的。
语言方案B
将其交给调用方,而不是交给API编写者:
只有一个void方法:public void setName() { ... }
,但调用者可以选择链式:
Foo x = new Foo()..setName()..setSomethingElse();
这里双点的意思是:调用方法,丢弃返回的内容,并将表达式解析为与接收器相同。(x.foo()
表示:调用foo
,抛出返回类型,表达式解析为x
)。
目前,OpenJDK列表中没有任何关于这方面的流量。但也许今天不同了。如果添加了这一点,您的API现在就被破坏了,可能会永远感到过时。
我并不是说流利的API完全是愚蠢的。但很接近。你正在破解语言,通常,无论是因为自我类型的问题还是未来语言的改进,这意味着你总有一天会后悔的。
这到底有多有用取决于客户端想对Characteristic
实例做什么,也就是说,当它无法判断它们是什么具体类时。
如果在某些情况下(例如)您想要显示一组Characteristic
的名称,也许还有它们值的toString
,那么这将非常有用。
你可以这样做:
interface Characteristic<T> {
public T getValue();
public String getName();
Characteristic<T> setValue(T value);
Characteristic<T> setName(String name);
}
class StringCharacteristic implements Characteristic<String>{
String name = "";
String value = "";
public String getValue() {
return value;
}
public StringCharacteristic setValue(String value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public StringCharacteristic setName(String name) {
this.name = name;
return this;
}
}
class IntCharacteristic implements Characteristic<Integer> {
String name = "";
int value = 0;
public Integer getValue() {
return value;
}
public IntCharacteristic setValue(Integer value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public IntCharacteristic setName(String name) {
this.name = name;
return this;
}
}
(顺便说一下,请使用正确的Java命名约定)
您可以使用一个抽象类来考虑常见的实现:
abstract class AbstractCharacteristic<T> implements Characteristic<T> {
String name = "";
T value;
public AbstractCharacteristic(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public Characteristic<T> setValue(T value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public Characteristic<T> setName(String name) {
this.name = name;
return this;
}
}
class StringCharacteristic extends AbstractCharacteristic<String>{
public StringCharacteristic() {
super("");
}
}
class IntCharacteristic extends AbstractCharacteristic<Integer> {
public IntCharacteristic() {
super(0);
}
}
(我们需要构造函数参数,因为默认情况下,字符串字段通常初始化为null
,而不是""
)