如何避免instanceOf和动态getter检查



此代码来自我在应用程序中使用的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检查,并且由于继承层次结构的原因:NumberedCardSpecialColoredCard都扩展了抽象的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);

mapcompareCards:中的应用

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);
}

最新更新