【问题标题】:Abstract Inheritance and Generics (with playing cards)抽象继承和泛型(带扑克牌)
【发布时间】:2010-10-13 23:32:37
【问题描述】:

所以,我一直在用 Java 制作一些扑克牌。 (实际上不是为了任何实际目的,我只是喜欢打牌,它们是很好的练习)现在,现在,我正在制作一些卡片结构、套牌、手牌、牌堆等。它们本质上都是一样的,所以我想我想使用一些继承。

我遇到的问题是每个结构的核心都是某种类型的集合,但它们并不都使用相同的集合类型。甲板使用堆栈,因为甲板本质上最常用作堆栈。但是,一只手使用 ArrayList(如果有比 ArrayList 更有效的东西可以使用,那也很高兴知道)。 因此,在尝试编写抽象类时,我想避免使用抽象方法(因为它违背了创建抽象类的最初目的,以节省代码)。但是,出于显而易见的原因,所有这些方法都依赖于核心集合,但我不知道集合是什么类型。到目前为止,这是我尝试过的:

public abstract class CardSet
{
    protected AbstractCollection<Card> elCollection;
    public CardSet()
    {
        super();
    }
    public CardSet(Card[] cards)
    {
        super();   
        for(Card c: cards)
            elCollection.add(c);

    }
    public void add(Card c)
    {
        elCollection.add(c);
    }
}
public class Pair extends CardSet  //Pair is just a dummy class to get inheritance right
{
     ArrayList<Card> elPair;
     public Pair()
     {
         elPair = new ArrayList<Card>();  //elPair is defined, because casting
         elCollection = elPair;  // elCollection to arraylist would be a pain.

     }
     public Pair(Card[] cards)
     { this();
       super(cards);
     } 
}

首先,请原谅我的变量名。我曾经将所有内容命名为“theVariable”,但我认为使用“el”更有趣。 (你总得自娱自乐吧?)另外,使用 protected 只是为了简单。

现在,一般的想法似乎可行,在抽象类中定义一个变量,然后在子类中定义它的别名,但我不太确定这是一个很好的做法。 我遇到的真正问题是构造函数。 Pair 中接受卡片数组的构造函数不起作用,因为要添加卡片,我首先需要创建 父构造函数尝试添加卡片之前的集合(在本例中为 ArrayList)。 有没有办法解决这个问题?这甚至是处理继承的可行方法吗?

【问题讨论】:

    标签: java generics inheritance abstract


    【解决方案1】:

    只需让每个实现在构造函数中传递集合类型:

    public abstract class CardSet<C extends Collection<Card>>
    {
        protected final C elCollection;
        public CardSet<Collection<Card>> cardSet()
        {
            return new CardSet<Collection<Card>>(new ArrayList<Card>());
        }
        public CardSet(C col){
            this.elCollection = col;
        }
        public CardSet(C col, Card[] cards)
        {
            this(col);
            for(Card c: cards)
                elCollection.add(c);
        }
        public void add(Card c)
        {
            elCollection.add(c);
        }
    }
    public class Pair extends CardSet<List<Card>> 
    {
        public Pair()
        {
            super(new ArrayList<Card>());
    
        }
        public Pair(Card[] cards)
        { 
            super(new ArrayList<Card>(), cards);
        } 
    }
    

    您可能需要对声明进行一些操作,但这应该是正确的

    【讨论】:

    • 我不得不将 this(new ArrayList()) 更改为 this((C) new ArrayList()),但我现在得到一个“未经检查或不安全的操作”因为它而出错。
    • 是的 - 我已经更新为创建一个静态工厂,该工厂将返回一个类型安全的对象。
    • 您可以让它返回一个通用类型的 List,但我相信在这种情况下您希望保留您的选项。我还冒昧地猜测,拥有一个空的构造函数的好处是有限的(无论如何,当您创建专家/便利类时)
    【解决方案2】:

    我会给出一个快速(hackish?)的答案:您可以做的是在基类CardSet 中定义一个protected abstract Collection&lt;Card&gt; createCollection() 方法。您的子类将覆盖它以创建并返回适合该子类的任何类型的集合。然后超类构造函数将使用该方法创建集合,之后它可以继续添加卡片:

    public CardSet(Card[] cards) {
        super();
        self.elCollection = createCollection();
        Collections.addAll(self.elCollection, cards);
    }
    

    【讨论】:

    • 所以我认为斯蒂芬的答案可能更好......但就其价值而言,这是一种对我来说已经派上用场几次的设计模式。
    【解决方案3】:

    我在这里的感觉是你试图用继承做两件不同的事情,所以看起来很混乱 -

    一方面,您有一组卡片的概念。这将有一组卡片。所以首先我们知道我们有这个:

    public abstract class CardSet {
        protected Collection<Card> cards;
    }
    

    此时你的类应该发散,因为到目前为止我们所拥有的是常见行为的范围(好吧,我们可能会有一些额外的方法,比如 size(), nextCard()、isEmpty() 等,很容易在受保护的集合上定义,但现在不用管这些了)。

    使用你自己的例子

     public class Deck extends CardSet {
          public Deck (){
            cards = new Stack<Card>();
         }
    
         public void shuffle() { ... }
    }
    
    public class Hand extends CardSet {
        public Hand(){
           //i'm using a set here instead of the list in your example because I 
          // don't think ordering is a property of a hand.
          cards = new HashSet<Card>();
        }
    }
    public class Pair extends CardSet {
    ...
    }
    

    这里的cardSets是不同种类的。它们是分开的,因为它们被期望表现不同,并代表这些类型的集合从卡组的广义概念中具有的附加行为。尝试将附加代码硬塞到抽象父级中可能会节省几行代码,但最终会造成混淆。

    【讨论】:

    • 我查看了集合,但是集合的问题是你不能有重复的元素,并且由于卡片是相等的(根据我的定义)如果它们具有相同的套件和值,那么这可能成为问题。 (如果你玩一个游戏有几副牌)
    【解决方案4】:

    我认为您最大的问题是您只是创建这些类而没有任何实际要求。继承在这里真的是正确的选择吗?感觉就像您正在设计类以适应预先设想的实现,而不是相反。

    根据实际需求为您需要的每个类定义接口,实现它们,然后看看抽象基类是否有意义。

    【讨论】:

    • 好吧,我想到了使用摘要,因为我花了很多时间重新定义重复的方法。我的意思是,如果我们是现实的,一堆和甲板有什么区别?好吧,一堆正面朝上。就是这样。
    • 这就是我的观点。如果你真的在编写一些代码,你甚至需要一个单独的类来存放一堆而不是一个甲板吗?我不只是在这里吹烟,实际上我以前写过基于卡片和多米诺骨牌的代码。我发现如果不关注需求,很容易过度设计。
    • +1 为约翰。首先编写客户端代码,让它依赖于一个只有它需要的方法的接口。不同的依赖可能会有不同的接口。然后你可以看到你真正需要多少不同的实现。
    猜你喜欢
    • 2023-04-03
    • 1970-01-01
    • 2019-06-27
    • 1970-01-01
    • 1970-01-01
    • 2021-07-05
    • 2012-04-11
    • 2017-02-23
    • 1970-01-01
    相关资源
    最近更新 更多