Object Oriented Programming concept with Kotlin
In Object Oriented Programming, we have data and objects to access and manipulate data rather than only maintaining them with functions and logics. So, in OOP, if we want to use the properties of a class, we can create one reference to that class which we call object and using them we can perform different actions.
In OOP, we have class which is the main blueprint of the programme, which holds the data. With the concept of OOP — these data can be wrapped with a security layer, can be inherited if allowed with proper access modifiers, some implementation can be hidden as abstraction and the data & functions can be overridden in other child classes.
There are 4 major components of OOP which we also call the pillars of OOPS. These are-
- Encapsulation
- Inheritance
- Polymorphism
- Abstraction
- Encapsulation:
Encapsulation involves bundling data and the methods that operate on the data into a single unit — a class. This creates a protective barrier, allowing controlled access to the data. In Kotlin, you can use access modifiers like private
, protected
, and public
to regulate the visibility of class members, ensuring data integrity and security.
Example:
In a Kotlin class, for encapsulation, we can have some private variables declared.
This, variables can only accessed by creating one object of the class and by calling function which is public to provide the data.
Here’s the code:
class Student{
private var name: String = "" // declared as private
private var age: Int = 0
fun getName(): String { // by default the function is public
return this.name
}
fun setName(name: String) { // to set the value for name property
this.name = name
}
fun getAge(): Int {
return this.age
}
fun setAge(age: Int) {
this.age = age
}
}
2. Inheritance:
Inheritance will stand for giving access of the parent class to the child class which extends it. In some programmes, we may have some scenarios where some properties or functions will be common for separate class. In these cases, rather using them separately, we have a parent class which contains all the common properties and functions.
For example,
class Dog {
private var breed: String = ""
private var color : String = "brown"
fun bark() {
println("The dog is barking")
}
fun eat() {
println("Dog is eating")
}
fun getColor(){
println("The dog color is: $color")
}
}
class Cat{
private var name: String = "Snowy"
private var color : String = "white"
fun meaw() {
println("The cat is meaw")
}
fun eat() {
println("Cat is eating")
}
}
In the above code, we have two different classes having one common variable “color” and one common method “eat”.
Now, rather using these common properties separately, we can make one common parent class like below:
class Animal {
var color : String = "blue"
fun eat() {
println("Animal is eating")
}
}
Now, there is an important thing to know, in Kotlin, classes, variables and function without any access modifier declared will be consider as “public static”. To make it accessible for other classes, we must use the “open” keyword like below:
open class Animal {
open var color : String = "blue"
open fun eat() {
println("Animal is eating")
}
}
To extend this Animal class, we need to use : after the name of the child class and then write the parent class(Animal) name which primary constructor.
class Dog : Animal(){
private var breed: String = ""
fun bark() {
println("The dog is barking")
}
fun getColor(){
println("The dog color is: $color")
}
}
Now, in the main function, by creating the object of the Dog class, we can access the properties of Animal classes as well.
fun main() {
var dog: Dog = Dog()
dog.color = "black"
dog.eat()
dog.getColor()
}
The Output:
Animal is eating
The dog color is: black
3. Polymorphism:
Polymorphism stands for the one object performing multiple roles in different classes. Suppose, in the above example of the Inheritance, we have seen that by creating the object of the Dog class, we can access the function of the Animal class. But when we are calling the eat of the Animal class, it is showing the text “Animal is eating” which was declared inside that.
What if, we want to use the same function in the Dog class, but want to modify it inside that. For example, if we want to print- “Dog is eating”, then what should we do?
Here comes the concept of polymorphism, where we can override the function of the parent along with calling parent class’s function too.
Here is the example:
class Dog : Animal(){
private var breed: String = ""
override var color: String = "red"
fun bark() {
println("The dog is barking")
}
fun getColor(){
println("The dog color is: $color")
}
override fun eat() {
super.eat()
println("The dog is eating")
}
}
Calling it in the main function:
fun main() {
var dog: Dog = Dog()
dog.eat()
dog.getColor()
}
The output:
Animal is eating
The dog is eating
The dog color is: red
4. Abstraction:
Abstraction in Object-Oriented Programming (OOP) is a fundamental concept that focuses on simplifying complex systems by emphasizing essential properties and ignoring unnecessary details. It allows developers to create models that capture the core features of an object or system, promoting a clear separation between what an object does and how it achieves its functionality. Through abstraction, programmers can design and work with high-level, generalized representations, fostering code reusability and easier maintenance. Ultimately, abstraction enhances the efficiency and clarity of software development by encapsulating intricate implementations behind simplified interfaces.
So, the Abstraction in Kotlin lies on two types of classes-
i) Abstract class ii) Interface
i) Abstract class:
In Kotlin, an abstract class is a class that cannot be instantiated on its own and is meant to be subclassed by other classes. The main purpose of using abstract classes is to provide a common base for multiple related classes, allowing you to define common functionality in the abstract class and enforce that all subclasses implement certain methods or properties.
Here are some key purposes and features of abstract classes in Kotlin:
> Common Base Class: Abstract classes allow you to create a common base for a group of related classes. This common base class can contain shared properties and methods that are applicable to all subclasses.
> Method Signatures: Abstract classes can declare abstract methods (methods without a body) that must be implemented by any concrete (non-abstract) subclass. This enforces a contract that all subclasses must provide their own implementation for certain methods.
abstract class Shape {
abstract fun calculateArea(): Double
}
class Circle(radius: Double) : Shape() {
override fun calculateArea(): Double {
return Math.PI * radius * radius
}
}
> Partial Implementation: Abstract classes can provide both abstract and concrete (implemented) methods. Concrete methods can include shared logic or default behavior that can be inherited by subclasses, but they can be overridden if needed.
abstract class Animal {
abstract fun makeSound()
fun sleep() {
println("Zzzz...")
}
}
> Preventing Instantiation: Abstract classes cannot be instantiated directly. They are meant to be subclassed, and objects are created from concrete subclasses.
// This would result in a compilation error, as Shape is abstract.
val shape = Shape()
In summary, abstract classes in Kotlin serve as a way to create a common base for related classes, enforce a contract for required methods, and provide a combination of shared and customizable functionality. They are particularly useful when you want to create a hierarchy of classes with a shared structure and behavior.
ii) Interface:
Interfaces in Kotlin serve the purpose of defining a contract for classes. They allow you to declare a set of methods or properties that must be implemented by classes that implement the interface. Here are some key purposes and features of interfaces in Kotlin:
> Defining Contracts: Interfaces provide a way to define a contract that a class must adhere to. The methods declared in an interface represent a set of behaviors that implementing classes must support.
interface Shape {
fun calculateArea(): Double
fun calculatePerimeter(): Double
}
> Multiple Inheritance: Unlike classes, Kotlin supports multiple inheritance through interfaces. A class can implement multiple interfaces, inheriting and providing implementations for the methods declared in each interface.
class Circle(radius: Double) : Shape {
override fun calculateArea(): Double {
return Math.PI * radius * radius
}
override fun calculatePerimeter(): Double {
return 2 * Math.PI * radius
}
}
> Default Implementations: Interfaces can provide default implementations for methods. This allows you to add new methods to an existing interface without breaking existing implementations.
interface Shape {
fun calculateArea(): Double
fun calculatePerimeter(): Double {
// Default implementation for perimeter calculation
return 0.0
}
}
> Abstraction: Interfaces, like abstract classes, provide a level of abstraction. They allow you to define the “what” (methods to be implemented) without specifying the “how” (implementation details).
> Contracts for Behavior: Interfaces are not limited to method declarations; they can also declare properties. This allows you to define a set of properties that implementing classes must have.
interface Drawable {
val color: String
fun draw()
}
> Polymorphism: Interfaces contribute to polymorphism by allowing objects of different classes to be treated as instances of a common interface. This promotes code flexibility and reusability.
fun printArea(shape: Shape) {
println("Area: ${shape.calculateArea()}")
}
In summary, interfaces in Kotlin provide a way to define contracts, support multiple inheritance, allow for default implementations, and promote polymorphism. They are a fundamental building block for achieving abstraction, flexibility, and maintainability in your code.