Instruction
In this lesson, you’ll learn:
- The purpose of an abstract class.
- The difference between an abstract class and an interface.
- The requirements for working abstract classes.
Abstract classes are useful when you want to define a base implementation that its subclasses can reuse or have instance variables inherited by its subclasses. The abstract class starts with the abstract keyword, followed by the property’s name or function.
How Does it Work?
Imagine you have a game with the following characters:
- Warriors
- Mages
- Archers
The characters share some features, like a name and health status, but each has unique abilities.
First, you’ll create a blueprint for a generic character called Adventurer. This blueprint contains standard features like name and health and includes some special abilities that every Adventurer should have, like the ability to move.
However, the Adventurer blueprint doesn’t describe exactly how each character should move or what kind of attacks they have. Instead, it leaves those details to be filled in later for the specific types of characters, like warriors, mages, and archers.
Next, you’ll create subclasses for each type of character. These subclasses inherit from the Adventurer blueprint, meaning these characters will get all those standard features and abilities described in the blueprint. In addition, you have the freedom to add your own unique features and abilities. For example, a warrior might have a special sword attack ability, while a mage might have a fireball ability.
This gives you an overview of the main class, the Adventurer in this case, and the subclasses, Warrior, Mage, and Archer.
Kotlin Abstract Class Declaration
You declare a class as abstract by starting the class with the abstract keyword, followed by the property’s name or function. Here’s how to write the game example from above:
abstract class Adventurer(val name: String, var health: Int) {
abstract fun performSpecialAbility()
fun move() {
println("$name is moving.")
}
}
class Warrior(name: String, health: Int) : Adventurer(name, health) {
override fun performSpecialAbility() {
println("$name performs a powerful sword attack!")
}
}
class Mage(name: String, health: Int) : Adventurer(name, health) {
override fun performSpecialAbility() {
println("$name casts a fireball spell!")
}
}
class Archer(name: String, health: Int) : Adventurer(name, health) {
override fun performSpecialAbility() {
println("$name shoots a precise arrow!")
}
}
fun main() {
val warrior = Warrior("Conan", 100)
val mage = Mage("Merlin", 80)
val archer = Archer("Legolas", 90)
warrior.move()
warrior.performSpecialAbility()
mage.move()
mage.performSpecialAbility()
archer.move()
archer.performSpecialAbility()
}
Kotlin Interface
The Kotlin interface is like a set of instructions that classes must follow. It defines what methods and properties a class must provide.
Imagine you’re a principal of a school that teaches different subjects like math, science, and history. Each subject has its own set of topics and activities.
An interface in Kotlin is like a curriculum guide for each subject. It outlines the topics and activities but doesn’t teach the material itself. You still need to hire different teachers to teach each subject. Each teacher represents a class in Kotlin.
You give each teacher a copy of the curriculum guide (interface) to ensure they cover all the required topics and activities. The teachers (classes) then use the curriculum guide (interface) to plan their lessons and activities. They must follow the guide closely, implementing all the methods and properties outlined in the interface.
When it’s time for class (when you call methods or access properties), you can be sure that each teacher (class) is covering the material according to the curriculum guide (interface). This consistency makes it easy to work with different subjects (classes) because they all follow the same structure provided by the interface.
Kotlin Interface Declaration
You declare an interface using the interface keyword followed by its name and members, its methods and properties. Here’s how you’d write the teacher example as described above:
// Interface representing a Subject curriculum guide
interface Subject {
// Method declaration for teaching topics
fun teachTopics()
// Method declaration for conducting activities
fun conductActivities()
}
// Concrete class representing a Math teacher
class MathTeacher : Subject {
override fun teachTopics() {
println("Teaching math topics.")
}
override fun conductActivities() {
println("Conducting math activities.")
}
}
// Concrete class representing a Science teacher
class ScienceTeacher : Subject {
override fun teachTopics() {
println("Teaching science topics.")
}
override fun conductActivities() {
println("Conducting science activities.")
}
}
// Concrete class representing a History teacher
class HistoryTeacher : Subject {
override fun teachTopics() {
println("Teaching history topics.")
}
override fun conductActivities() {
println("Conducting history activities.")
}
}
fun main() {
// Creating instances of different teachers
val mathTeacher = MathTeacher()
val scienceTeacher = ScienceTeacher()
val historyTeacher = HistoryTeacher()
// Using the teachers to teach their respective subjects
mathTeacher.teachTopics()
mathTeacher.conductActivities()
scienceTeacher.teachTopics()
scienceTeacher.conductActivities()
historyTeacher.teachTopics()
historyTeacher.conductActivities()
}
Differences Between Abstract Class and Interface
You may be wondering when to use an abstract class and when to use an interface. Here are some guidelines that can help you when considering which one to use:
Use abstract classes when you want to:
- Define a base implementation that its subclasses can reuse.
- Enforce a contract for any subclass that extends the abstract class.
- Have instance variables inherited by its subclasses.
- Define a hierarchy of related classes.
Use interfaces when you want to:
- Define a contract for classes that implement the interface.
- Provide default implementations for the methods in the interface.
- Give a class flexibility to implement multiple interfaces.
- Define a set of unrelated classes that share a common behavior.
It’s essential to remember interfaces can’t store state. The classes that implement the interface maintain the state.
Here’s an example where the interface can’t store a state:
interface FruitBox {
fun printContents()
}
class AppleBananaBox(private val numOfItems: Int) : FruitBox {
override fun printContents() {
println("This fruit box contains $numOfItems apples and bananas.")
}
}
fun main() {
val appleBananaBox = AppleBananaBox(10)
appleBananaBox.printContents()
}
Here’s a code breakdown:
- You create an interface called
FruitBoxthat declares an abstract method calledprintContents(). - Then, you create a class called
AppleBananaBoxthat implements theFruitBoxinterface. This class contains a private field callednumOfItemsto store the number of items in the box. - Finally, you create an instance of
AppleBananaBox,initialize it with ten items, and call theprintContents()method from within themain()method to print that value to the console.
Based on the example above, you’ll see that the FruitBox interface declares the printContents() method, which must be implemented by classes that implement the interface. The state in this example refers to the number of items numOfItems stored in the AppleBananaBox class, not in the interface itself.
Important Points
Now that you understand what Kotlin abstract classes are and what you can achieve by implementing them in your project, there are a few points that you need to take note of:
- You can’t directly create objects from an abstract class using the
newkeyword. - Any subclass inheriting from the abstract class must implement all abstract methods.
- You can declare an abstract class as
finalto prevent further inheritance.