The DRY Principle
Before delving into the inheritance magic, let’s take a minute to introduce the DRY principle. DRY is an acronym for Don’t Repeat Yourself. In essence, it means that code shouldn’t be duplicated. If, in your program or library, you have some code that does something, you shouldn’t duplicate it somewhere else to replicate this functionality. Each piece of code should be unique and take care of one particular task. Each time you feel like copy-pasting a bit of code, an alarm should trigger in your head and you should pause and figure out whether there’s an alternative approach that doesn’t involve duplicating code.
For example, if you find yourself repeating the same group of statements multiple times in your program, take this group of statements, wrap them in a function and call this function when you need it. Okay, that’s a mouthful and it sounds kind of complicated but if you recall your second lesson on functions, we did exactly that! Let’s revisit it, to illustrate the DRY principle:
Not DRY:
func Main() { Stand up Go to the coffee machine Press the 'coffee' button Wait for the cup Take the cup Deliver the cup to John Stand up Go to the coffee machine Press the 'coffee' button Wait for the cup Take the cup Deliver the cup to Linda Stand up Go to the coffee machine Press the 'coffee' button Wait for the cup Take the cup Deliver the cup to Tyrion }
DRY:
func MakeCoffee() -> Cup { Stand up Go to the coffee machine Press the 'coffee' button Wait for the cup Return the cup } func Main(){ Deliver MakeCoffee() to John Deliver MakeCoffee() to Linda Deliver MakeCoffee() to Tyrion }
The above examples both produce the same result but the second one is shorter to type, easier to read and faster to change. For example, if in the future we had to change the logic or add a statement such as Insert coin
before Press the 'coffee' button
, we would only need to add it once within the MakeCoffee
function. As we will soon see, designing software with change in mind is an excellent practice. The DRY principle allows us to do just that, whilst also keeping our code succinct and readable.
Similar Classes
Now that you know how to write and read what a class does, have a look at these two similar yet different classes. Don’t rush. Take your time to understand what they do and which objects they can create:
Cat
class Cat { Name as String Weight as Float Color as String func Init() { self.Name = "Unknown" self.Weight = 0.0 self.Color = "Undefined" } func Walk() -> String { return self.Name + " is walking." } func Sleep() -> String { return self.Name + " is sleeping." } }
Dog
class Dog { Name as String Weight as Float Color as String func Init() { self.Name = "Unknown" self.Weight = 0.0 self.Color = "Undefined" } func Walk() -> String { return self.Name + " is walking." } func Sleep() -> String { return self.Name + " is sleeping." } }
In the code above, we have two classes. One that represents a Cat
and one that represents a Dog
. However there is so much repeated code, it hurts! In reality, the only difference between our computerized dog and cat is the class name. Inheritance to the rescue!
The Base Class
Consider the code below, that presents 3 classes. Read them and try to figure out what they mean.
Animal
class Animal { Name as String Weight as Float Color as String func Init() { self.Name = "Unknown" self.Weight = 0.0 self.Color = "Undefined" } func Walk() -> String { return self.Name + " is walking." } func Sleep() -> String { return self.Name + " is sleeping." } }
Cat
class Cat: Animal { }
Dog
class Dog: Animal { }
So, did you figure out what these three classes do? The first one, Animal
represents a generic animal. We don’t know exactly which animal, just that its an animal. It has some properties and methods. The Cat
class is said to inherit from Animal
. You see that from the line class Cat: Animal
. The colon (:
) translates to inherit from in programmer’s lingo. In English, it would translate to “is a…..”. Therefore, Cat is an animal.
This “is a” relationship means that all the properties and methods that are implemented within the Animal
class are also available in the Cat
class. Same goes for the Dog
class since Dog is also an Animal. This, in a nutshell, is the concept of inheritance.
Animal
would be described as the base class or parent class.
Cat
and Dog
would each be described as a child class.
Child Class Usage
Let’s write a small program using the classes we just created. Let’s also pretend that we saved our three classes, Animal
, Cat
and Dog
in a library named AnimalsLibrary
.
What would you expect to be displayed on the screen if we ran this program?
Think it through and try not to peek at the answer below until you’ve got your answer!
import AnimalsLibrary func Main() { myCat = new Cat myDog = new Dog myCat.Name = "Spock" myDog.Name = "Gandalf" Display myCat.Sleep() Display myDog.Walk() }
…and this is the result:
Spock is sleeping. Gandalf is walking.
How was this possible? Because of inheritance. Both myCat
object and myDog
object inherited their properties and methods from the Animal
class.
Specific Members
So far, the examples have been trivial because both Cat
and Dog
had the exact same members (properties and methods). What if we wanted to have a method named Eat
but only for the Cat
, not the Dog
? Easy! We would implement the Eat
method directly in the Cat
class instead of doing so within the Animal
class.
Like so (pay attention to the modified Cat
class):
Animal
class Animal { Name as String Weight as Float Color as String func Init() { self.Name = "Unknown" self.Weight = 0.0 self.Color = "Undefined" } func Walk() -> String { return self.Name + " is walking." } func Sleep() -> String { return self.Name + " is sleeping." } }
Cat
class Cat: Animal { func Eat() -> String { return self.Name + " is eating." } }
Dog
class Dog: Animal { }
Now, a cat can eat but a dog can’t. A fairly unacceptable state of affairs for any self respecting dog! The dog cannot eat because there is no Eat
method inside the Dog
class nor within the Animal
class. A dog object wouldn’t be able to call the Eat
method because it can’t inherit it from Animal
and because a dog is not a cat.
Let’s modify our previous program:
import AnimalsLibrary func Main() { myCat = new Cat myDog = new Dog myCat.Name = "Spock" myDog.Name = "Gandalf" Display myCat.Eat() Display myDog.Eat() }
The result on the screen would be:
Spock is eating.
Overriding
Lets take our examples a step further. Assume we want the Sleep
method to return “name-of-the-dog is taking a nap.” for a dog object and “name-of-the-cat is sleeping.” for a cat object. We would achieve this by providing an override to the Sleep
method within the Dog
class. Put simply, this means that we would provide our own version of the Sleep
method in the Dog
class rather than letting it use the Sleep
method implemented in the Animal
base class. Pay close attention to the changes in the Dog
class:
Animal
class Animal { Name as String Weight as Float Color as String func Init() { self.Name = "Unknown" self.Weight = 0.0 self.Color = "Undefined" } func Walk() -> String { return self.Name + " is walking." } func Sleep() -> String { return self.Name + " is sleeping." } }
Cat
class Cat: Animal { func Eat() -> String { return self.Name + " is eating." } }
Dog
class Dog: Animal { func Sleep() -> String { return self.Name + " is taking a nap." } }
Again, let’s modify our program as below:
import AnimalsLibrary func Main() { myCat = new Cat myDog = new Dog myCat.Name = "Spock" myDog.Name = "Gandalf" Display myCat.Sleep() Display myDog.Sleep() }
Running the program will now display this on screen:
Spock is sleeping. Gandalf is taking a nap.
When you call a method or want to access a property on an object, the computer will look for it within the class that was used to create the object. If it doesn’t find it there, it will look for it in the base class. If it doesn’t find it in the base class, the computer will output an error saying that the member was not found.
That is why, here, when calling Sleep
on myCat
, the computer looked for the method in the Cat
class, didn’t find it but knew that Cat
inherited from Animal
so it looked for it in Animal
, found it and used it. When calling Sleep
on myDog
, the computer looked for it in the Dog
class, found it there and used it without even trying to find a Sleep
method in Animal
.
Summary
You now know that repeating yourself is not a good practice and that is why we use the DRY principle. The DRY principle can be applied to object-oriented programming by using inheritance. A child class inherits from a base class and thus can access all the members implemented in the base class. Some programmers use the term parent class for a base class. We’ve also seen that a child class can implement members such as methods and properties that are not present in the base class. And we learned that we can override methods from a base class inside a child class; that is provide our own version of a method that already exists in a base class.
Congratulations! You’ve made it through some challenging lessons. The last four lessons on object-oriented programming were heavy on theory. All of this fuzzy theory will soon be put into practice when we learn a real programming language and your efforts will start to pay dividends as you see results on your screen. Keep up the good work!
Important Words
- DRY principle
- Inheritance
- Base class / parent class
- Child class
- Override
Exercise
Since we’ve concluded our basic introduction to object-oriented programming, now is the perfect time to assess your understanding. Could you devise a program that would make use of a car, a truck and a motorbike? Write the pseudocode for each class, wrap them in a library and then create the program that would use these classes. Share your code in the Forums, so that your fellow trainee coders (and OptionalBits instructors) can see, comment, and help correct your work.