This is Part 6 of a 100 part series introducing you to TypeScript if you’ve not used it before; which will give you a brush up on things you already know if you’ve used it, and maybe introduce you to some new things that you might not have come across before. In this post, we will look at what inheritance is, and how we can apply it with classes. We will also discuss how to make classes abstract and use the protected keyword, and introduce the ability to cast one type to another.
On day 5, we looked at how to implement interfaces to say what behaviour a class can have. I said, at the end, that we were moving into the territory of doing inheritance. Today, we are going to look at how we can implement interfaces, inherit from classes and a whole lot more.
What is Inheritance?
If you have never worked with an Object Oriented language before, the concept of inheritance might seem a little odd at first but it’s actually pretty straight forward. To explain inheritance, I am going to go back to an old favourite of mine, the guitar. I am going to demonstrate with a “pseudo class” structure, how to use inheritance. (A pseudo-class is one that demonstrates a class, but isn’t written in any particular language). To start with, I am going to look at the properties that a guitar might have.
- Necks (typically one, but guitarists such as Jimmy Page sometimes used guitars with more than one neck)
- Strings
- Pickups (for electric guitars)
- Pickup selector (for electric guitars)
- Volume controls (for electric guitars)
- Tone controls (for electric guitars)
- A bridge (the bit that the strings go into in the body of the guitar)
- Strap connectors
- A body
- A manufacturer
- A make
- Serial number
- Sound hole (for acoustic / electro-acoustic guitars)
- Headstock (the bit at the top of most guitars)
- Tuners
That’s not an exhaustive list of all of the things that we can use to make a guitar, but it gives us a good starting point. Before I start creating my classes, I need to decide what is common and what is specialized; in other words, anything that all guitars share is a common feature, and things that are specific to a particular type of guitar are specialized. Anything that is common goes into something we call a base class; a base class is something we will inherit from that has properties and behaviours that are shared.
It is very tempting to jump in and say that the starting point here is a guitar, but I am going to take a step back. While I listed properties for a guitar, it is important to remember that a guitar belongs to a family of things we call musical instruments and there are certain features of a guitar that are common across all commercial musical instruments; if I were writing an application for a music shop, I would want to start with those features as the base for my guitars.
Musical Instruments as hierarchies
I will start with a MusicalInstrument
pseudo class and put the features in there that are common.
Class MusicalInstrument
Manufacturer
Make
SerialNumber
End Class
You might be surprised to see that I haven’t said what type of musical instrument it is that we are dealing with in there. The reason I haven’t done that is down to inheritance; I am going to use inheritance to say what type of instrument it is that I am dealing with so if I wanted to have a Piano
class, I might look at that as being a type of Keyboard
, which is a type of MusicalInstrument
. This would give me something like this:
Class Keyboard InheritsFrom MusicalInstrument
... Features common to all keyboard type instruments
End Class
Class Piano InheritsFrom Keyboard
... Features that are specific to a piano
End Class
Going back to the guitar scenario, I might want to create my hierarchy like this:
Class Guitar InheritsFrom MusicalInstrument
Neck
Headstock
Tuners
End Class
Class AcounsticGuitar InheritsFrom Guitar
SoundHole
End Class
Class ElectricGuitar InheritsFrom Guitar
Pickups
VolumeControl
ToneControls
End Class
I’m not going to add every property to these classes, but this should give you an idea as to what the inheritance chain should look like. What we see in all of this is that inheritance is about creating an object from a hierarchy.
Class Inheritance with TypeScript
On Day 5, I demonstrated how I can use implements
to “inherit” from an interface (technically, we call this implementation rather than inheritance but people sometimes think of interface implementation as inheritance). I am now going to demonstrate how to inherit from classes. To make this interesting, I am going to start with the same validation interface that I implemented in the last post.
interface Validate {
IsValid(): boolean;
}
If you remember, the last post demonstrated the minimum length validation and maximum length validation but there was code that was repeated between the two classes. What I want to do is create a base class that does all that is necessary for the common code. I am going to start with this code.
class LengthValidationBase implements Validate {
constructor(protected text: string, protected length: number) {}
IsValid(): boolean {
}
}
This code will not compile because IsValid
doesn’t return anything. I have two choices here. I can either return a default value or I can use “tricks” to avoid returning the value here. The trick that I am going to use is to make this class something called an abstract
class. What this means is that this class cannot be created on its own; it must be inherited from, and the inherited classes can be instantiated.
By creating an abstract
class, I am able to create an abstract
method. What this is, is a piece of code that, effectively, says “I am not going to give you an implementation here, but anything that inherits from me must add its own implementation.” Unsurprisingly, the keyword to make a class or method abstract
is, well it’s abstract
. With this, my code now looks like this:
abstract class LengthValidationBase implements Validate {
constructor(protected text: string, protected length: number) {}
IsValid(): boolean {
return this.CheckForValidity();
}
public Debug() {
console.log(`Checking whether the length check of ${this.length} for
${this.text} is valid returns ${this.CheckForValidity()}`);
}
protected abstract CheckForValidity(): boolean;
}
There are a few things in my class here that I want to talk about. Let’s start with the constructor. In an earlier post, I talked about how I can declare class-level variables directly in the constructor signature. This is one of those really great features that TypeScript gives us that you won’t see in languages like C#. What we haven’t seen before, though, is this funny protected
keyword. What does this mean?
If I want something to be visible only inside the class, I mark a method of field as private
. This means that I cannot see this variable/method from outside the class. If I inherit from the class, I still can’t see that variable. If you are thinking that I need to see the values in text and length in my derived class (when we inherit from a class, we say that we have derived from the class), you would be correct. The way that we say that a method or function cannot be seen from outside the class, but can be seen from a derived class is to use the protected
keyword.
The CheckForValidity
method is interesting. This is an abstract
method that I am going to call from the IsValid
method, giving us the boolean return value that IsValid
is expecting. You can see that this does not actually do anything – we can think of this as being a signature or contract that says “when you inherit from me, you must supply an implementation for this method”.
Note: You can only add abstract
methods inside abstract
classes. If your class isn’t marked as abstract
, then you can’t create abstract
methods in there.
Here’s a quick question. Do you think I can create a private abstract
method? The answer to this is no, if I tried to do this, TypeScript would tell me that I cannot create a private abstract
method. The message I would get is The “private” modifier cannot be used with the “abstract” modifier. When you think about it, this makes perfect sense. A private
method cannot be seen outside the class, but an abstract
method says that the code must be added in an inherited class.
Something you might be wondering about. Why have I added a Debug
method? It’s not described in our interface, so why have I added one? I wanted to provide the ability to write out debug information about what the class is doing and, as the ability to debug doesn’t have anything to do with the validation interface. By using a backtick `
instead of an apostrophe '
in the console log, I am able to do something called string
interpolation. This is the funny-looking syntax in the string
where the values inside ${}
are printed out directly.
Let’s revisit our minimum and maximum length validation. By using inheritance, I move from this.
class MinimumLengthValidation implements Validate {
constructor(private text: string, private minimumLength: number) {}
IsValid(): boolean {
return this.text.length >= this.minimumLength;
}
}
class MaximumLengthValidation implements Validate {
constructor(private text: string, private maximumLength: number) {}
IsValid(): boolean {
return this.text.length <= this.maximumLength;
}
}
to:
class MaximumLengthValidation extends LengthValidationBase {
protected CheckForValidity(): boolean {
return this.text.length <= this.length;
}
}
class MinimumLengthValidation extends LengthValidationBase {
protected CheckForValidity(): boolean {
return this.text.length >= this.length;
}
}
It may not seem like I have removed a lot of code, but that’s not the main reason for doing inheritance. The main reason for inheritance is to remove code duplication. In the code example above, I only have to declare the constructor once. This is a simple and largely trivial example, but it means that any code I write that inherits from my base class results in exactly the same behaviour in building up the class. I have completely removed the IsValid
code from my derived classes, by providing an implementation for my abstract
method (note that abstract
has been removed from the method name here because I am supplying the real code).
Testing the Implementation
I am going to test the implementation of my code. To start with, I am going to use the same method to add the validation items that I did in the last post.
const validation: Validate[] = [];
validation.push(new MinimumLengthValidation('ABC12345', 10));
validation.push(new MinimumLengthValidation('ABC12345AB12345', 10));
validation.push(new MaximumLengthValidation('ABC12345', 10));
validation.push(new MaximumLengthValidation('ABC12345AB12345', 10));
We need to pay attention to the fact that I have created this array as an array of Validate
instances. This is going to become important to us because, while I am looping over the validation examples here, I want to call the Debug
method, but as this isn’t present in my Validate
interface, I can’t do this directly.
validation.forEach((val: Validate) => {
console.log(val.IsValid());
});
You see that bit where I say I want to do something clever here? What I want to do here is to do something called a cast. What a cast is, is a way to change one type into another. What I want to do here is to cast the Validate
implementation into a LengthValidationBase
class so that I can call Debug
. To do this, I am going to write the following code, where I use the as
keyword to say what I want to cast my class to.
const lengthValidation = val as LengthValidationBase;
lengthValidation.Debug();
In future posts, I am going to deal further with casting, because there are all sorts of cool things we can do with it – far more than we can cover in this one post. For the moment, I will leave you with what my code looks like right now.
validation.forEach((val: Validate) => {
console.log(val.IsValid());
const lengthValidation = val as LengthValidationBase;
lengthValidation.Debug();
});
Conclusion
In this article, we have looked at what inheritance is, and how we can apply it with classes. We discussed making classes abstract
and using the protected
keyword. We also introduced the ability to cast one type to another. As always, the code for this article is available on Github.