Объектно-ориентированное программирование (ООП) — это парадигма программирования, которая зависит от классов и объектов. Это помогает разработчикам программного обеспечения структурировать свои коды и делать их многократно используемыми частями.

Разработчики программного обеспечения создают для этого множество различных классов.
Классы — это определяемые пользователем типы данных, которые имеют свои собственные атрибуты и методы. Эти методы могут определять логические операции класса. А атрибуты могут содержать данные, которые может иметь класс.
Мы можем создать столько экземпляров класса, сколько захотим. Эти экземпляры называются объектами.

Концепции объектно-ориентированного программирования

Объектно-ориентированное программирование имеет четыре основных понятия. Эти концепции могут показаться вам немного сложными, если вы не были знакомы с ними раньше. Но понимание того, как работают эти концепции, может очень помочь вам в вашей карьере разработчика программного обеспечения.

Инкапсуляция

Инкапсуляция означает, что атрибуты класса скрыты от любого другого класса и доступны только через любые методы-члены собственного класса, в которых объявлены атрибуты.
Для этого мы можем определить атрибуты как закрытые. Затем мы можем добавить методы getter-setter как общедоступные для атрибута.
Существует 3 типа видимости для переменных и методов. Это:

  • общедоступный: если переменные или методы определены как общедоступные, к ним можно получить доступ из любого другого класса путем создания экземпляра.
  • private: Если переменные или методы определены как private, к ним можно получить доступ только в классе, в котором мы определили переменные или методы.
  • защищено. Если переменные или методы определены как защищенные, к ним можно получить доступ только из класса, в котором были определены переменные или методы, или из других классов, которые были расширены из этого класса и имеют переменные или методы.

Я приведу вам пример инкапсуляции. Но нам не нужно беспокоиться об инкапсуляции в Kotlin. Я объясню причину после того, как приведу пример кода:

class EncapsulationExample {
    private var testString: String = ""
    private var testInteger: Int = 0
}

Нам не нужно беспокоиться об инкапсуляции в Kotlin. Потому что компилятор Kotlin для JVM компилирует исходные файлы Kotlin в файлы классов Java. Таким образом, наш код будет скомпилирован следующим образом:
Примечание: Имя класса не изменится. Я изменил его, чтобы сделать его более понятным.

public class EncapsulationCompiled {
    private String testString = "";
    private int testInteger = 0;

    public String getTestString() {
        return testString;
    }

    public void setTestString(String testString) {
        this.testString = testString;
    }

    public int getTestInteger() {
        return testInteger;
    }

    public void setTestInteger(int testInteger) {
        this.testInteger = testInteger;
    }
}

Абстракция

Абстракция — это процесс сокрытия внутренних деталей класса от внешнего мира.
Мы можем определять абстрактные методы или абстрактные классы. Если у одного класса есть хотя бы один абстрактный метод, это означает, что класс может быть абстрактным классом. Абстрактные классы обычно используются для сбора объектов с общими свойствами под одной крышей.

abstract class AbstractionExample{
    abstract fun abstractMethod()
    
    fun methodWithBody(){
        println("This method has a body.")
    }
}

Но, если все методы абстрактные и нет ни одного метода с телом, значит это интерфейс.

interface InterfaceExample {
    fun methodOne()
    fun methodTwo()
}

Наследование

Наследование означает, что один класс приобретает атрибуты и методы другого класса. Это означает, что мы можем создавать новые классы поверх существующих классов.
Основная цель наследования — обеспечить возможность повторного использования кода. Итак, класс должен писать только уникальные атрибуты и методы. Остальные общие атрибуты и методы могут быть расширены из другого класса.
Здесь появляются два новых термина. Родительский класс и дочерний класс.

  • Родительский класс. Это класс, атрибуты и методы которого будут использоваться дочерним классом.
  • Дочерний класс. Это класс, который расширяет возможности родительского класса.

Примечание. Если мы хотим создать расширяемый класс (родительский класс) в Kotlin, мы должны добавить ключевое слово «open» перед ключевым словом «class». В противном случае мы не сможем расширить этот класс из других классов. Как и в случае с классами, если мы хотим переопределить метод в дочернем классе, мы должны добавить ключевое слово «open» перед ключевым словом «fun» в родительском классе.

open class InheritanceParentClass{
    var attributeParentClass1 = "Attribute in Parent Class"
    private var attributeParentClass2 = "We can't reach this attribute in child class. Because we defined it as private"

    fun methodParentClass1() = "This is first method in InheritanceParentClass"

    open fun methodParentClass2() = "This is second method in InheritanceParentClass. We will override this one."
}

Как мы узнали из раздела «Инкапсуляция», мы не можем получить доступ к атрибутам или методам в дочернем классе, которые определены как закрытые в родительском классе. Поэтому, если мы хотим, чтобы их можно было использовать в дочернем классе, мы не должны добавлять ключевое слово private.

class InheritanceChildClass: InheritanceParentClass() {
    fun methodChildClass1() = "This is first method in InheritanceParentClass"

    override fun methodParentClass2() = "This method has overridden in child class"

    fun printAttributeParentClass1(){
        println("$attributeParentClass1")
        attributeParentClass1 = "Attribute in Child Class"
        println("$attributeParentClass1")
    }
}

Я дам очень простой основной метод для печати этих методов.

fun main(){
    var parentClass = InheritanceParentClass()
    var childClass = InheritanceChildClass()

    println("Parent Class - methodParentClass1: ${parentClass.methodParentClass1()}")
    println("Parent Class - methodParentClass2: ${parentClass.methodParentClass2()}")

    println("Child Class - methodChildClass1: ${childClass.methodChildClass1()}")
    println("Child Class - methodParentClass2: ${childClass.methodParentClass2()}")
    childClass.printAttributeParentClass1()
}

Вывод:

Parent Class - methodParentClass1: This is first method in InheritanceParentClass
Parent Class - methodParentClass2: This is second method in InheritanceParentClass. We will override this one.
Child Class - methodChildClass1: This is first method in InheritanceParentClass
Child Class - methodParentClass2: This method has overridden in child class
Attribute in Parent Class
Attribute in Child Class

Полиморфизм

С помощью полиморфизма мы можем выполнять одно и то же действие по-разному. Для этого нам нужно определить один интерфейс, класс или абстрактный класс, и у нас будет несколько реализаций.
Я приведу краткий пример. Вы увидите, что с помощью полиморфизма метод может выполнять различные операции в зависимости от объекта, над которым он действует.

Во-первых, у нас будет родительский класс под названием «PolymorphismBaseClass».

open abstract class PolymorphismBaseClass {
    open fun runMobileServiceOnDevice(){
        println("This device is not running any mobile services.")
    }

    abstract fun mobileServicesName(): String
}

Затем мы можем создать дочерние классы из этого родительского класса. Наши дочерние классы будут использовать тот же метод. Эти методы будут выполнять разные, но похожие операции.

class PolymorphismHuaweiClass: PolymorphismBaseClass() {
    override fun runMobileServiceOnDevice() {
        println("This device can work with Huawei Mobile Services.")
    }

    override fun mobileServicesName(): String = "Huawei Mobile Services"
}
class PolymorphismGoogleClass: PolymorphismBaseClass() {
    override fun runMobileServiceOnDevice() {
        println("This device can work with Google Mobile Services.")
    }

    override fun mobileServicesName(): String = "Google Mobile Services"
}