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();
}
2 Answers 2
Writing Style
In Swift, semicolons are not needed at the end of a line.
Specifying
self
inself.suit
andself.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
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.
- 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
-
\$\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– Tom Schulz2019年05月16日 18:00:59 +00:00Commented May 16, 2019 at 18:00
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.