As we mentioned in the first blog post of the series, inheritance is a concept that allows us to create a class by building upon the properties and characteristics of another class.
The upside is that you can write reusable code and establish relationships between classes.
A good example involving our SoftwareEngineer
class is that there are different SoftwareEngineer
variations, e.g. BackendEngineer
, FrontendEngineer
, QAEngineer
, DevOpsEngineer
, MachineLearningEngineer
, MobileEngineer
(like me), and more.
Each of these SoftwareEngineer
variations share common properties such as name, years of experience, student and employment statuses, and methods(behaviors) such as getting a job, losing a job, and more.
However, each variation's languages, tech stacks, and job descriptions differ.
To cater to each of these needs we’ll need to:
Create a parent class with the shared properties and methods, i.e. the
SoftwareEngineer
class.Create multiple subclasses for each variation containing both the shared properties and the properties that are unique to each variation.
However, in Kotlin all classes are final by default, meaning you cannot extend them unless you define the relationships between them.
How To Implement A Relationship Between Classes
Using the SoftwareEngineer
class let’s define a parent class and sub-classes as follows:
Add the open keyword to the parent class to make it extendable.
open class SoftwareEngineer(val name: String) { ... }
The
open
keyword overrides the default final class setting by informing the compiler that this class is open and can be extended by other classes.Create a subclass that extends the parent class. To do this you’ll need to start with creating the class header as we do in ordinary class creation, followed by a space, a colon, another space, and a set of parentheses.
class BackendEngineer(name: String) : SoftwareEngineer(name = name) { ... }
The constructor definition of the
BackendEngineer
does not specify whether the properties are mutable or immutable, meaning that thename
property is merely a constructor parameter instead of class properties.It would be unusable in the class but is simply passed to the super or parent constructor.
Add any property with setter or getter functions.
Define a new
techStack
property and assign it to a“Add here…”
value.class BackendEngineer(name: String) : SoftwareEngineer(name = name) { /* add any parent class properties with set and get functions here */ var techStack = "Add here..." }
Define a new
languages
property and assign to a1
value with a setter function that specifies a1..5
range:class BackendEngineer(name: String) : SoftwareEngineer(name = name) { /* add any parent class properties with set and get functions here */ var techStack = "Add here..." var languages = 1 set(value) { if (value in 1..4) { field = value } } }
Add an
addLanguage()
method that increases the language property and prints a“Congratulations on adding another language to your tech stack!”
string.class BackendEngineer(name: String) : SoftwareEngineer(name = name) { /* add any parent class properties with set and get functions here */ var techStack = "" var languages = 1 set(value) { if (value in 1..4) { field = value } } fun addTechStack(tech) { techStack = techStack + " " + tech languages++ println("Congratulations on adding another language to your tech stack!") } }
On the line after the
BackendEngineer
subclass, define aKotlinEngineer
subclass that extends theSoftwareEngineer
class.class BackendEngineer(name: String) : SoftwareEngineer(name = name) { ... } class KotlinEngineer(name: String) : SoftwareEngineer(name = name) { // A Kotlin Engineer }
In the
KotlinEngineer
subclass body, define aspecialzn
property assigned to a“Add here…”
and add a methodaddSpecialization()
to receive a specialization from the user:class KotlinEngineer(name: String) : SoftwareEngineer(name = name) { // A Kotlin Engineer var specialzn = "Add Kotlin specialization..." fun addSpecialization(spec) { specialzn = spec println("Kotlin Specialization: $specialzn") } fun workingAt(company){ println("I work at: $company") // Should then call the "this.gotAJob()" method. } }
Relationships Between Classes
There are two types of relationships between classes:
IS-A relationship: defined when a class inherits from a superclass. e.g. a
BackendEngineer
IS-ASoftwareEngineer
HAS-A relationship: defined when an object owns an instance of another class without being an instance of that class itself. e.g. Company X can have a
BackendEngineer
while it is not an instance of aBackendEngineer
class.
1. IS-A Relationships
In this relationship it is important to note the following:
the subclass can do what the superclass can do, e.g. a
BackendEngineer
can get a job.the relationship is unidirectional meaning the
BackendEngineer
is aSoftwareEngineer
but aSoftwareEngineer
is not aBackendEngineer
PS: Don’t use inheritance solely to achieve code reusability, remember to check whether:
the two classes are related to each other.
the two classes qualify for the IS-A relationship, i.e. Can I say a subclass is a superclass?
2. HAS-A Relationships
At times, classes are related without inheritance. For instance, in a Company
or a Project
, there is a need to have a KotlinEngineer
. However, the company
is not or doesn’t inherit aKotlinEngineer
Let’s create a
company
class below theKotlinEngineer
class.class KotlinEngineer(name: String) : SoftwareEngineer(name = name) { ... } class Company { ... } fun main() { ... }
In the Company class constructor, use the
val
keyword to create akotlinEngineer
property ofKotlinEngineer
type.class Company(val kotlinEngineer: KotlinEngineer) { // Body }
Within the Company, create a
createTeam()
method that calls theworkingAt()
method on thekotlinEngineer
property.class Company(val name: string, val kotlinEngineer: KotlinEngineer) { fun createTeam() { kotlinEngineer.workingAt(this.name) } }
On the line after the
createTeam()
method, define aroles()
method that calls theaddSpecialization()
method on thekotlinEngineer
property.class Company(val name: string, val kotlinEngineer: KotlinEngineer) { fun createTeam() { kotlinEngineer.workingAt(this.name) } fun roles() { kotlinEngineer.addSpecialization("Android") } }
Use the
val
keyword to add thebackendEngineer
property ofBackendEngineer
type:class Company( val name: string, val kotlinEngineer: KotlinEngineer, val backendEngineer: BackendEngineer ) { fun createTeam() { kotlinEngineer.workingAt(this.name) } fun roles() { kotlinEngineer.addSpecialization("Android") } }
In the Company body, within the
roles()
function, add a call to theaddTechStack()
method on thebackendEngineer
propertyclass Company( val name: string, val kotlinEngineer: KotlinEngineer, val backendEngineer: BackendEngineer ) { fun createTeam() { kotlinEngineer.workingAt(this.name) } fun roles() { kotlinEngineer.addSpecialization("Android") backendEngineer.addTechStack("Java: SpringBoot") } }
Override Superclass Methods From Subclasses
To override is to take manual control or to intercept an action. When overriding a method in the subclass, it interrupts the execution of the method defined in the superclass and provides its execution.
To override a method we do the following:
Within the superclass, add an
open
keyword before thefun
keyword of the method(s) we need to override. For example:open class SoftwareEngineer(name: String) { ... open fun schoolUpdate(course) { println("Studying: $course") } ... }
Within the subclass, create a method with the same name as the method we wish to override.
class KotlinEngineer(name: String) : SoftwareEngineer(name = name) { // A Kotlin Engineer fun schoolUpdate(course) {} }
Then add the
override
keyword and code logic or values you wish to override.class KotlinEngineer(name: String) : SoftwareEngineer(name = name) { // A Kotlin Engineer override fun schoolUpdate(course) { println("The Kotlin Engineer is now studying: $course") } }
The
override
keyword informs the Kotlin runtime to execute the code enclosed in the method defined in the subclass.
Reuse Superclass Code In Subclasses With The super
Keyword
To call the overridden method in the superclass from the subclass, you need to use the super
keyword.
Calling a method from the superclass is similar to calling the method from outside the class.
Instead of using a .
operator between the object and method, you need to use the super
keyword, which informs the Kotlin compiler to call the method on the superclass instead of the subclass.
Syntax
//super.[functionName]([Optional arguments])
// a good example is shown below
class KotlinEngineer(name: String) :
SoftwareEngineer(name = name) {
// A Kotlin Engineer
override fun schoolUpdate(course) {
println("The Kotlin Engineer is now studying: $course")
}
// using the super keyword
override fun gotAJob() {
// we'll use this to update the employed variable
super.gotAjob()
println("The job is a Kotlin Engineer role!!")
}
}
Override Superclass Properties From Subclasses
These follow the same steps as overriding a method.
Superclass
open class SoftwareEngineer(val name: String) { var employed: Boolean = false var student: Boolean = false // let's override these properties open var experience: Double = 0.0 open var seniorEngineer: Boolean = false ... }
Subclass
class KotlinEngineer(name: String) : SoftwareEngineer(name = name) { // A Kotlin Engineer ... // let's override these properties override var experience: Double = 10.5 override var seniorEngineer: Boolean = true ... }
Exciting Stuff
There we have a simplistic overview of class relationships in Kotlin. To learn more about a concept in inheritance, visit the official Kotlin documentation.
Asalaam Aleykum!!