Lesson 7 - Inheritance and polymorphism in Swift
In the previous lesson, Arena with warriors in Swift, we made an arena that simulates two warriors in battle.
In today's Swift tutorial, we're going to expand our knowledge of
object-oriented programming. In the introduction of this course, we mentioned
that the OOP is based on three fundamental concepts:
encapsulation, inheritance, and
polymorphism. We're already familiar with the encapsulation and
have used the private
access modifier. Today, we're going to take a
look at the two remaining concepts.
Inheritance
Inheritance is one of the basic features of OOP and is used to create new data structures based on old ones. Let's look at a simple example:
Imagine that we were hired to create a database that will keep track of animals at a zoo There will be two types of users in our system: users and administrators. Users will be common zookeepers who will be able to edit information about the animals, e.g. their weight or wingspan. The administrator will be able to modify animal data as well and add or delete animals from the database. Administrators will have an extra field, a phone number so they can be contacted in case of system failure. It'd be pointless and confusing to fully define both classes since they have many fields and methods in common. Both the user and the administrator will have names, ages and be able to log in and log out. Today will mostly be about going over the concepts, we'll program a fully functioning example in the next lesson (the following code is just a draft):
class User { private var name : String private var password : String private var age : Int func logIn(password: String) -> Bool { // ... } func logOut() -> Bool { // ... } func setWeight(animal: Animal) { // ... } // ... }
We've just made a general outline of the class, but I'm sure you get the gist
of it. Without knowing what inheritance is, we would define our
Admin
class like this:
class Admin { private var name : String private var password : String private var age : String private var telNumber : String func logIn(password: String) -> Bool { // ... } func logOut() -> Bool { // ... } func setWeight(animal: Animal) { // ... } func addAnimal(animal: Animal) { // ... } func removeAnimal(animal: Animal) { // ... } // ... }
We can see that our code is very redundant in this class. All further changes
would have to take a place in both classes, it'd make the code very hard to
maintain. Now, we'll declare the Admin
class using inheritance.
We'll let the Admin
class to be inherited from the
User
class. We don't have to declare properties and methods again,
Swift will add them automatically:
class Admin: User { private var telNumber : String func addAnimal(animal: Animal) { // ... } func removeAnimal(animal: Animal) { // ... } // ... }
We can see that we've used the :
operator for inheritance. In
Swift, inheritance can be often referred to as subclassing.
Visibility and access modifiers for inheritance
In the example above, the private properties won't be accessible to the
descendant. Only the properties and methods with the public
modifier will or the members without any modifier (which is
internal
by default). private
properties and methods
are understood as a special logic that belongs to a certain class. Meaning that,
they are hidden from descendants - even a descendant is actually using this
logic, it's unable to modify it.
Some programming languages provide the special protected
access
modifier which makes methods and properties available only for the class and its
descendants. Swift has no such modifier and the official explanation for it is
the derived class can provide these methods and properties as new
public
/internal
methods and properties.
protected
also wouldn't work very good with extension
s
which is an important part of the Swift language and we'll get to it later.
However, we'd still like to achieve an encapsulation which would hide properties
or methods from the outside, but make them accessible for the descendants of the
class. So we'll help ourselves using a different modifier.
fileprivate
fileprivate
is as close as we can get to achieve good
encapsulation with inheritance. As the name suggests, methods and properties
with this modifier are visible to the given swift file only. In Swift, we can
have multiple classes in a single file and as long they are small enough and
related to each other, it's a valid object design If we put a class and its
descendant into the same file and use the fileprivate
modifier,
we'll achieve the behavior when the descendant sees the class members, but from
other classes, more precisely form different files, these won't be visible.
We'd modify the User
class as follows and move the
Admin
class to the same file as the User
class:
/---class swift class User {
fileprivate var name : String
fileprivate var password : String
fileprivate var age : Int
// ... }
class Admin: User {
// the properties such as name, password and other are accessible here } \--
Now, if we created user and administrator instances, they'd both have
name
properties and logIn()
methods. Swift will
inherit the User
class and redeclare all its properties
automatically.
The advantages of using inheritance are clear. We don't have to copy the same properties to two classes. All we have to do is declare new ones. The rest will be inherited. The benefits are tremendous, we can extend existing components of new methods and reuse them. We don't have to write lots of redundant code, and most importantly - when we change a single field in the parent class, this change is then inherited everywhere automatically. We don't need to change something manually in 20 classes, which could potentially cause errors. We're humans and we'll always make mistakes, we have to choose the right programming techniques to make as less mistakes as possible.
The parent class is sometimes called the ancestor, our User
class, and the class that inherits from it, a descendant, our Admin
class. Descendants may add new methods or override the existing methods of the
parent class (see example below). Sometimes, you may encounter the terms
superclass and subclass which essentially mean the same
thing.
Class hierarchy
Another way to design the object model would be to create a
User
parent class whose sole purpose would be
inheritance. The User
would be then inherited by
Attendant
and Admin
classes. This technique would be
useful if we had lots of user types. In that case, we're talking about a class
hierarchy, which we'll get into further along in this online course. Our example
was simple so two classes were enough. There are also design
patterns that are established and approved object models used for
common situations. If you find it interesting, you can visit our Design pattern courses. All of that
stuff is advanced and interesting, and I promise we'll get to them later as
well.
In object modeling, we draw inheritance as an empty arrow pointing to the parent class. In our case, the graphical notation would look like this:
Inheritance and the data type
The biggest advantage to using inheritance is that if we create a
variable of the parent data type, we can store its descendants in it as
well. We are able to do this because a descendant always includes
everything that the parent class does, therefore, it meets the "requirements" of
the data type (more accurately it provides the necessary interface). What all
descendants are is the parent class with some extra stuff. That way, we are able
to create an array of the User
data type and store there both users
and administrators. Here is an example assigning a descendant to a variable of
the ancestor type and vice-versa:
let u = User("John Newman", 33) var a = Admin("Jack White", 25) // now we assign the administrator to the variable of the User type: u = a // It's fine since the User class is its parent class // If we try in conversely, we'll cause an error: a = u
There are many constructs in Swift, that allow us to work with data types of inherited instances. We'll go over them in detail during the course. For now, we'll demonstrate how we can verify the data type of an instance in a variable:
let u : User = Admin("Jack White", 25) if (u is Admin) { println("The user given has been identified as an administrator") } else { println("The user given is not an administrator") }
We can ask whether an object is of a given type using the
is
operator. The code above checks whether there
is a user or its descendant, administrator, in the u
variable.
Languages can support either single or "multiple inheritance". With the single inheritance, a class can inherit only from one other class. With the "multiple inheritance", a class can inherit from several classes at the same time. Multiple inheritance never became very popular. We'll discuss why and show you how to work around it later. Kotlin only supports the single inheritance. You can encounter multiple inheritance in the C++ language.
Polymorphism
Don't be scared of the obscure name of this technique, it's actually very
simple. Polymorphism allows us to use a unified interface to work with objects
of different types. Imagine that we have, for example, many objects representing
geometric shapes (circles, squares, triangles, and so on). It'd certainly be
helpful if we could communicate with them in some unified way even though
they're different. We can create a GeometricShape
class containing
a color
property and a render()
method. All the
geometric shapes would then inherit the interface from this class. Now you may
be thinking, "But the circle and square object would render differently!." Well,
polymorphism allows us to override the render()
method in** every subclass so it will do what we want. The
interface will be unified and we won't have to think about which method to call
to render different objects.
Polymorphism is often explained using animals. All having a
speak()
method in their interface, but each animal performs it
differently.
The essence of polymorphism is a method or methods, that all the descendants have defined with the same heads, but with different method bodies.
In the next lesson, Arena with a mage in Swift (inheritance and polymorphism) , we'll use polymorphism along with inheritance on our warriors in the arena. We'll add a mage who will inherit warrior's fields and behavior and override the warrior's attacking method to use mana. We won't be able to tell that he's not a pure warrior from the outside since he will have the same interface. It'll be fun