此代码来自我在应用程序中使用的CardUtility
类。
public static boolean areBothColoredAndHaveSameColor(Card c1, Card c2) {
if (c1 instanceof ColoredCard coloredCard1 && c2 instanceof ColoredCard coloredCard2)
return coloredCard1.getColor() == coloredCard2.getColor();
return false;
}
public static boolean areBothNumberedAndHaveSameNumber(Card c1, Card c2) {
if (c1 instanceof NumberedCard numberedCard1 && c2 instanceof NumberedCard numberedCard2)
return numberedCard1.getNumber() == numberedCard2.getNumber();
return false;
}
public static boolean areBothSpecialAndHaveSameSpeciality(Card c1, Card c2) {
if (c1 instanceof SpecialColoredCard specialColoredCard1 && c2 instanceof SpecialColoredCard specialColoredCard2)
return specialColoredCard1.getSpeciality() == specialColoredCard2.getSpeciality();
return false;
}
很明显,我可以看到这里发生了代码重复,但我无法使用equals
检查,并且由于继承层次结构的原因:NumberedCard
和SpecialColoredCard
都扩展了抽象的ColoredCard
类。如何避免冗余?我应该让所有这些实现一个新的CardWithKeyProperty interface
,并将这些方法重构为一个单独的doBothHaveTheSameKeyProperty()
方法吗?但我也不确定,因为ColoredCard
在继承层次结构中更高一步,所以所有实现相同接口的方法听起来都不太正确。
- 将getter放在基本卡类上,但使其返回类型为
Optional<Colour>
、Optional<Integer>
等,这样不具有该特性的卡就可以返回Optional.empty()
然后你的代码可以看起来像这样:
enum UnoColour {
RED, GREEN, YELLOW, BLUE
}
interface Card {
Optional<Integer> getNumber();
Optional<UnoColour> getColour();
}
public static boolean areBothColoredAndHaveSameColor(Card c1, Card c2) {
return compareCards(c1, c2, c -> c.getColour());
}
public static boolean areBothNumberedAndHaveSameNumber(Card c1, Card c2) {
return compareCards(c1, c2, c -> c.getNumber());
}
public static <T> boolean compareCards(Card a, Card b, Function<Card,Optional<T>> extractor) {
Optional<T> aValue = extractor.apply(a);
Optional<T> bValue = extractor.apply(b);
return aValue.isPresent() && bValue.isPresent() && aValue.get().equals(bValue.get());
}
因此compareCards
删除了重复的代码。
通过使用方法引用而不是显式lambda:,我们可以使代码更加惯用(尽管如果您不熟悉所使用的功能,可能会更难理解(
return compareCards(c1, c2, Card::getNumber);
map
在compareCards
:中的应用
public static <T> boolean compareCards(Card a, Card b, Function<Card,Optional<T>> extractor) {
return extractor.apply(a)
.flatMap(at -> extractor.apply(b).map(bt -> at.equals(bt)))
.orElse(false);
}
您仍然可以为每种类型的卡使用子类,例如:
class ColouredCard implements Card {
private final Optional<UnoColour> colour;
ColouredCard(UnoColour colour) {
this.colour = Optional.of(colour);
}
@Override
public Optional<Integer> getNumber() {
return Optional.empty();
}
@Override
public Optional<UnoColour> getColour() {
return colour;
}
}
为Card
提供一个静态类型以在编译时捕获错误可能很有用——如果看不到您的代码,我不知道这会有多有用:
ColouredCard c = new ColouredCard(BLUE);
functionWhichOnlyTakesNumberedCards(c); // <-- helpful compile time error
就像在评论中提到的那样,继承并不总是最好的解决方案。使用NumberedCard和ColoredCard对这个场景进行建模是非常有用的。因为一张彩色卡片总是有一个数字,而一张有编号的卡片总是有颜色,所以为什么要把这些概念分开呢。
我会创建两个类。一个是带有颜色和编号的NormalCard,另一个是SpecialCard。并且在接口Card中应该存在compatibleTo((方法。
只有我的2c
与tgdavies的解决方案一样,我会使用Function参数,但不会修改Card子类。(我并不是说我同意你的继承层次结构,但我没有足够的信息来真正质疑它。(
private static <C extends Card> boolean attributeMatches(
Card c1,
Card c2,
Class<C> cardType,
Function<C, ?> attribute) {
return cardType.isInstance(c1) && cardType.isInstance(c2)
&& Objects.equals(attribute.apply(c1), attribute.apply(c2));
}
public static boolean sameColor(Card c1, Card c2) {
return attributeMatches(c1, c2, ColoredCard, ColoredCard::getColor);
}
public static boolean sameNumber(Card c1, Card c2) {
return attributeMatches(c1, c2, NumberedCard, NumberedCard::getNumber);
}
public static boolean sameSpecialty(Card c1, Card c2) {
return attributeMatches(c1, c2, SpecialColoredCard, SpecialColoredCard::getSpecialty);
}