Reputation: 43
I am trying to convert a Java class to Swift:
// Card class with only a main function
public class Card {
// fields
private int numvalue;
// constructor(s)
public Card(int v) {
if (v > 0 && v < 11) {
this.numvalue = v;}
else {
System.out.println("Error: Card value is outside the allowed range.
Value must be between 1 and 10 inclusive.")
}
If I try to initialize a Card object using the constructor with a value that isn't between 1 and 10, the initialization fails, and a statement is printed in the Terminal explaining that the value was not within the acceptable range. I tried to recreate this in Swift.
class Card {
var numValue : Int?
init(numValue:Int) {
if (numValue > 0 && numValue < 11) {self.numValue = numValue}
else {print("Card value must be between 0 and 11")}
}
func setNumValue(value:Int) -> () {
self.numValue = value
}
func getNumValue() -> Int {
return self.numValue!
}
}
The above works in the proper cases, but it appears to still create a Card instance even when an unacceptable value is passed, because I can still use the setNumValue(value:) method to change the value.
I tried making the init(numvalue:) method failable, but then I get an Optional(Card) if initialization is successful. Ideally, I'd like for any successfully initialized Card object to be a Card object, not an Optional containing a Card. Is this possible?
Upvotes: 2
Views: 640
Reputation: 236410
If your object type doesn't need to persist you should use a Struct. BTW no need to make the value property a variable and create methods to just change its properties. You just create a new Card object to replace it instead of changing the old one.
Your Card struct should look like this:
struct Card {
let value: Int
init?(value: Int) {
guard 1...10 ~= value else {
print("Card value is out of range 1...10")
return nil
}
self.value = value
}
}
let card = Card(value: 2) // returns an object of optional type (Card?) "optional Card(value: 10)\n"
let nilCard = Card(value: 11) // valus out of the range 1...10 will return nil
if let card = Card(value: 10) {
print(card) // "Card(value: 10)\n"
}
I'd like for any successfully initialized Card object to be a Card object, not an Optional containing a Card. Is this possible?
Yes it is possible. Just add a precondition to your initializer and make it non failable:
struct Card {
let value: Int
init(value: Int) {
precondition(1...10 ~= value, "Card value is out of range 1...10")
self.value = value
}
}
let card = Card(value: 2) // returns an object of type (Card) "Card(value: 2)\n"
let cantInitCard = Card(value: 11) // won't execute (precondition not met range 1...10)
You can also add a second parameter range (optional because you can set a default value) for your card initializer and add a range property to your card so you can check whats your possible card values:
struct Card {
let value: Int
let range: ClosedRange<Int>
init(value: Int, range: ClosedRange<Int> = 1...10) { // you can set a default range for yor cards
precondition(range ~= value, "Card value is out of range: " + range.description)
self.value = value
self.range = range
}
}
let card = Card(value: 5) // returns an object of type (Card) "Card(value: 5, range: 1...10)\n"
print(card.value, card.range) // "5 1...10\n"
If you would like to have a card with values with a different range 1...13 just pass the range
let newCard = Card(value: 11, range: 1...13) // ok to execute (precondition met range 1...13)
print(newCard.value, newCard.range) // "11 1...13\n"
Upvotes: 1
Reputation: 2593
Ideally, I'd like for any successfully initialized Card object to be a Card object, not an Optional containing a Card. Is this possible?
This is not possible with the standard initialization schemes available in Swift. You will ultimately end up with an Optional<Card>
if your initialization should fail under certain conditions.
Failable Initialization
You can make your initializer failable by adding a ?
to the end of the init
keyword. That is init?
.
This indicates that the initializer could fail (return nil).
In your initializer, you can then return nil when the conditions for creating your instance are not met.
In this case, I'm using a guard
statement, but you could also use an if
statement.
class Card {
var numValue : Int?
init?(numValue: Int) {
guard numValue > 0 && numValue < 11 else {
print("Card value must be between 0 and 11")
return nil
}
self.numValue = numValue
}
func setNumValue(value:Int) -> () {
self.numValue = value
}
func getNumValue() -> Int {
return self.numValue!
}
}
Since you are now returning an optional instance of Card
, you can use if let
to safely unwrap the optional and do something with the instance, or handle the fact that an instance was not created successfully (you have a nil).
let card = Card(numValue: 12)
if let card = card {
print(card)
} else {
print("Unable to create a Card instance")
}
In the above example, the "Unable to create a Card instance" string would be printed (in addition to "Card value must be between 0 and 11").
More information about how failable initialization works in Swift can be found here.
Upvotes: 1
Reputation: 10199
Well, you'll have to check the returned Optional whether it is nil or not, and then unwrap it into a non-Optional:
class Card {
var numValue : Int
init?(numValue:Int) {
guard (numValue > 0 && numValue < 11) else {
print("Card value must be between 0 and 11")
return nil
}
self.numValue = numValue
}
func setNumValue(value:Int) -> () {
guard (numValue > 0 && numValue < 11) else {
print("Card value must be between 0 and 11")
return
}
self.numValue = value
}
func getNumValue() -> Int {
return self.numValue
}
}
let cOk = Card(numValue:1)
let cFails = Card(numValue:-1)
if let c = cOk {
// now work with c because it's a Card,
// not an Optional<Card>
}
Upvotes: 2