We can use the same method to cause different things to happen according to the class on which we invoke the method. In object-oriented programming, this feature is known as polymorphism.
For example, consider that we defined a talk
method in the Animal
class. The different subclasses of Animal
must override this method to provide their own implementation of talk
.
The Dog
class overrode this method to print the representation of a dog barking, that is, a Woof
message. On the other hand, a Cat
class will override this method to print the representation of a cat meowing, that is, a Meow
message.
Now, let's think about a CartoonDog
class that represents a dog that can really talk as part of a cartoon. The CartoonDog
class would override the talk
method to print a Hello
message because the dog can really talk.
Thus, depending on the type of instance, we will see a different result after invoking the same method with the same arguments even when all of them are subclasses of the same base class, that is, the Animal
class.
The following lines show the code for the SmoothFoxTerrier
class that inherits from TerrierDog
. The code file for the sample is included in the swift_3_oop_chapter_04_08
folder:
open class SmoothFoxTerrier: TerrierDog {
open override class var averageNumberOfChildren: Int {
get {
return 6;
}
}
open override var breed: String {
get {
return "Smooth Fox Terrier dog"
}
}
private func initializeSmoothFoxTerrier() {
print("SmoothFoxTerrier created")
}
override init(age: Int, name: String, favoriteToy: String) {
super.init(age: age, name: name, favoriteToy: favoriteToy)
initializeSmoothFoxTerrier()
}
override init(age: Int, isPregnant: Bool, name: String,
favoriteToy: String) {
super.init(age: age, isPregnant: isPregnant, name: name,
favoriteToy: favoriteToy)
initializeSmoothFoxTerrier()
}
open override class func printALeg() {
print("!", terminator: String())
}
open override class func printAChild() {
// Print Dog's face emoji
print(String(UnicodeScalar(0x01f415)!), terminator: String())
}
}
The class has the same initializers that we coded for its superclass. Both initializers invoke the initializers defined in the superclass and then call the initializeSmoothFoxTerrier
private method. The method prints a message indicating that a SmoothFoxTerrier
class is created. The class overrides the getter method to return "Smooth Fox Terrier"
for the breed
computed property that was defined in the Dog
superclass, and was overridden in the TerrierDog
superclass and also in this class. In addition, the class overrides the getter method for the averageNumberOfChildren
type property to return 6
.
The class also overrides both the printALeg
and printAChild
type methods inherited from Animal
and overridden in the TerrierDog
superclass. The printALeg
method prints an exclamation mark symbol (!
). The printAChild
method prints a dog emoji. This emoji is different from the dog's face emoji that the superclass method printed.
After we code all the classes, we can write code in the Playground to create instances of both the TerrierDog
and SmoothFoxTerrier
classes. The following are the first lines that create an instance of the SmoothFoxTerrier
class named tom
and use one of its initializers that doesn't require the isPregnant
argument. The code file for the sample is included in the swift_3_oop_chapter_04_08
folder:
var tom = SmoothFoxTerrier(age: 5, name: "Tom", favoriteToy: "Sneakers") tom.printBreed() tom.printBreedFamily()
The following lines show the messages displayed in the Playground after we enter the previous code:
Animal created Mammal created DomesticMammal created Dog created TerrierDog created SmoothFoxTerrier created Smooth Fox Terrier dog Terrier
First, the Playground prints the messages displayed by each initializer that is called. Remember that each initializer calls its base class initializer and prints a message indicating that an instance of the class is created. We don't have six different instances; we just have one instance that calls the chained initializers of six different classes to perform all the necessary initialization to create an instance of SmoothFoxTerrier
. If we execute the following lines in the Playground, all of them will display true
as a result, because tom
belongs to the Animal
, Mammal
, DomesticMammal
, Dog
, TerrierDog
, and SmoothFoxTerrier
classes. The code file for the sample is included in the swift_3_oop_chapter_04_08
folder:
print(tom is Animal) print(tom is Mammal) print(tom is DomesticMammal) print(tom is Dog) print(tom is TerrierDog) print(tom is SmoothFoxTerrier)
The following screenshot shows the results of executing the previous lines in the Playground. Note that the Playground uses an icon to let us know that all the is
tests will always evaluate to true
:
We coded the printBreed
and printBreedFamily
methods within the Dog
class, and we didn't override these methods in any of the subclasses. However, we overrode the properties whose content these methods display: breed
and breedFamily
. The TerrierDog
class overrode both properties, and the SmoothFoxTerrier
class overrode the breed
property again.
The following line creates an instance of the TerrierDog
class named vanessa
. Note that in this case, we will create an instance of the superclass of the SmoothFoxTerrier
class and use the initializer that requires the isPregnant
argument. The code file for the sample is included in the swift_3_oop_chapter_04_08
folder:
var vanessa = TerrierDog(age: 6, isPregnant: true, name: "Vanessa", favoriteToy: "Soda bottle")
The next lines call the printLegs
and printChildren
instance methods for tom
, the instance of SmoothFoxTerrier
, and vanessa
, which is the instance of TerrierDog
. The code file for the sample is included in the swift_3_oop_chapter_04_08
folder:
tom.printLegs() tom.printChildren() vanessa.printLegs() vanessa.printChildren()
We coded these methods in the Animal
class, and we didn't override them in any of its subclasses. Thus, when we call these methods for either tom
or vanessa
, Swift will execute the code defined in the Animal
class. The printLegs
method calls the printALeg
type method for the type retrieved from the instance in which we will call it as many times as the value of the numberOfLegs
type property. The printChildren
method calls the printAChild
type method for the type retrieved from the instance in which we will call it as many times as the value of the averageNumberOfChildren
type property.
Both the TerrierDog
and SmoothFoxTerrier
classes overrode the printALeg
and printAChild
type methods, and the averageNumberOfChildren
type property. Thus, our call to the same methods will produce different results. The following screenshot shows the output generated for tom
and vanessa
. Note that tom
prints four exclamation marks (!
) to represent its legs, while vanessa
prints four pipes (|
). Regarding children, tom
prints six dog emoji icons, while vanessa
prints four dog's face emoji icons. Both instances run the same code for the two type methods that we called. However, each class overrode type properties that provide different values and cause the differences in the output:
The following lines call the bark
method for the instance named tom
with a different number of arguments. This way, we take advantage of the bark
method that we overloaded four times with different arguments. Remember that we coded the four bark
methods in the Dog
class and the SmoothFoxTerrier
class inherits the overloaded methods from this superclass through its hierarchy tree:
tom.bark() tom.bark(times: 2) tom.bark(times: 2, otherDomesticMammal: vanessa) tom.bark(times: 3, otherDomesticMammal: vanessa, isAngry: true)
The following lines show the results of calling the methods with different arguments:
Tom: Woof Tom: Woof Woof Tom to Vanessa: Woof Woof Tom to Vanessa: Grr Woof Woof Woof
If we go back to the code that declared the bark
method in the Dog
class in the Playground, we will notice that the SmoothFoxTerrier
class name is displayed on the right-hand side for each method that we used from the Dog
class:
The following lines show the code for the Cat
class that inherits from DomesticMammal
. The code file for the sample is included in the swift_3_oop_chapter_04_09
folder:
open class Cat: DomesticMammal {
open static override var numberOfLegs: Int {
get {
return 4;
}
}
open static override var abilityToFly: Bool {
get {
return false;
}
}
open override class var averageNumberOfChildren: Int {
get {
return 6;
}
}
private func initializeCat() {
print("Cat created")
}
override init(age: Int, name: String, favoriteToy: String) {
super.init(age: age, name: name, favoriteToy: favoriteToy)
initializeCat()
}
override init(age: Int, isPregnant: Bool, name: String,
favoriteToy: String) {
super.init(age: age, isPregnant: isPregnant, name: name,
favoriteToy: favoriteToy)
initializeCat()
}
open func printMeow(times: Int) {
var meow = "(name): "
for _ in 0 ..< times {
meow += "Meow "
}
print(meow)
}
open override func talk() {
printMeow(times: 1)
}
open override class func printALeg() {
print("*_*", terminator: String())
}
open override class func printAChild() {
// Print grinning cat face with smiling eyes emoji
print(String(UnicodeScalar(0x01F638)!), terminator: String())
}
}
The Cat
class overrides the talk
method inherited from DomesticMammal
. As it happened with the overridden properties in other subclasses, we just added the override
keyword to the method declaration. The method doesn't invoke the method with the same name for its superclass, that is, we don't use the super
keyword to invoke the talk
method defined in DomesticMammal
. The talk
method in the Cat
class invokes the meow
method with 1
as the number of times. The meow
method prints the representation of a cat meowing--that is, a Meow
message--the number of times specified in its times
argument. We will learn how to code this method with a functional programming approach in the forthcoming chapters. In this case, we use a simple for
loop.
As it happened with other classes that we analyzed before, the class overrides the getter method for the averageNumberOfChildren
type property. The class also overrides both the printALeg
and printAChild
type methods inherited from Animal
. The printALeg
method prints *_*
, and the printAChild
method prints a grinning cat face with smiling eyes emoji.
The following lines show the code for the Bird
class that inherits from Animal
. The code file for the sample is included in the swift_3_oop_chapter_04_09
folder:
open class Bird: Animal {
open var feathersColor: String = String()
open static override var numberOfLegs: Int {
get {
return 2;
}
}
private func initializeBird(feathersColor: String) {
self.feathersColor = feathersColor
print("Bird created")
}
override init(age: Int) {
super.init(age: age)
initializeBird(feathersColor: "Undefined / Too many colors")
}
init(age: Int, feathersColor: String) {
super.init(age: age)
initializeBird(feathersColor: feathersColor)
}
}
The Bird
class inherits the members from the previously declared Animal
class and adds a new String
stored property initialized with the default empty string value. The class overrides the numberOfLegs
type property to return 2
and disables any subclass's chance to override this type property again using the static
keyword. Note that this class declares two initializers, as it happened with the Mammal
class that also inherited from Animal
. One of the initializers requires an age
value to create an instance of the class, as it happened with the Animal
initializer. The other initializer requires the age
and feathersColor
values. If we create an instance of this class with just one age
argument, Swift will use the first initializer. If we create an instance of this class with two arguments, an Int
value for age
and a String
value for feathersColor
, Swift will use the second initializer. Again, we overloaded the initializer and provided two different initializers.
The two initializers use the super
keyword to call the inherited init
method from the base class or superclass, that is, the init
method defined in the Animal
class. Once the initialized code in the superclass finishes its execution, each initializer calls the initializeBird
private method that initializes the feathersColor
stored property with the value received as an argument or the default "Undefined / Too many colors"
value in case it isn't specified.
The following lines show the code for the DomesticBird
class that inherits from Bird
. The preceding class simply adds a name
stored property and allows the initializers to specify the desired name for the domestic bird. The code file for the sample is included in the swift_3_oop_chapter_04_09
folder:
open class DomesticBird: Bird {
open var name = String()
private func initializeDomesticBird(name: String) {
self.name = name
print("DomesticBird created")
}
init(age: Int, name: String) {
super.init(age: age)
initializeDomesticBird(name: name)
}
init(age: Int, feathersColor: String, name: String) {
super.init(age: age, feathersColor: feathersColor)
initializeDomesticBird(name: name)
}
}
The following lines show the code for the DomesticCanary
class that inherits from DomesticBird
. The code file for the sample is included in the swift_3_oop_chapter_04_09
folder:
open class DomesticCanary: DomesticBird {
open override class var averageNumberOfChildren: Int {
get {
return 5;
}
}
private func initializeDomesticCanary() {
print("DomesticCanary created")
}
override init(age: Int, name: String) {
super.init(age: age, name: name)
initializeDomesticCanary()
}
override init(age: Int, feathersColor: String, name: String) {
super.init(age: age, feathersColor: feathersColor, name: name)
initializeDomesticCanary()
}
open override class func printALeg() {
print("^", terminator: String())
}
open override class func printAChild() {
// Print bird emoji
print(String(UnicodeScalar(0x01F426)!), terminator: String())
}
}
The class overrides the two initializers declared in the superclass to display a message whenever we create an instance of the DomesticCanary
class. In addition, the class overrides the averageNumberOfChildren
type property and the printALeg
and printAChild
methods.
After we declare the new classes, we will create the following two functions outside any class declaration that receives an Animal
instance as an argument, that is, an Animal
instance or an instance of any subclass of Animal
. Each function calls a different instance method defined in the Animal
class: printChildren
and printLegs
. The code file for the sample is included in the swift_3_oop_chapter_04_09
folder:
public func printChildren(animal: Animal) { animal.printChildren() } public func printLegs(animal: Animal) { animal.printLegs() }
Then, the following lines create instances of the next classes, which are TerrierDog
, Cat
, and DomesticCanary
. Then, the lines call the printChildren
and printLegs
functions with the previously created instances as arguments. The code file for the sample is included in the swift_3_oop_chapter_04_09
folder:
var pluto = TerrierDog(age: 7, name: "Pluto", favoriteToy: "Teddy bear") var marie = Cat(age: 4, isPregnant: true, name: "Marie", favoriteToy: "Tennis ball") var tweety = DomesticCanary(age: 2, feathersColor: "Yellow", name: "Tweety") print("Meet their children") print(pluto.name) printChildren(animal: pluto) print(marie.name) printChildren(animal: marie) print(tweety.name) printChildren(animal: tweety) print("Look at their legs") print(pluto.name) printLegs(animal: pluto) print(marie.name) printLegs(animal: marie) print(tweety.name) printLegs(animal: tweety)
The following screenshot shows the results of executing the previous lines in the Playground. The three instances become an Animal
argument for the different methods. However, the values used for the properties aren't those declared in the Animal
class. The call to the printChildren
and printLegs
methods take into account all the overridden members because each instance is an instance of a subclass of Animal
:
Both the functions can only access the members defined in the Animal
class for the instances that they receive as arguments because their type within the function is Animal
. We can unwrap TerrierDog
, Cat
, and DomesticCanary
that are received in the animal
argument if necessary. However, we will work with these scenarios later as we cover more advanced topics.
Now, we will create another function outside any class declaration that receives a DomesticMammal
instance as an argument, that is, a DomesticMammal
instance or an instance of any subclass of DomesticMammal
. The following function calls the talk
instance method defined in the DomesticMammal
class. The code file for the sample is included in the swift_3_oop_chapter_04_10
folder:
public func forceToTalk(domesticMammal: DomesticMammal) { domesticMammal.talk() }
Then, the following few lines call the forceToTalk
function with the TerrierDog
and Cat
instances as arguments: pluto
and marie
. The code file for the sample is included in the swift_3_oop_chapter_04_10
folder:
forceToTalk(domesticMammal: pluto) forceToTalk(domesticMammal: marie)
The call to the same method for a DomesticMammal
instance received as an argument produces different results because dogs bark and cats meow. However, both are domestic mammals, and they produce specific sounds instead of talking. We defined the representation of the sounds they produce in the Dog
and Cat
classes. The following lines show the results of the two function calls:
Pluto: Woof Marie: Meow
18.118.163.159