创建一个新对象 class 或编写一个转换子 class 对象的方法?或者是其他东西?性能不是偏好

Create a new object class or write a method which converts sub-class objects? or something else? Performance NOT Preference

我将两个单独的程序放在一起,它们可以玩一种名为 'Crazy Eights' 的纸牌游戏。

我为这个程序编写的 classes 基于默认的 'card' 包,它提供纸牌对象和一些通用的纸牌方法。

我采用了两种不同的方法来实现这一点,它们都具有各自的功能。

这里有两个描述这两种方法的 UML class 图:

继承子class'conversion'方法

用类似的方法组合子class

正如您在方法 1 中看到的那样,class EightsCard 包含一个方法 convert(Card) 这是该方法:

  /**
   * Converts a Card into an EightsCard
   * @param card The card to be converted
   * @return The converted EightsCard
   */
   public EightsCard convert(Card card) {
     if (card != null) {
     EightsCard result = new EightsCard(card.getRank(), card.getSuit());
     return result;
     } 
     return null;
   }
 }

此方法允许您调用 CardCollection 中的方法,否则这些方法是不合法的。例如,在如下所示的 EightsPlayer class 的播放方法中:

  /**
   * Removes and returns a legal card from the player's hand.
   */
  public EightsCard play(Eights eights, EightsCard prev) {
    EightsCard ecard = new EightsCard(0, 0);
    ecard = ecard.convert(searchForMatch(prev));
    if (ecard == null) {
      ecard = drawForMatch(eights, prev);
      return ecard;
    }
    return ecard;
  }

方法 2 不需要任何转换,因为类似的方法已写入扩展 CardCollection 的新 class EightsCardCollection。现在播放方法可以这样写:

  public EightsCard play(Eights eights, EightsCard prev) {
    EightsCard card = searchForMatch(prev);
    if (card == null) {
      card = drawForMatch(eights, prev);
    }
    return card;
  }

这让我想到了几个问题:

例如,写 'similar' classes 更具体 1 而不使用默认的 classes 可能更好2 完全没有。

1 在 class 图表中标记为 'crazyeights.syd.jjj' 或 'chaptwelvetofort'。

2 在 class 图中标记为 'defaults.syd.jjj' 或 cards.syd.jjj'。

子太多classes

这两种方法都不是很好,因为它们都有比需要更多的子classes。为什么 EightsCard 扩展 Card?如果您想实现其他纸牌游戏怎么办? (有很多,你知道的......)你会为每个纸牌游戏做一个子class吗?请不要。

我想要这些 classes:

  • 卡片
  • 卡片收藏
  • 玩家

我什至不会有一个 Deck class 因为它看起来除了扩展 CardCollection 没有提供任何更多的功能.

然后我会为每个游戏实现一个 class,所以一个 EightsController class 处理该游戏的游戏逻辑。没有特定的 class 用于 EightsCard,没有特定的 class 用于 EightsCardCollection,等等

原因很简单:您只需要这个。无论您玩的是哪种纸牌游戏,Card 和 CardCollection 都是完全相同的。玩家在所有游戏中也是一样的。

关于那些域模型,我同意 Simon Forsberg 的观点,它们太复杂了,而且我知道它们旨在展示一些概念,但即便如此,它们也可以使用更现实的示例,例如组件在汽车和他们的关系。您可能只需要两个域 classes,Card 和 Player。对于有序的卡片集合,请使用 List<Card>。在发牌、轮流等方面,如何组织游戏逻辑有很大的创意空间。 Generally it is preferable to compose types out of other ones 而不是严重依赖在实践中不太灵活的继承。

在性能方面,通用规则适用。例如,尽可能重用对象。如果您总是使用 52 张牌,那么甚至没有理由使用 Card class。可能没有什么比在任何地方重用枚举值更好的了。

enum Card {
    ACE_CLUBS,
    TWO_CLUBS,
    // ...
    ACE_DIAMONDS,
    // ...
}

(你真的不能让这个枚举变得更复杂,因为不同的游戏使用不同的顺序,根据上下文在不同的卡片上放置不同的值,等等)

关于性能编程的优秀实用指南,我推荐这本书,Java Performance: The Definitive Guide

西蒙的评论引起了我的注意:"I would prefer having two enums - one for Suit and one for Rank - instead of one enum for the whole card"

Is there a better way to compose this program?

关键点在于——您的大部分程序应该与卡片的内存表示完全隔离。

在内存中,一张卡可能是

  • 一个整数
  • 一个字节
  • 枚举
  • 一个字符串
  • 一个URL (http://example.org/cards/hearts/7)
  • an integer/byte 限制在 [0,52)
  • 范围内
  • 一个元组
    • 整数,整数
    • 枚举,枚举
    • 字节,字节
    • 混搭
  • 对第三方实施的引用

你的大部分代码不应该关心

理想情况下,唯一关心的是提供实现的模块,也许还有组合根。

图表中大多数有 <<Java Class>> 的地方你应该有 <<Java Interface>>.

Parnas, 1972

在那种设计中,您将使用 组合 将疯狂的八语义附加到其他地方定义的通用卡片定义。

Eights.Deck deck(Iterable<Generic.Card> standardDeck) {
    List<Eights.Card> cards = new ArrayList();
    for(Generic.Card c : standardDeck) {
        cards.add(
            new Eights.Card(c)
        );
    }
    return new Eights.Deck(cards);
}

或者也许

Eights.Deck deck(Generic.Deck standardDeck) {
    return new Eights.Deck(
         standardDeck
             .stream()
             .map(Eights.Deck::new)
             .collect(Collectors.toList())
    );
}

组合根看起来像

public static void main(String [] args) {
    GenericCards genericCards = new ApacheGenericCards();
    EightsCards eightsCards = MyAwesomEightsCardsV2(genericCards);
    EightsGame game = new EightsGame(eightsCards)
    game.play();
}