patternjavaMinor
OOP Design of a Generic Deck of Cards
Viewed 0 times
genericcardsdesigndeckoop
Problem
I have structured a package containing interfaces that could be implemented and used for any card game.
I'm interested in knowing if I'm on the right track OOP-wise, or If I need to change my design.
ICard.java
IDeck.java
IHand.java
IRank.java
ISuit.java
Here is an example usage of the interfaces when I want to create a game of BlackJack:
Card.java
Deck.java
```
package com.tn.blackjack;
import com.tn.deck.ICard;
import com.tn.deck.IDeck;
import java.util.ArrayList;
import java.util.List;
public class Deck implements IDeck {
private int sizeOfDeck;
private List cards;
Deck(int sizeOfDeck) {
this.sizeOfDeck = sizeOfDeck;
this.cards = new ArrayList<>(sizeOfDeck);
}
void initializeWith(String[] suits, int[] ranks) {
for(int i = 0; i < sizeOfDeck; i++) {
cards.add(new Card(
suits[i % suits.length],
ranks[i % ranks.length]
));
}
print();
}
public int getSize() {
return c
I'm interested in knowing if I'm on the right track OOP-wise, or If I need to change my design.
ICard.java
package com.tn.deck;
public interface ICard {
void printCard();
}IDeck.java
package com.tn.deck;
public interface IDeck {
ICard dealCard();
void shuffle();
}IHand.java
package com.tn.deck;
public interface IHand {
void addCard(ICard card);
void removeCard(ICard card);
void showHand();
int calculateValue();
}IRank.java
package com.tn.deck;
public interface IRank {
int[] addRanks();
}ISuit.java
package com.tn.deck;
public interface ISuit {
String[] addSuits();
}Here is an example usage of the interfaces when I want to create a game of BlackJack:
Card.java
package com.tn.blackjack;
import com.tn.deck.ICard;
public class Card implements ICard {
private String suit;
private int rank;
Card(String suit, int rank) {
this.suit = suit;
this.rank = rank;
}
public String getSuit() {
return suit;
}
public int getRank() {
return rank;
}
@Override
public void printCard() {
System.out.printf("%s%d ", suit, rank);
}
}Deck.java
```
package com.tn.blackjack;
import com.tn.deck.ICard;
import com.tn.deck.IDeck;
import java.util.ArrayList;
import java.util.List;
public class Deck implements IDeck {
private int sizeOfDeck;
private List cards;
Deck(int sizeOfDeck) {
this.sizeOfDeck = sizeOfDeck;
this.cards = new ArrayList<>(sizeOfDeck);
}
void initializeWith(String[] suits, int[] ranks) {
for(int i = 0; i < sizeOfDeck; i++) {
cards.add(new Card(
suits[i % suits.length],
ranks[i % ranks.length]
));
}
print();
}
public int getSize() {
return c
Solution
Thanks for sharing the code.
Naming
Finding good names is the hardest part in programming, so always take your time to think about the names of your identifiers.
Naming Conventions.
On the bright side you follow the Java Naming Conventions
But you prefix you interfaces with
Currently this is considered "old fashioned".
I personally don't do it because my IDEs auto completion suggests variables starting with an
In general names of interfaces should be based on adjectives where possible.
Do not surprise the reader
You have method names like
Also: If an object is a "
OOP
Doing OOP means that you follow certain principles which are (amongst others):
Interfaces, abstract classes, or inheritance support hat principles and should be used as needed. They do not "define" OOP.
interfaces
Interfaces should be uses if you plan to create multiple classes sharing the same methods. Sometime it is usefull to create an interface for a class with complex/expensive behavior in order to decouple it, eg.: to enable UnitTesting.
In your exampe a good interfaces meight be:
inharitence
We use inharitance to extend or redefine the behavior of a base class (including interfaces).
Inheritance represents an is a relationship between the base class and the sub class.
A bad example ist your class
The game itself does not have "Suits" and "Ranks", therefore it should not have the methods
In your example for a concrete game (BlackJack) you could implement the cards like this:
This could be used like this:
in the booleanIsSameSuit implementation you say suit.equals(other.suit); how? Suitable doesn't know about suits?
Thats correct.
We have three options to solve this:
-
cast
This works because we now that there is no other class in the program that implements the
But it is kinda brittle.
-
add a method
The problem with that is that this method is not useful a user of a
-
so the best is to also add a generic parameter to the interface:
Naming
Finding good names is the hardest part in programming, so always take your time to think about the names of your identifiers.
Naming Conventions.
On the bright side you follow the Java Naming Conventions
But you prefix you interfaces with
I.Currently this is considered "old fashioned".
I personally don't do it because my IDEs auto completion suggests variables starting with an
i in that case, and there is no point in telling by the name of a variable that it is declared as an interface type...In general names of interfaces should be based on adjectives where possible.
Do not surprise the reader
You have method names like
addSuits() which suggests that you put something into the object but instead your methods have no parameter and return something from the object.Also: If an object is a "
Rank" what sense does it make to call iRank.addRank()?OOP
Doing OOP means that you follow certain principles which are (amongst others):
- information hiding / encapsulation
- single responsibility
- separation of concerns
- KISS (Keep it simple (and) stupid.)
- DRY (Don't repeat yourself.)
- "Tell! Don't ask."
- Law of demeter ("Don't talk to strangers!")
Interfaces, abstract classes, or inheritance support hat principles and should be used as needed. They do not "define" OOP.
interfaces
Interfaces should be uses if you plan to create multiple classes sharing the same methods. Sometime it is usefull to create an interface for a class with complex/expensive behavior in order to decouple it, eg.: to enable UnitTesting.
In your exampe a good interfaces meight be:
interface Suitable {
booleanIsSameSuit(Suitable other);
}
interface Rankable {
booleanIsConsecutive(Rankable other);
}inharitence
We use inharitance to extend or redefine the behavior of a base class (including interfaces).
Inheritance represents an is a relationship between the base class and the sub class.
A bad example ist your class
BlackJack:The game itself does not have "Suits" and "Ranks", therefore it should not have the methods
addSuits() nor addRanks()In your example for a concrete game (BlackJack) you could implement the cards like this:
enum Suit {SPADE, CLUB, DIAMOND, HEART};
enum Rank {R2,R3,R4,R5,R6,R7,R8,R9,R10,JACK,QUEEN,KING,ACE}
public class Card implements Suitable, Rankable, Comparable{
private final Rank rank;
private final Suit suit;
public Card(Suit suit, Rank rank){
this.suit = suit;
this.rank = rank;
}
@Override
public int compareTo(Card other){
// compare rank and suit
}
@Override
public booleanIsSameSuit(Suitable other){
return suit.equals(other.suit);
}
@Override
public booleanIsConsecutive(Rankable other){
return 1+rank.ordinal()==other.rank.ordinal()
|| other.rank.ordinal()==rank.ordinal()-1;
}
}This could be used like this:
List deck = createDeck(Suit.values(),Rank.values());
// ...
private List createDeck(Suit[] suits, Rank[] ranks){
List newDeck = new ArrayList<>();
for(Suit suit : suits)
for(Rank rank : ranks)
newDeck.add(new Card(suit,rank));
return newDeck;
}in the booleanIsSameSuit implementation you say suit.equals(other.suit); how? Suitable doesn't know about suits?
Thats correct.
We have three options to solve this:
-
cast
other to CardThis works because we now that there is no other class in the program that implements the
Suitable interface.But it is kinda brittle.
-
add a method
getSuit() to the interface.The problem with that is that this method is not useful a user of a
Suitable only for the implementer.-
so the best is to also add a generic parameter to the interface:
interface Suitable {
booleanIsSameSuit(T other);
}
interface Rankable {
booleanIsConsecutive(T other);
}public class Card implements Suitable, Rankable, Comparable{
private final Rank rank;
private final Suit suit;
public Card(Suit suit, Rank rank){
this.suit = suit;
this.rank = rank;
}
@Override
public int compareTo(Card other){
// compare rank and suit
}
@Override
public booleanIsSameSuit(Card other){
return suit.equals(other.suit);
}
@Override
public booleanIsConsecutive(Card other){
return 1+rank.ordinal()==other.rank.ordinal()
|| other.rank.ordinal()==rank.ordinal()-1;
}
}Code Snippets
interface Suitable {
booleanIsSameSuit(Suitable other);
}
interface Rankable {
booleanIsConsecutive(Rankable other);
}enum Suit {SPADE, CLUB, DIAMOND, HEART};
enum Rank {R2,R3,R4,R5,R6,R7,R8,R9,R10,JACK,QUEEN,KING,ACE}
public class Card implements Suitable, Rankable, Comparable<Card>{
private final Rank rank;
private final Suit suit;
public Card(Suit suit, Rank rank){
this.suit = suit;
this.rank = rank;
}
@Override
public int compareTo(Card other){
// compare rank and suit
}
@Override
public booleanIsSameSuit(Suitable other){
return suit.equals(other.suit);
}
@Override
public booleanIsConsecutive(Rankable other){
return 1+rank.ordinal()==other.rank.ordinal()
|| other.rank.ordinal()==rank.ordinal()-1;
}
}List<Card> deck = createDeck(Suit.values(),Rank.values());
// ...
private List<Card> createDeck(Suit[] suits, Rank[] ranks){
List<Card> newDeck = new ArrayList<>();
for(Suit suit : suits)
for(Rank rank : ranks)
newDeck.add(new Card(suit,rank));
return newDeck;
}interface Suitable<T> {
booleanIsSameSuit(T other);
}
interface Rankable<T> {
booleanIsConsecutive(T other);
}public class Card implements Suitable<Card>, Rankable<Card>, Comparable<Card>{
private final Rank rank;
private final Suit suit;
public Card(Suit suit, Rank rank){
this.suit = suit;
this.rank = rank;
}
@Override
public int compareTo(Card other){
// compare rank and suit
}
@Override
public booleanIsSameSuit(Card other){
return suit.equals(other.suit);
}
@Override
public booleanIsConsecutive(Card other){
return 1+rank.ordinal()==other.rank.ordinal()
|| other.rank.ordinal()==rank.ordinal()-1;
}
}Context
StackExchange Code Review Q#162342, answer score: 6
Revisions (0)
No revisions yet.