1
\$\begingroup\$

I'm trying to properly write a card class in swift. I've been taking classes in software engineering and I need help in understanding how to take a more object-oriented approach to my class. I have seen a Class for suit and rank and I'm wondering would that approach would be better? I have also seen the suit as enums and wonder if that approach would be better.

Any help will be appreciated.

Thank you

class Card
private var rank: Int
private var suit: Int

init(n: Int) {
    self.rank = -1;
    self.suit = -1;
    if(n >= 0 && n <= 51) {
        self.rank = n % 13;
        self.suit = n / 13;
    }
}

func getRank() -> Int {
    return self.rank;
}

func getSuit() -> Int {
    return self.suit;
}

func compareRank(otherCard: Card) -> Bool {
    return self.rank == otherCard.getRank();
}

func compareSuit(otherCard: Card) -> Bool {
    return self.suit == otherCard.getSuit();
}
\$\endgroup\$
1
\$\begingroup\$

Writing Style

  1. In Swift, semicolons are not needed at the end of a line.

  2. Specifying self in self.suit and self.rank is not needed (since there is no possible confusion). The compiler can infer that you are referring to the properties of this class.


Initializer

With rank and suit defined as integers, a card would be initialized with -1 as rank and suit if n < 0. As defined, the initializer would produce an invalid card. An honest initializer would fail instead :

init?(n: Int) {
    guard n >= 0 && n <= 51 else {
        return nil
    }
    rank = n % 13
    suit = n / 13
}

And since the suit and rank properties can't be changed (you've defined them as private properties with getter methods), it would be appropriate to define them as constants: let instead of var:

private let rank: Int
private let suit: Int

Type Choices

  1. Creating a card based on an integer isn't very intuitive. An alternative choice, as you've mentioned, would be using enums. Such an approach avoids the possibility of creating an invalid Card :

    enum Suit: Character {
        case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
    }
    
    enum Rank: Int {
        case ace = 1, two, three, four, five, six, seven, eight, nine, ten, jack, queen, king
    }
    

Usually, it is not sufficient to look for equality when comparing the rank of a card. The value associated with a rank could be defined in a Blackjack game this way.

  1. Using a struct instead of a class would be encouraged here since there is no flagrant purpose of having a shared mutable state for a Card instance. Plus, value types are stored in the Stack instead of the Heap, which allows quicker access.

Here is a version of your code with all the previous suggestions :

enum Suit: Character {
    case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
}

enum Rank: Int {
    case ace = 1, two, three, four, five, six, seven, eight, nine, ten, jack, queen, king
}

class Card {

    private let rank: Rank
    private let suit: Suit

    init(_ rank: Rank, of suit: Suit) {
        self.rank = rank
        self.suit = suit
    }

    func getRank() -> Rank {
        return rank
    }

    func getSuit() -> Suit {
        return suit
    }

    func compareRank(with otherCard: Card) -> Bool {
        return rank == otherCard.getRank()
    }

    func compareSuit(with otherCard: Card) -> Bool {
        return suit == otherCard.getSuit()
    }
}

And you could use it like so :

let a = Card(.ace, of: .spades)
let q = Card(.queen, of: .hearts)

print(a.getRank())              //ace
print(a.getSuit())              //spades
print(a.getSuit().rawValue)     //♠
print(q.getRank().rawValue)     //12
print(a.compareRank(with: q))   //false
print(a.compareSuit(with: q))   //false
\$\endgroup\$
  • \$\begingroup\$ while using self is not necessary I think it can make things more clear. Swift sacrifices a lot of readability for brevity. \$\endgroup\$ – Tom Schulz May 16 at 18:00
3
\$\begingroup\$

In addition to what @Carpsen90 said:

No get... accessor methods

Unlike some other programming languages, you don't define accessor methods for the properties, these are implicitly created by the compiler. If suit and rank are constant properties (initialized once and never mutated) then it is simply

struct Card {
    let rank: Int
    let suit: Int

    // ...
}

For properties which are publicly read-only, but internally read-write, it is

 struct Card {
    private(set) var rank: Int
    private(set) var suit: Int

    // ...
}

That makes the compareRank/Suit methods obsolete, because you can directly test

if card1.rank == card2.rank { ... }

etc.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.