Android Mastery: Inheritance In Kotlin

Kotlin Developer Path Ep4

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:

  1. 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.

  2. 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 the nameproperty 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.

  3. Add any property with setter or getter functions.

  4. Define a new techStackproperty 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..."
         }
    
  5. Define a new languagesproperty and assign to a 1value 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
                     }
                 }
         }
    
  6. 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!")
             }
         }
    
  7. On the line after the BackendEngineersubclass, define a KotlinEngineersubclass that extends the SoftwareEngineerclass.

     class BackendEngineer(name: String) :
         SoftwareEngineer(name = name) {
             ...
     }
    
     class KotlinEngineer(name: String) :
         SoftwareEngineer(name = name) {
             // A Kotlin Engineer
     }
    
  8. In the KotlinEngineersubclass body, define a specialznproperty assigned to a “Add here…” and add a method addSpecialization()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 BackendEngineerIS-A SoftwareEngineer

  • 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 a BackendEngineerclass.

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 BackendEngineercan get a job.

  • the relationship is unidirectional meaning the BackendEngineeris a SoftwareEngineerbut a SoftwareEngineeris not a BackendEngineer

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 Companyor a Project , there is a need to have a KotlinEngineer. However, the company is not or doesn’t inherit aKotlinEngineer

  1. Let’s create a companyclass below the KotlinEngineerclass.

     class KotlinEngineer(name: String) :
         SoftwareEngineer(name = name) {
             ...
     }
    
     class Company {
         ...
     }
    
     fun main() {
         ...
     }
    
  2. In the Company class constructor, use the valkeyword to create a kotlinEngineerproperty of KotlinEngineertype.

     class Company(val kotlinEngineer: KotlinEngineer) {
         // Body
     }
    
  3. Within the Company, create a createTeam() method that calls the workingAt()method on the kotlinEngineer property.

     class Company(val name: string, val kotlinEngineer: KotlinEngineer) {
    
         fun createTeam() {
             kotlinEngineer.workingAt(this.name)
         }
     }
    
  4. On the line after the createTeam() method, define a roles() method that calls the addSpecialization() method on the kotlinEngineer property.

     class Company(val name: string, val kotlinEngineer: KotlinEngineer) {
    
         fun createTeam() {
             kotlinEngineer.workingAt(this.name)
         }
    
         fun roles() {
             kotlinEngineer.addSpecialization("Android")
         }
     }
    
  5. Use the valkeyword to add the backendEngineerproperty of BackendEngineer type:

     class Company(
         val name: string, 
         val kotlinEngineer: KotlinEngineer,
         val backendEngineer: BackendEngineer
     ) {
    
         fun createTeam() {
             kotlinEngineer.workingAt(this.name)
         }
    
         fun roles() {
             kotlinEngineer.addSpecialization("Android")
         }
     }
    
  6. In the Company body, within the roles() function, add a call to the addTechStack() method on the backendEngineer property

     class 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:

  1. Within the superclass, add an openkeyword before the fun keyword of the method(s) we need to override. For example:

     open class SoftwareEngineer(name: String) {
         ...
         open fun schoolUpdate(course) {
           println("Studying: $course")
       }
       ...
     }
    
  2. 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) {}
     }
    
  3. 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 superKeyword

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.

  1. 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
    
         ...
     }
    
  2. 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!!