views:

681

answers:

4

So, I've been working on some playing cards in Java. (Not for any practical purpose really, I just enjoy playing cards and they're good practice) Now, right now, I'm making some Card Structures, decks, hands, piles, etc. They're all essentially the same, so I figure that I'd like to use some inheritance.

The problem that I've encountered is that the core of each structure is some type of collection, but they don't all use the same collection type. A deck uses a Stack, since a deck essentially functions as a stack most often. But, a hand uses an ArrayList (If there is something more efficient than an ArrayList to use, that would be good to know as well). So, when trying to write an abstract class, I'd like to avoid using abstract methods (as it defeats the original purpose of making an abstract class, to conserve code). But, all of these methods rely on the core collection, for obvious reasons, but I don't know what type the collection is. This is what I've tried so far:

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

First off, forgive my variable names. I used to name everything "theVariable", but I decided that using "el" was more interesting. (You've got to amuse yourself somehow, right?) Also, using protected was just for the sake of simplicity.

Now, the general idea seems to work, defining a variable in the abstract class, and then defining an alias of it in the child class, but I'm not so sure that it's a great practice. The real problem I'm having is with the constructor. The constructor in Pair that accepts an array of Cards doesn't work, because to add the cards, I would first need to create the collection (in this case, the ArrayList) before the parent constructor tries to add the cards. Is there any way to work around this? Is this even a viable way of handling inheritance?

+2  A: 

Just have each implementation pass down the collection type in the constructor:

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

You may have to play a little bit with the declarations, but that should see you right

Stephen
I had to change this(new ArrayList<Card>()) to this((C) new ArrayList<Card>()), but I'm now getting an "unchecked or unsafe operations" error because of it.
Retsam
True - I've updated to make a static factory that will return a type safe object.
Stephen
You could make it return a generic type of List, but I believe in that case you want to leave your options open. I'd also venture a guess that having an empty constructor is of limited benefit (when you're creating your specialist/convenience classes anyhow)
Stephen
A: 

I'll give a quick (hackish?) answer: something you could do is have a protected abstract Collection<Card> createCollection() method defined in the base class, CardSet. Your subclasses would override this to create and return whatever type of collection is appropriate for that subclass. Then the superclass constructor would use that method to create the collection, after which it could go ahead and add the cards:

public CardSet(Card[] cards) {
    super();
    self.elCollection = createCollection();
    Collections.addAll(self.elCollection, cards);
}
David Zaslavsky
So I think Stephen's answer is probably better... but for what it's worth, this is a design pattern that has come in handy for me a few times.
David Zaslavsky
A: 

My feeling here is that you're trying to do 2 separate things with inheritance, so it seems confusing -

On the one hand, you have the concept of a set of cards. This will have a collection of cards. So first we know we have this:

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

At this point your classes should diverge, because what we have so far is the extent of the common behavior (well, we'll probably have some additional methods in there, like size(), nextCard(), isEmpty(), etc etc, that are easy enough to define on the protected collection, but never mind those now).

To use your own example

 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 {
...
}

Here cardSets are of different kinds. Thery're separated because they're expected to behave differently, and represent the additional behavior these types of collections have from the generalized notion of a card set. Trying to shoehorn additional code into the abstract parent may save a few lines but ultimately obfusactes.

Steve B.
I looked at sets, but the problem with sets is that you can't have duplicate elements, and since Cards are equal (by my definition) if they have the same suite and value, then that may be a problem. (If you play a game with several decks)
Retsam
+2  A: 

I think your biggest problem is that your just creating these classes without any real requirements. Is inheritance really the right choice here? It feels like you're designing the classes to fit a pre-conceived implementation instead of the other way around.

Define your interfaces for each class you need based on the real requirements, implement them, and then see if an abstract base class makes sense.

JohnOpincar
Well, I came to the conculsion of using an abstract because I was spending alot of time redefining duplicate methods. I mean, if we're realistic, what's the difference between a pile and a deck? well, a pile is face up. That's about it.
Retsam
That's my point. If you were actually coding something, would you even need a separate class for a pile versus a deck? I'm not just blowing smoke here, I've actually written card and dominoe based code before. What I found is that it's easy to overdesign if you don't focus on requirements.
JohnOpincar
+1 for John. Write the client code first, letting it depend on an interface that has just the methods it needs. Different dependencies will probably have different interfaces. Then you can see how many different implementations you really need.
erickson