Working with typecasting and polymorphism

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:

Working with typecasting and polymorphism

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:

Working with typecasting and polymorphism

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:

Working with typecasting and polymorphism

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:

Tip

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.

Working with typecasting and polymorphism

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 
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.163.159