Object-oriented programming principles

Object-oriented programming (OOP) is a programming paradigm that seeks to model data using logical representations such as classes. The classes are used to create objects, which contain information (properties) and actions (methods). OOP can help organise applications by delegating processes to different classes. In this article, we will explore several key OOP principles.

Encapsulation

Encapsulation describes the hiding of a class's data, such as a method or variable, from other classes. Access to the hidden data may be available in a controlled manner via other visible methods. In selectively exposing only the necessary application components, encapsulation makes it easier to refactor data and extend classes elsewhere in the application. For example, in the below code, the Kitchen class has a public method called cookDinner that any other class or object can interact with. The cookDinner class runs a method called getIngredients to retrieve the ingredients required to make the food. External objects don't need to use the getIngredients method, and so it is marked as private, thereby encapsulating the method.

class Kitchen {
	// Enapsualted variables
	private var hobTemperature = 0
	private var cookingPotContents = mutableListOf<String>()
	
	// Encapsulated method
	private fun getIngredients(): List<String> {
		return listOf("Pasta", "Tomatoes", "Pesto")
	}
	
	// Public method
	fun cookDinner() {
		hobTemperature = 140
		for (ingredient in getIngredients()) cookingPotContents.add(ingredient)
	}
}

Inheritance

Inheritance is a feature of object-oriented programming wherein one class can incorporate the data and methods of another class. Inheritance helps minimise the duplication of code and enforces the relationships between related classes. For example, in the below code, the Speaker class inherits the data from the Announcement class. This means the Speaker class can access the Announcement class's publicAnnouncement variable. Importantly, the principle of encapsulation still applies, which means only public data can be inherited. For this reason, private data such as the Announcement class's encapsulatedAnnouncement variable cannot be accessed directly via inheritance (although may be accessed indirectly via other public methods).

open class Announcement() {
    val publicAnnouncement = "Public announcement!"
    
    private val encapsulatedAnnouncement = "Private announcement..."
}

class Speaker() : Announcement() {
    init {
        // Prints: Public announcement!
        println(publicAnnouncement)
        
        // Throws an error because encapsulatedAnnouncement is private
        println(encapsulatedAnnouncement)
    }
}

fun main(args: Array<String>) {
	Speaker()        
}

Polymorphism

Polymorphism is an object-oriented programming principle that expands on the feature of inheritance. In brief, polymorphism describes how an inheriting class can override the behaviour of the parent class. Polymorphism enables inheriting classes to repurpose the data from the parent class to better suit their needs. For example, in the below code, the Speaker class overrides the makeAnnouncement method from the Announcement class. The overridden version of the makeAnnouncement method converts the message to upper case before printing the text to the console, thereby demonstrating how polymorphism enables classes to customise the behaviour of inherited methods.

open class Announcement() {
    val publicAnnouncement = "Public announcement!"
    
    open fun makeAnnouncement(message: String) = println(message)
}

class Speaker() : Announcement() {
    init {
        // Prints: PUBLIC ANNOUNCEMENT!
        makeAnnouncement(publicAnnouncement)
    }
	
	override fun makeAnnouncement(message: String) = println(message.uppercase())
}

fun main(args: Array<String>) {
	Speaker()        
}