In this chapter, you will learn about one of the most important topics of object-oriented programming: inheritance. We will work with examples on how to create class hierarchies, override methods, overload methods, work with inherited initializers, and overload operators. In addition, you will learn about polymorphism and basic typecasting.
You're reading from Object???Oriented Programming with Swift 2
So far, we created classes to generate blueprints for real-life objects. Now, it is time to take advantage of the more advanced features of object-oriented programming and start designing a hierarchy of classes instead of working with isolated classes. First, we will design all the classes that we need based on the requirements, and then, we will use the features available in Swift to code the design.
We worked with classes to represent superheroes. Now, let's imagine that we have to develop a very complex app that requires us to work with hundreds of types of domestic animals. We already know that the app will start working with the following four domestic animal species:
Dog (Canis lupus familiaris)
Guinea pig (Cavia porcellus)
Domestic canary (Serinus canaria domestica)
Cat (Felis silvestris catus)
The previous list provides the scientific name for each domestic animal species. Of course, we will work with the most common name for...
When a class inherits from another class, it inherits all the elements that compose the parent class, which is also known as a superclass. The class that inherits the elements is known as a subclass. For example, the Mammal
subclass inherits all the properties, instance fields or instance attributes, and class fields or class attributes defined in the Animal
superclass.
The Animal
abstract class is the baseline for our class hierarchy. We say that it is an abstract class because we shouldn't create instances of the Animal
class; instead, we must create instances of the specific subclasses of Animal
. However, we must take into account that Swift doesn't allow us to declare a class as an abstract class.
We require each Animal
to specify its age, so we will have to specify the age when we create any Animal
—that is, any instance of any Animal
subclass. The class will define an age property and display a message whenever an animal is created. The class defines three type...
The following lines show the code for the Animal
base class in Swift. The class header doesn't specify a base class, so this class will become our base class for the other classes:
public class Animal { public static var numberOfLegs: Int { get { return 0; } } public static var averageNumberOfChildren: Int { get { return 0; } } public static var abilityToFly: Bool { get { return false; } } public var age: Int init(age : Int) { self.age = age print("Animal created") } public static func printALeg() { preconditionFailure("The pringALeg method must be overriden") } public func printLegs() { for _ in 0..<self.dynamicType.numberOfLegs { self.dynamicType.printALeg() } print(String()) } public static func printAChild()...
Swift allows us to define a method with the same name many times with different arguments. This feature is known as method overloading. In some cases, as in our previous example, we can overload the initializer. However, it is very important to mention that a similar effect might be achieved with optional parameters or default values for specific arguments.
For example, we can take advantage of method overloading to define multiple versions of the bark
method that we have to define in the Dog
class. However, it is very important to avoid code duplication when we overload methods.
Sometimes, we define a method in a class, and we know that a subclass might need to provide a different version of the method. When a subclass provides a different implementation of a method defined in a superclass with the same name, arguments, and return type, we say that we are overriding a method. When we override a method, the implementation in the subclass overwrites the code...
First, we will try to override the numberOfLegs
type property that the Dog
class will inherit from the Animal
base class. We will face an issue and solve it. The following lines show the code for a simplified version of the Dog
class that inherits from DomesticMammal
and just tries to override the numberOfLegs
type property:
public class Dog: DomesticMammal { public static override var numberOfLegs: Int { get { return 4; } } }
After we enter the previous lines in the Playground, we will see the following error message in the line that tries to override the numberOfLegs
type property: error: class var overrides a 'final' class var
. The following screenshot shows the error in the Playground:
The following lines show the code for the complete Dog
class that inherits from DomesticMammal
. Note that the following code replaces the previous Dog
class that just declared an overridden type property:
public class Dog: DomesticMammal { public static override var numberOfLegs: Int { get { return 4; } } public static override var abilityToFly: Bool { get { return false; } } public var breed: String { get { return "Just a dog" } } public var breedFamily: String { get { return "Dog" } } private func initializeDog() { print("Dog created") } public override init(age: Int, name: String, favoriteToy: String) { super.init(age: age, name: name, favoriteToy: favoriteToy) initializeDog() } public override init(age: Int...
We can use the same method—that is, the same name and arguments—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 the instance, we will see a different result after invoking the same method with...
Swift allows us to redefine specific operators to work in a different way based on the classes to which we apply them. For example, we can make comparison operators, such as less than (<
) and greater than (>
), return the results of comparing the age
value when they are applied to instances of Dog
.
Tip
The redefinition of operators to work in a specific way when applied to instances of specific classes is known as operator overloading. Swift allows us to overload operators through the usage of operator functions.
An operator that works in one way when applied to an instance of a class might work differently on instances of another class. We can also redefine the overloaded operators to work on specific subclasses. For example, we can make the comparison operators work in a different way in a superclass and its subclass.
We want to be able to compare the age of the different Animal
instances using the following binary operators in Swift:
Less than...
We already declared an operator function that allows any instance of Animal
or its subclasses to use the postfix increment (++
) operator. However, sometimes we want to specify a different behavior for one of the subclasses and its subclasses.
For example, we might want to express the age of dogs in the age
value that is equivalent to humans. We can declare an operator function for the postfix increment (++
) operator that receives a Dog
instance as an argument and increments the age value 7
years instead of just one. The following lines show the code that achieves this goal:
public postfix func ++ (dog: Dog) { dog.age += 7 }
The following lines create an instance of the SmoothFoxTerrier
class named goofy
, print the age for goofy
, apply the postfix ++
operator, and print the new age. Because SmoothFoxTerrier
is a subclass of Dog
, Swift invokes the operator function that receives a Dog
instance instead of invoking the one that receives an...
Create operator functions to allow us to determine whether two DomesticMammal
instances are equal or not with the ==
and !=
operators. We will consider the instances to be equal when their age
, name
, and favoriteToy
properties have the same value.
Create the following three new subclasses of the TerrierDog
class:
AiredaleTerrier
: This is an Airedale Terrier breedBullTerrier
: This is a Bull Terrier breedCairnTerrier
: This is a Cairn Terrier breed
Add the necessary code to these classes to print text that represents the children in a different way than we did for the SmoothFoxTerrier
class. Test the results by creating an instance of each of these classes and calling the printChildren
method.
When you use the
static var
keywords to declare a type property:You cannot override the type property in the subclasses.
You can override the type property in the subclasses.
You can override the type property only in the superclass.
When you use the
class var
keywords to declare a type property:You cannot override the type property in the subclasses.
You can override the type property in the subclasses.
You can override the type property only in the superclass.
When you use the
final
keyword to declare an instance method:You cannot override the instance method in the subclasses.
You can override the instance method in the subclasses.
You can override the instance method only once—that is, in just one subclass.
Polymorphism means:
We can call the same method—that is, the same name and arguments—in instances of classes that aren't included in the same hierarchy tree.
We can use the same method—that is, the same name and arguments—to cause different things to happen according to the...
In this chapter, you learned how to take advantage of simple inheritance to specialize a base class. We designed many classes from top to bottom using chained initializers, type properties, computed properties, stored properties, and methods. Then, we coded most of these classes in the interactive Playground, taking advantage of different mechanisms provided by Swift.
We took advantage of operator functions to overload operators that we could use with the instances of our classes. We overrode and overloaded initializers, type properties, and methods. We took advantage of one of the most exciting object-oriented features: polymorphism.
Now that we have learned to work with inheritance, abstraction, and specialization, we are ready to work with protocols, which is the topic of the next chapter.