Kotlin Abstract Classes & Example Usage | Android Introduction #22

ibrahimcanerdogan
5 min readJan 22, 2024

--

In this reading, you will learn more about the details of abstract classes, their restrictions and the case for using them as an alternative to multiple classes.

UDEMY COURSE

Abstract classes

When you use the abstract keyword before a class definition, you make this class abstract. Abstract classes can be thought of as a hybrid of an open class and an interface. There are two main consequences of making a class abstract:

  1. Abstract classes can have abstract methods or properties. Such elements are marked with an abstract modifier, and they do not have a body. They are just definitions, similar to those in interfaces, that need to be overridden in subclasses.
  2. Abstract classes cannot be used to create objects. However, you can inherit subclasses from them. This is a result of the first consequence — you cannot create objects with abstract methods or properties. Those need to be overridden.

You can use abstract classes as a replacement for interfaces, but this is considered a bad practice. Wherever possible, the preference is to use interfaces. Each class can inherit from only one class but implement many interfaces. Interfaces are considered to be an easier concept to learn compared to abstract classes. The key advantage of abstract classes is that they can have non-open methods (on interfaces, all elements are open) and non-abstract properties.

Example of Interface Code

interface I {

val a: Int = 123 // ERROR! You cannot define non-abstract properties in Interfaces
}

Abstract classes are mainly used when you want to specify a set of generic operations for multiple classes. Let’s say that you write an application that draws various shapes in different ways. You might be drawing them on native Android components, on websites, or on a terminal. Since you draw in different ways on each of those platforms, you need separate classes.

However, there is also another option. Because a square and a rectangle can be drawn using lines, you can define an abstract class ShapeDrawer, that will define methods drawSquare and drawRectangle based on the abstract method drawLine. Now your drawer classes can inherit from shapeDrawer and only need to define one function drawLine, to have also drawSquare and drawRectangle.

Consider for a moment the code for this Abstract class example.

Example of Abstract Class

abstract class ShapeDrawer { 
fun drawSquare(){
drawLine()
}
fun drawRectangle(){
drawLine()
}
internal abstract fun drawLine()
}

class AndroidShapeDrawer():ShapeDrawer(){

override fun drawLine() {
//code that draw lines for android platform
println("Test code -Draw line for android platform")
}
}

class DesktopShapeDrawer():ShapeDrawer(){
override fun drawLine() {
//code that draw lines for android platform
println("Test code -Draw line for desktop platform")
}
}

fun main(){
val androidDrawer:ShapeDrawer = AndroidShapeDrawer()
androidDrawer.drawSquare()
val desktopDrawer:ShapeDrawer = DesktopShapeDrawer()
desktopDrawer.drawSquare()
}

The code produces this output:

Test code -Draw line for android platform
Test code -Draw line for desktop platform

Interface vs Abstract Class

To summarize the difference between Interface and Abstract class:

Defining & Using Abstract Classes

The code snippet below shows a general format of abstract classes.

abstract class SomeAbstractClass {
abstract fun abstractMethod()
fun callAbstractTwice() {
abstractMethod() // You can use abstract methods inside the class, because it is assumed they
// will be overridden in the child class.
abstractMethod()
}
}

class SomeRegularClass : SomeAbstractClass {
override fun abstractMethod() {
println("Calling abstract method")
}
}

fun main() {
val regular = SomeRegularClass()
regular.abstractMethod() // Calling abstract method
regular.callAbstractTwice()
// Calling abstract method
// Calling abstract method
}

In the following example, there are three classes; AndroidShapeDrawer, WebsiteShapeDrawer and TerminalShapeDrawer. Each class has a function to draw a line, draw a square and draw a rectangle.

class AndroidShapeDrawer {
fun drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int) {
/*...*/
}
fun drawSquare(x: Int, y: Int, size: Int) {
/*...*/
}
fun drawRectangle(x: Int, y: Int, height: Int, width: Int) {
/*...*/
}
}

class WebsiteShapeDrawer {
fun drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int) {
/*...*/
}
fun drawSquare(x: Int, y: Int, size: Int) {
/*...*/
}
fun drawRectangle(x: Int, y: Int, height: Int, width: Int) {
/*...*/
}
}

class TerminalShapeDrawer {
fun drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int) {
/*...*/
}
fun drawSquare(x: Int, y: Int, size: Int) {
/*...*/
}
fun drawRectangle(x: Int, y: Int, height: Int, width: Int) {
/*...*/
}
}

Each class could implement these functions individually, however, there is another option.

Since square and rectangle can be drawn using lines, you can define an abstract class ShapeDrawer, that will define methods drawSquare and drawRectangle based on the abstract method drawLine. Now our drawer classes can inherit from ShapeDrawer and only need to define one function drawLine but will also have the functionality drawSquare and drawRectangle.

abstract class ShapeDrawer {

abstract fun drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int)

fun drawSquare(x: Int, y: Int, size: Int) {
drawLine(x, y, x + size, y)
drawLine(x + size, y, x + size, y + size)
drawLine(x, y, x, y + size)
drawLine(x, y + size, x + size, y + size)
}

fun drawRectangle(x: Int, y: Int, height: Int, width: Int) {
drawLine(x, y, x + width, y)
drawLine(x + width, y, x + width, y + height)
drawLine(x, y, x, y + height)
drawLine(x, y + height, x + width, y + height)
}
}


class AndroidShapeDrawer: ShapeDrawer() {
override fun drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int) { /*...*/ }
}

class WebsiteShapeDrawer: ShapeDrawer() {
override fun drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int) { /*...*/ }
}

class TerminalShapeDrawer: ShapeDrawer() {
override fun drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int) { /*...*/ }
}

This is how abstract classes are used in practice — to share behavior for all its subclasses and allow subclasses to focus on their class-specific functionality.

İbrahim Can Erdoğan

LINKEDIN

YOUTUBE

UDEMY

GITHUB

--

--

ibrahimcanerdogan
ibrahimcanerdogan

Written by ibrahimcanerdogan

Hi, My name is Ibrahim, I am developing ebebek android app within Ebebek. I publish various articles in the field of programming and self-improvement.

No responses yet