Classes, objects, and some best practices in Kotlin
In this blog, I repeat some programming fundamentals and take notes. I followed codelabs that were prepared from developer.android.com you can check from this. I hope it will useful.
Firstly let's repeat OOP principles. There are 4 main common principles. These are Encapsulation, Abstraction, Inheritance, and Polymorphism.
Encapsulation: Allows management objects accessible status. For example, you can hide specific information and control access to the object.
Abstraction: The main idea is to hide internal implementation logic as much as. Users only use the function and don't care about how it is built etc.
Inheritance: Establishing a parent-child relationship between classes enables the child class to use characteristics and behavior from the parent.
Polymorphism: Polymorphism is the ability to use different objects in a single, common way.
Getter and Setter Functions
Getter and Setter are used for protecting and setting data. With Getter get a return of value and setter provides set a new values. If you don't write getter and setter Kotlin compiler default internally creates these. The compiler autogenerates the getter and setter functions as you can see in this code snippet:
var speakerVolume = 2
get() = field
set(value) {
field = value
}
You won’t see these lines in your code because they’re added by the compiler in the background.
Constructor
The constructor aims to define specific objects when they are created.
The default constructor has no parameters. You can define it like that.
class SmartDevice constructor() {
...
}
or remove the constructor keyword:
class SmartDevice {
...
}
When defining a constructor you need to ensure that the instance of a class has the properties like parameters.
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
fun turnOn(){
println("Smart device is turned on.")
}
fun turnOff(){
println("Smart device is turned off.")
}
}
There are two main types of constructors in Kotlin:
Primary constructor. A class can have only one primary constructor, which is defined as part of the class header. A primary constructor can be a default or parameterized constructor. The primary constructor doesn’t have a body. That means that it can’t contain any code.
Secondary constructor. A class can have multiple secondary constructors. You can define the secondary constructor with or without parameters. The secondary constructor can initialize the class and has a body, which can contain initialization logic. If the class has a primary constructor, each secondary constructor needs to initialize the primary constructor.
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
constructor(name: String, category: String, statusCode: Int) : this(name, category) {
deviceStatus = when (statusCode) {
0 -> "offline"
1 -> "online"
else -> "unknown"
}
}
...
}
Implement a relationship between classes
Some classes may have the same attributes and properties. In this case, you can inherit these properties and make the code reusable with inheritance. Firstly define a parent class that has common properties and attributes then define some child classes that inherit related attributes and properties.
By default all classes are final class in Kotlin, therefore, you have to add an open keyword before the class keyword.
open class SmartDevice(val name: String, val category: String) {
...
}
The
open
keyword informs the compiler that this class is extendable, so now other classes can extend it.
There are two types of relationships between classes: IS-A relationships and HAS-A relationships.
IS-A relationship: This means the subclass can do whatever the parent can do. Every subclass is a parent but every parent is not a subclass.
// Smart TV IS-A smart device.
class SmartTvDevice : SmartDevice() {
}
HAS-A relationship: This means the class may have the same properties as the other class.
// The SmartHome class HAS-A smart TV device.
class SmartHome(val smartTvDevice: SmartTvDevice) {
}
SmartHome has a smart tv device and does what smartTvDevice can do.
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
fun turnOffTv() {
smartTvDevice.turnOff()
}
}
Class may have two different classes. In the SmartHome
class constructor, move the smartTvDevice
property parameter to its line followed by a comma:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
smartLightDevice.turnOn()
}
fun turnOffLight() {
smartLightDevice.turnOff()
}
}
Override superclass methods from subclasses
To override means to intercept the action, typically to take manual control.To override means to intercept the action, typically to take manual control.
To specify the function add override it and add what you want.
class SmartLightDevice(name: String, category: String) :
SmartDevice(name = name, category = category) {
var brightnessLevel = 0
override fun turnOn() {
deviceStatus = "on"
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
override fun turnOff() {
deviceStatus = "off"
brightnessLevel = 0
println("Smart Light turned off")
}
fun increaseBrightness() {
brightnessLevel++
}
}
Reuse superclass code in subclasses with the super
keyword
You can reuse the code when you update the status in the Parent class. Use super keyword to overridden method from superclass.
class SmartTvDevice(name: String, category: String) :
SmartDevice(name = name, category = category) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
override fun turnOn() {
super.turnOn()
println("Smart TV turned on. Speaker volume set to $speakerVolume.")
}
override fun turnOff() {
super.turnOff()
println("Smart TV turned off")
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
}
fun previousChannel() {
channelNumber--
}
}
Override superclass properties from subclasses
You can also override properties of the superclass with the use open and val keywords.
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open val deviceType = "unknown"
...
}class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
...
override val deviceType = "Smart TV"
...
}
Visibility modifiers
Kotlin provides some modifiers for set accessibility of methods and properties. These are private, public, protected, and internal.
public
. Default visibility modifier. Makes the declaration accessible everywhere. The properties and methods that you want used outside the class are marked as public.
private
. Makes the declaration accessible in the same class or source file.
protected
. Makes the declaration accessible in subclasses. The properties and methods that you want used in the class that defines them and the subclasses are marked with theprotected
visibility modifier.
internal
. Makes the declaration accessible in the same module. The internal modifier is similar to private, but you can access internal properties and methods from outside the class as long as it's being accessed in the same module.
Default when you define class it’s publicly visible and can be accessed by any package that imports it.
private var deviceStatus = "online"
You can also set the visibility modifiers to setter functions. The modifier is placed before the set
keyword. For the SmartDevice
class, the value of the deviceStatus
property should be readable outside of the class through class objects. However, only the class and its children should be able to update or write the value.
open class SmartDevice(val name: String, val category: String) {
...
var deviceStatus = "online"
protected set(value){
field = value
}
...
}
Like properties, you can also set the visibility of methods.
protected fun nextChannel() {
channelNumber++
}
Set visibility of constructor:
open class SmartDevice protected constructor (val name: String, val category: String) {
...
}
Finally set the visibility of the class:
internal open class SmartDevice(val name: String, val category: String) {
...
}
Thanks for everything. Good luck.