Course Content
Android Development with Kotlin
Android Development with Kotlin
Inheritance and Interfaces
In OOP, there are certain principles and functionalities to follow. These principles help in writing more convenient and flexible code.
Let's go over these principles.
OOP Principles
There are three main principles, so let's briefly cover each one:
- Inheritance: Inheritance allows a new class (subclass) to inherit the properties and methods of an existing class (superclass). This promotes code reuse and establishes a hierarchical relationship between classes.
For example, if you have a superclassAnimal
that defines properties and attributes common to most animals, you can have a subclassDog
that inherits from this superclass and adopts its properties. This helps maintain proper code structure and enhances flexibility; - Polymorphism: Polymorphism allows objects to be treated as instances of their parent class, even if they are instances of a child class. It enables a single function or method to work in different ways depending on the object it operates on.
For example, theAnimal
class might have amakeSound
function(method) representing the animal's sound. TheDog
subclass would also have a sound, which might be a "bark." Polymorphism allows us to inherit the property from the superclass but override and customize it for the subclass; - Encapsulation: Encapsulation is the bundling of data (properties) and methods (functions) that operate on the data into a single unit or class. It also involves restricting access to some of the object's components, meaning the internal representation of an object is hidden from the outside. This is typically achieved using access modifiers like
private
,protected
, andpublic
.
We will discuss encapsulation and polymorphism in more detail in the next chapters, as these are important principles that need closer examination.
Note
There’s also a fourth, often overlooked principle in OOP—Abstraction. Abstraction tells us to depend on abstractions rather than concrete implementations. We’ll discuss abstract classes and fields in more detail later on.
In summary, the three OOP principles are as follows:
Inheritance
Let’s talk more about inheritance in Kotlin and how it is achieved.
We’ll look at the main concepts, focusing on two classes that will have a specific relationship with each other.
Base (Parent) Class:
The base class is the class whose properties and methods are inherited by another class. In Kotlin, all classes are final by default, meaning they cannot be inherited unless explicitly marked as open
.
Derived (Child) Class:
The derived class inherits properties and methods from the base class and can also have additional properties or methods. You can override
base class methods in the derived class to provide specific functionality.
Note
Method overriding means that a specific method will have different, overridden functionality in the class where it was overridden. For example, if in the parent class, the method displays "
Hello, World!
" on the screen, in the child class, the overridden method might display a different text, like "Hello, Country!
". Once you see an example, it will become clearer how this works.
Let's start with an example where we have a base class, Animal
, and two derived classes, Dog
and Cat
.
Base (Parent) class Animal
:
Animal
open class Animal(val name: String, val age: Int) { fun sleep() { println("$name is sleeping") } open fun makeSound() { println("$name makes a sound") } }
As you can see, the class is created using the open
keyword, indicating that this class can be inherited and that it is a base class.
You might also notice that the second method, makeSound()
, is also marked as open
. This means the method can be overridden, which we will do later in the child classes.
Now, let's look at the creation of the child class Dog
:
Dog
class Dog(name: String, age: Int) : Animal(name, age) { fun bark() { println("$name is barking") } override fun makeSound() { println("$name barks") } }
In Kotlin, when a class inherits from another class, the child class needs to pass information to the parent class. This is done using : ParentClass(parameters)
right after the child class's name. It tells Kotlin first to set up the parent class with those parameters.
For example, when you create a Dog
from Animal
, Dog("Buddy", 3) : Animal("Buddy", 3)
passes "Buddy"
and 3
to Animal
first, setting up name
and age
.
Here, you can also see how overriding works. The function that was marked as open
in the parent class is overridden here, and instead of $name makes a sound
, the function called from the Dog
class will output $name barks
, since dogs typically bark.
So, inheritance allows Dog
to use the same base structure (name
, age
) but also customize behavior (bark
instead of a generic sound).
Interface
Inheritance is closely related to interfaces in Kotlin.
In Kotlin, interfaces define a contract that classes implementing the interface must adhere to. Interfaces specify methods and properties that must be implemented but do not contain specific implementations, only their declarations.
Here’s how it works: the interface will only declare the methods, and the implementing class must inherit and override them, providing specific implementations.
Let’s look at an example of declaring an interface:
Main
interface AnimalActions { fun makeSound() fun move() }
An interface is created using the interface
keyword. Inside the interface, you can declare methods and properties that must be implemented in the classes. Each method in the interface must be open
because classes will need to override them.
To implement an interface, a class uses the :
symbol and must provide implementations for all the methods declared in the interface.
If the class fails to implement any of the methods, it will trigger an exception that will halt the service or program.
Let's look at an example class implementing the AnimalActions
interface:
Main
class Dog : AnimalActions { override fun makeSound() { println("Bark") } override fun move() { println("Run") } }
We don't need to pass any data into the interface constructor, as we did when inheriting a class. When implementing an interface, we only override all the methods in the implementing class.
Additionally, Kotlin supports multiple inheritance of interfaces, for example:
Main
interface AnimalActions { fun makeSound() fun move() } interface PetActions { fun play() } class Cat : AnimalActions, PetActions { override fun makeSound() { println("Meow") } override fun move() { println("Walk") } override fun play() { println("Chase a laser pointer") } }
As you can see, we created two interfaces that define certain behaviors for animals. The Cat
class inherited these interfaces and overrode the methods from both interfaces, customizing the inherited methods for itself.
Inheriting Properties from Interfaces
Earlier, I mentioned that we shouldn't pass properties to the interface through a constructor because we handle this differently.
If the interface specifies certain properties, we must inherit and override them in the derived classes.
Here's how it looks in the code:
Main
interface AnimalInfo { val species: String } class Dog : AnimalInfo { override val species = "Canine" }
Summary
Inheritance is a crucial aspect and principle of OOP. It lets you structure your code the way you want.
Additionally, using interfaces, we can separate implementation and abstraction into distinct classes (interfaces) to distinguish between declaration and implementation.
In the following chapters, we will discuss the other two OOP principles and examine their implementation in Kotlin.
Thanks for your feedback!