Topic 1: Understanding OOPs Concepts from Scratch

The first topic of our Learn android from basic to advance series

Topic 1: Understanding OOPs Concepts from Scratch

Hello devs, I'm excited to share with you the first blog post in our Learn Android from Basic to Advanced series. Today, we'll be diving into the world of object-oriented programming concepts - trust me, they're super helpful when it comes to writing clean, secure, and organized code. So, let's get started!

Let's start with the oops concept this is the fundamental concept in Android development

OOPS Concept

  • Class

  • Object

  • Inheritance

  • Encapsulation

  • Polymorphism

  • Abstraction

Class

Class is a blueprint of our application. In Android, we create classes to define the structure and behaviour of various components such as activity, fragment and custom view classes.

class Person(val name: String, var age: Int) {
    fun speak() {
        println("Hello, my name is $name and I am $age years old.")
    }
}

In this example, Person is a class with properties name and age, and a method speak().

Object

So in Object Oriented Programming, everything is treated as an object. It means that UI elements like buttons text fields and even the app are represented as objects with properties and behaviours.

class Person(val name: String, var age: Int) {
    fun speak() {
        println("Hello, my name is $name and I am $age years old.")
    }
}

fun main() {
    val person1 = Person("John", 30)
    val person2 = Person("Alice", 25)

    person1.speak()
    person2.speak()
}

We create two Person objects (person1 and person2) and call the speak() method on each object.

Inheritance

Inheritance is like passing down traits in a family. We create two classes one is a superclass or parent class and the other one is a derived class or subclass and the subclass inherits the properties and behaviour of the superclass.

open class Animal(val name: String) {
    open fun makeSound() {
        println("Animal $name makes a sound.")
    }
}

class Dog(name: String) : Animal(name) {
    override fun makeSound() {
        println("Dog $name says: Woof!")
    }
}

fun main() {
    val dog = Dog("Buddy")
    dog.makeSound()
}

In this example, Animal is a superclass with a property name and a method makeSound(). Dog is a subclass of Animal, inheriting the name property and overriding the makeSound() method.

Encapsulation

Encapsulation is a concept of bundling data and methods in a single unit or a class. For encapsulations, we use access modifiers. Using this access modifier we can control who can access or use our data and methods throughout the project. There are four access modifiers that we use :

Access ModifierUses
DefaultThe default modifier allows accessibility in the same package but it is not allowed to be used in other packages.
PublicA public modifier allows accessibility from anywhere in our code
PrivateA private modifier allows accessibility within the same class
ProtectedProtected modifier allows accessibility within the same class and subclasses
// Class demonstrating encapsulation and access modifiers
class Employee(private val name: String, var age: Int) {
    private var salary: Double = 0.0
    protected var department: String = "HR"
    var employeeID: Int = 0 // No access modifier, making it package-private

    fun displayInfo() {
        println("Name: $name, Age: $age, Salary: $salary, Department: $department, EmployeeID: $employeeID")
    }

    // Public method to set salary
    fun setSalary(amount: Double) {
        if (amount >= 0) {
            salary = amount
        } else {
            println("Invalid salary amount.")
        }
    }
}

// Subclass inheriting from Employee class
class Manager(name: String, age: Int) : Employee(name, age) {
    fun changeDepartment(newDepartment: String) {
        department = newDepartment
    }
}

fun main() {
    val employee = Employee("John", 30)
    employee.setSalary(50000.0)
    employee.displayInfo()

    val manager = Manager("Alice", 35)
    manager.changeDepartment("Finance")
    manager.employeeID = 1001
    manager.displayInfo()

    // println(employee.salary) // Error: 'salary' is private and cannot be accessed from outside the class
    // println(employee.department) // Error: 'department' has protected visibility and cannot be accessed from outside the class hierarchy
}

In this example code we define a class Employee that represents an employee in an organization. The class encapsulates the employee's name, age, salary, department, and employeeID. These properties are accessed and modified through methods to ensure proper encapsulation and data integrity.

  • The name and age properties are private and can only be accessed within the Employee class.

  • The salary property is private and can only be accessed and modified within the Employee class.

  • The department property is protected, allowing access within the class hierarchy.

  • The employeeID property has no access modifier, making it package-private, meaning it can only be accessed within the same package.

Polymorphism

Polymorphism means one thing has many forms. That means we create one interface or abstract class and use this in many places.
There are two types of polymorphism :

  • Runtime Polymorphism

  • Compile Time Polymorphism

Runtime Polymorphism

When a method in a subclass has the same name and type signature as a method in its superclass then its call function overriding or runtime polymorphism. It is also called late binding or dynamic binding.

open class Animal {
    open fun sound() {
        println("Animal makes a sound")
    }
}

class Dog : Animal() {
    override fun sound() {
        println("Dog barks")
    }
}

class Cat : Animal() {
    override fun sound() {
        println("Cat meows")
    }
}

fun main() {
    val animal1: Animal = Dog()
    val animal2: Animal = Cat()

    animal1.sound() // Output: Dog barks
    animal2.sound() // Output: Cat meows
}

In this example, both Dog and Cat classes override the sound() method inherited from the Animal class. At runtime, when calling the sound() method on animal1 and animal2, the overridden method in each respective subclass (Dog and Cat) gets executed.

Compile time polymorphism

A class having multiple methods by the same name but different parameters is known as method overloading or compile time polymorphism. It is also called early binding or static binding.

class Calculator {
    fun add(a: Int, b: Int): Int {
        return a + b
    }

    fun add(a: Double, b: Double): Double {
        return a + b
    }
}

fun main() {
    val calculator = Calculator()

    val sum1 = calculator.add(3, 4) // Invokes add(int, int) method
    val sum2 = calculator.add(2.5, 3.5) // Invokes add(double, double) method

    println("Sum1: $sum1") // Output: Sum1: 7
    println("Sum2: $sum2") // Output: Sum2: 6.0
}

In this example, the Calculator class has two add() methods with different parameter types (Int and Double). At compile-time, the appropriate method to be invoked is determined based on the method signature and the arguments passed.

Abstraction

Abstraction means hiding the implementation details and showing only functionality. We use abstract class and interface for abstraction.

Abstract class

An abstract class is a special class that has both normal and abstract methods and variables. Abstract methods are the methods that have no code body. We can not create an object for an abstract class to use the abstract class we need to inherit from another class where we have to provide the implementation for that abstract method.

// Abstract class defining methods for a Shape
abstract class Shape {
    abstract fun area(): Double
    abstract fun perimeter(): Double
}

// Concrete implementation of Circle shape
class Circle(private val radius: Double) : Shape() {
    override fun area(): Double {
        return Math.PI * radius * radius
    }

    override fun perimeter(): Double {
        return 2 * Math.PI * radius
    }
}

// Concrete implementation of Rectangle shape
class Rectangle(private val width: Double, private val height: Double) : Shape() {
    override fun area(): Double {
        return width * height
    }

    override fun perimeter(): Double {
        return 2 * (width + height)
    }
}

fun main() {
    val circle = Circle(5.0)
    println("Circle area: ${circle.area()}")
    println("Circle perimeter: ${circle.perimeter()}")

    val rectangle = Rectangle(4.0, 6.0)
    println("Rectangle area: ${rectangle.area()}")
    println("Rectangle perimeter: ${rectangle.perimeter()}")
}

In this example, Shape is an abstract class with abstract methods area() and perimeter(). Classes Circle and Rectangle extend the Shape abstract class and provide their implementations for these methods.

Interface

Interface is one type of reference type. Interface is a collection of abstract methods. Interface is worked as multiple inheritance. Interface can extend other interfaces but cannot extend an abstract class. For the inherited interface we use Implementation but for interfaces that can inherit another interface, we use the Extends keyword.

// Interface defining methods for a Shape
interface Shape {
    fun area(): Double
    fun perimeter(): Double
}

// Concrete implementation of Circle shape
class Circle(private val radius: Double) : Shape {
    override fun area(): Double {
        return Math.PI * radius * radius
    }

    override fun perimeter(): Double {
        return 2 * Math.PI * radius
    }
}

// Concrete implementation of Rectangle shape
class Rectangle(private val width: Double, private val height: Double) : Shape {
    override fun area(): Double {
        return width * height
    }

    override fun perimeter(): Double {
        return 2 * (width + height)
    }
}

fun main() {
    val circle = Circle(5.0)
    println("Circle area: ${circle.area()}")
    println("Circle perimeter: ${circle.perimeter()}")

    val rectangle = Rectangle(4.0, 6.0)
    println("Rectangle area: ${rectangle.area()}")
    println("Rectangle perimeter: ${rectangle.perimeter()}")
}

In this example, the Shape interface defines methods area() and perimeter(). Classes Circle and Rectangle implement the Shape interface and provide their implementations for these methods.

Both examples demonstrate abstraction, allowing us to define a common interface or structure for different types of shapes while leaving the specific implementations to the concrete classes.

Difference between Abstract class and interface

Abstract classInterface
A class can extend only one abstract classA class can extend multiple interfaces
Abstract classes have both methods normal and abstractInterface has only abstract methods as default
The abstract class have variablesThe interface cannot have any type of variable
In the abstract class, we use any access modifier for the variables and for the method, we use public or protectedThe interface is public by default, which means it does not use other modifiers

We've wrapped up the blog post now. Just wanted to give you a heads-up that our next topic in this series is all about Lifecycles! Can't wait to catch up with you on the next blog, Devs.


Connect with Me:

Hey there! If you enjoyed reading this blog and found it informative, why not connect with me on LinkedIn? 😊 You can also follow my Instagram page for more mobile development-related content. πŸ“²πŸ‘¨β€πŸ’» Let’s stay connected, share knowledge and have some fun in the exciting world of app development! 🌟

Check out my Instagram page

Check out my LinkedIn

Β