Концепции объектно-ориентированного программирования и способы их реализации в Scala

Scala — это популярный язык программирования, функциональный и объектно-ориентированный, работающий на виртуальной машине Java (JVM). Шаблон объектно-ориентированного программирования (ООП) был разработан для преодоления недостатков, присущих другим методологиям программирования. Этот новый ООП-подход произвел новую революцию в технологиях и языках программирования. ООП позволяет нам писать программы с классами и объектами с учетом важности данных. Так что состояние и поведение этих объектов будут максимально похожи на сущностей реального мира.

Давайте раскроем объектно-ориентированные концепции на примерах:

1. Классы и объекты

1.1 Определение классов

В Scala мы определяем класс с помощью ключевого слова class. Основная функция класса — хранить данные, информацию или состояние. Мы можем создать объект класса с ключевым словом new.

class Person 
val person = new Person

1.2 Определение полей

Мы можем определить поля в классе, используя var или val, как определение переменной.

  • val создает неизменяемую переменную (например, final в Java)
  • var создает изменяемую переменную
class Person {
val name: String = "John"
var age: Int = 26
}

Мы можем получить доступ к полям, создав объект.

val person = new Person
println(person.age)
println(person.name)

1.3 Определение методов

Мы можем определить метод, используя defключевое слово. Не нужно указывать ключевое слово return, так как возвращаемое значение является результатом последней инструкции в методе.

class Person {
val name: String = "John"
var age: Int = 26
def get_info(): Unit = println(s"Age of $name is $age")
}

Мы можем вызвать метод после того, как создадим объект для класса.

val person = new Person
person.get_info()

1.4 Конструкторы

Первичный конструктор используется для присвоения значений в классе, которые будут использоваться позже в полях или методах класса.

class Person(val name: String, val age: Int) {
  def get_info(): Unit = println(s"Age of $name is $age")
}
val person = new Person("John", 26)
println(person.age)

name и age являются параметрами класса, а не полями класса, что означает, что вы не можете получить доступ к этим параметрам через объекты класса. Единственный способ преобразовать параметр класса в поле класса — добавить val / var перед параметр.

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

Инкапсуляция является фундаментальной концепцией ООП, она описывает идею объединения данных и методов, которые работают с этими данными внутри класса.

class Person( private val name: String, private val age: Int) {

 def info(): Unit = println(name +" "+ age)

}

val person = new Person("John", 26)
person.info()

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

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

Scala имеет наследование на уровне одного класса, как и другие языки (т.е. вы можете наследовать только один класс за раз). Класс может наследовать другой класс, используя ключевое слово extends.

class Animal {
  def eat(): Unit = println("nomnom")
}
class Cat extends Animal
val cat = new Cat
cat.eat()

В приведенном выше коде Animal — это суперкласс, а Cat — подкласс. Подкласс может наследовать поля и методы от суперкласса.

class Animal {
  def eat(): Unit = println("nomnom")

  private def walk(): Unit = println("I am walking")

  protected def run(): Unit = println("I am running")
}

В приведенном выше примере:

  • walk() определен как частный метод — доступ к закрытым методам возможен только из класса, в котором метод определен.
  • run() определен как защищенный метод — защищенные методы могут быть доступны классу, в котором метод определен, и подклассу.
class Cat extends Animal {

  def catRun(): Unit = {
    // Works fine as run is a protected method in super class
    run()
  }
}

val cat = new Cat
cat.catRun()

Мы можем использовать ключевое слово final или sealed, чтобы предотвратить наследование. Мы можем назначить это ключевое слово перед любым классом.

final class Vehicle
// Compilation Error: class Car cannot extend final class Vehicle
class Car extends Vehicle

Использование ключевого слова sealed для предотвращения наследования. Классы в одном файле могут наследовать суперкласс, но классы, присутствующие в любом другом файле, не могут наследовать суперкласс. Это может быть полезно, когда вы хотите ограничить количество классов, наследуемых от суперкласса. Предположим, если вы считаете, что Cat и Dog могут быть только двумя подклассами Animal, определите Animal как запечатанное и определите классы Cat и Dog в одном и том же файле.

sealed class Employee 
class Manager extends Employee

4. Полиморфизм

Полиморфизм — это способность языка ООП обрабатывать данные по-разному в зависимости от их типов входных данных.

4.1 Перегрузка методов

Перегрузка метода – это распространенный способ реализации полиморфизма. Методы могут иметь одно и то же имя, но иметь разный список параметров (т. е. количество параметров, порядок параметров и типы данных параметров).

class Person(var name: String, val age: Int) {

  def greet(name: String): Unit = {
    println(s"${this.name} says, Hi $name")
  }

  def greet(): Unit = {
    println(s"Hi, I am $name")
  }

}


object MethodOverloading extends App {
  val person = new Person("John", 26)

  person.greet("Daniel")
  // John says, Hi Daniel

  person.greet()
  // Hi, I am John
}
  • Компилятор не учитывает тип возвращаемого значения при дифференциации перегруженного метода.
  • Вы не можете объявить два метода с одинаковой сигнатурой и разными типами возвращаемого значения. Это вызовет ошибку времени компиляции.

4.2 Переопределение метода

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

Предположим, у нас есть класс Animal с полем creatureType и методом eat, как показано ниже.

class Animal {
  val creatureType = "Wild"
  def eat(): Unit = println("nomnom")
}

Теперь есть еще один класс Dog, который наследует класс Animal, но имеет другую реализацию для поля creatureType и eat. метод.

class Dog extends Animal {
  override val creatureType = "Domestic"
  override def eat(): Unit = println("Crunch, Crunch")
}

Теперь давайте создадим экземпляр этих классов и будем использовать эти члены.

object MethodOverriding extends App {
val animal: Animal = new Animal
  animal.eat() // nomnom
  println(animal.creatureType) // Wild
val dog: Animal = new Dog
  dog.eat() // Crunch, Crunch
  println(dog.creatureType) // Domestic
}
  • Для переопределения методов одним из важнейших правил является то, что класс, который переопределяет, должен использовать модификатор override.
  • В переопределении метода мы не сможем переопределить var с помощью val или наоборот, иначе будет выброшена ошибка.

Спасибо за просмотр этого блога.