Программирование на языке Scala/Object - контейнер кода

Object - контейнер кода

править

Сущность object (объект) используется в качестве:

  1. Наименованного контейнера кода
  2. Уникального значения

В этом уроке рассмотрим object как контейнер кода.

Код проекта необходимо размещать по контейнерам (блокам). Это позволяет разбить проект на части, и далее манипулировать этими частями кода, создавая из них композиции. Одним из таких контейнеров является object.

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

object SomeName {
  // Здесь может быть код 
}

где:

  • object - ключевое слово для определения объекта,
  • SomeName - придуманое имя объекта,
  • { ... } - блок определений и выражений

Название объекта должно начинаться с большой буквы.

Пример определения объекта с набором определений неких значений:

object SomeName {
  val hoursPerDay = 24
  val minsPerHour = 60
  val secsPerMin = 60
  val secsPerHour = secsPerMin * minsPerHour 
  val secsPerDay = {
      val a = secsPerHour
      a * hoursPerDay
  }

  val inc = (a: Int) => a + 1
  val tuple = (0, "s", 0L, "ms")
}

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

val secs = SomeName.secsPerDay
val res = SomeName.inc(secs)

Содержимое объекта создается само и сразу при начале его использования.

Если объект нигде не используется, то он и не создается. Это важно, если он выполняет при создании 'внешние эффекты'. Тем самым он является 'ленивой' сущностью.

Так как названия элементов локализуются внутри объекта-контейнера, то они могут быть более короткими, и можно не бояться конфликта с другими именами вне контейнера. По существу имя элемента состоит из имени объекта и названия самого элемента. Это можно учитывать при создании имен элементов, делая их более читаемыми.

Объект отличается от блока выражений тем, что имеет имя, в то время как блок выражений сам по себе является анонимным. Также объект имеет свой тип, блок выражений имеет тип своего последнего выражения.

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

Обычная программа подразумевает наличие первичного объекта, и. этот объект запускается первым в программе. После запуска первичного объекта, он в своем коде создает все необходимые другие сущности и объекты. Те же, в свою очередь, создают сущности и объекты более глубокие по иерархии. Таким образом код в программе исполняется.

// Первичный объект программы, именно он запускается самым первым.
object Main extends App {
 // Код программы  
}

Первичный объект программы можен иметь любое имя (в данном примере Main), главное, чтобы после имени был указана конструкция extends App. Объектов в программе может бы много, сколько требует архитектура программы, но первичный объект обычно только один.

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


Небольшое отступление. Название 'object' исходит из объектно-ориентированной парадигмы (ООП), где объектом называют экземпляр класса (классы будут обсуждаться в дальнейших уроках). Действительно, сущность 'object' является уже созданным и единственным экземпляром класса, который по существу определяется после ключевого слова object. Хотя использование object специфично в рамках языка Scala, он является полноценным 'объектом' в контексте ООП.

Сущность первого класса

править

Object - является сущностью первого класса.

Термин "сущность первого класса" (англ. first-class object, first-class entity, first-class citizen) относится к тому, как можно манипулировать с элементами данного вида.

Сущности первого класса считаются полноценными элементами языка программирования, которые могут быть использованы следующими способами:

  1. Именованное значение: Сущности первого класса могут инициализировать именованые значения.
  2. Передача в качестве аргументов: Они могут быть переданы в функции и методы в качестве аргументов.
  3. Возврат из функций: Функции могут возвращать сущности первого класса в качестве своего результата.
  4. Хранение в структурах данных: Они могут храниться в массивах, списках, словарях и других структурах данных.
  5. Создание динамически: Вы можете создавать и изменять сущности первого класса в процессе выполнения программы.

Object - также является сущностью первого класса, хотя динамическое создание (5 пункт) для объекта является бессмысленным. Объекты создаются автоматически.

Название объекта не является названием типа этого объекта, это имя созданного объекта. Тем не менее, название объекта необходимо писать с большой буквы. Тип объекта можно получить только используя его базовый метод type.

Пример манипуляций с объектом:

// Определяем некий объект
object SomeName {
  // Объект содержит некую функцию
  val fun: String => Unit = str => println(str)
}

// Вызов функции из объекта
SomeName.fun("abc")

// Определяем значения объекта. Значение имеет тип объекта.
val sn: SomeName.type = SomeName

// Вызов функции из объекта
sn.fun("abc")

// Определение функции, которая имеет параметр типа объекта SomeName и возращает объект типа SomeName
val fun2: (String, SomeName.type) => SomeName.type = (str, someName) => {
  someName.fun(str)
  SomeName
}

// Объект как сущность первого класса может храниться в структурах данных
val tuple: (SomeName.type, SomeName.type) = (sn, SomeName) // Один и тот же объект находится в кортеже из двух элементов

Методы объектов

править

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

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

def add(param1: Int, param2: Int): Int = param1 + param2

здесь:

  • def add - def - ключевое слово определения метода с названием add,
  • в круглых скобках список определяемых параметров,
  • param1: Int - параметр с типом Int,
  • param2: Int - параметр с типом Int,
  • : Int - тип результата (значения) метода,
  • = - начало описания тела метода
  • param1 + param2 тело метода заданое выражением.

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

Пример определения методов в объекте:

object SomeName {
  // Метод с одним выражением
  def methodName(param1: String, param2: Int): Boolean = param1.isEmpty && param2.isValidChar

  // Метод с блоком выражений
  def methodName2(param: Int): Boolean = {
    val res1 = param.isValidChar
    val res2 = (param + 1).isValidChar
    res1 || res2
  }
}

Вызов метода при его использовании (вычисления результата метода или получения значения метода) осуществляется указанием его имени и списка аргументов для его параметров в строгом порядке их определения.

val bool: Boolean = methodName("abc", 1) // вызов ранее определенного метода methodName, с указанием списка аргументов.

где:

  • для параметра param1 указан аргумент "abc",
  • для параметра param2 указан аргумент 1.

Указание аргументов при вызове метода выполнено в порядке их определения в методе.

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

Необходимость наличия в языке двух похожих сущностей (метода и анонимной функции) связана с необходимостью реализовать полноценную поддержку одновременно двух концепций программирования - объектно-ориентированой и функциональной.

При необходимости методы можно легко конвертировать в анонимные функции, являющимися сущностями первого класса. Обратная конвертация не имеет смысла.

И анонимной функции и методы часто просто называют функциями в процессе функционального стиля программирования.

Сигнатура метода

править

Имя метода и список его параметров называется сигнатурой метода. В сигнатуру метода не входит тип значения метода.

Методы в одном объекте могут быть с одинаковыми именами, при условии, что список параметров их отличается. Такие методы называются перегруженными (overload). В таких методах различные сигнатуры. Методы с одинаковыми сигнатурами, но разными типами значений метода не могут находиться в одном объекте. У них одинаковые сигнатуры.

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

Файлы проектов

править

Файлы с расширением .sc это вспомогательные файлы, в которых код проекта не размещается. В них можно хранить код экспериментов.

Код проекта размещается в файлах с расширением .scala.

Режим IDE отладки кода

править

Код размещенный в файлах с расширением .scala поддерживает режим отладки кода при запуске его в IDE (Integrated Development Environment) - интегрированной среде разработки. Отладка кода подразумевает остановку выполнения кода в заданных точках останова и далее пошаговое выполнение кода, с отслеживанием значений в коде.

Точки останова устанавливаются кликом строчки кода в её начале.

Запуск режима отладки в среде IDE IDEA выполняется клавишей F9. После остановки выполнения кода для продолжения также используется F9.

Для пошагового режима (остановка на каждой команде) можно использовать F8.

Домашнее задание

править

1. В учебном проекте создать файл Objects.scala. Обратите внимание на расширение файла, оно должно быть .scala.

2. Создайте в файле объект Main с суффиксом extends App. Создайте в объекте два метода def helloWorldPrint c одинаковыми названиями, которые вывели бы в консоль строки "Hello world!". Запустите эти два метода.

3. Создайте объект с именем FirstClassObject. В объекте создайте println("Start"). Запустите программу используя объект Main. Строка "Start" не будет выведена в консоль. Как сделать так, чтобы эта строка появилась в консоле.

4. В объекте FirstClassObject создайте метод firstMethod, который будет выводить в консоль строку "Yo". Вызовите этот метод в объекте Main. Запустите программу и убедитесь что в консоле есть "Yo".

5. В объекте FirstClassObject создайте метод thirdMethod c одним параметром типа String. Тело метода - println(str). В объекте Main создайте метод secondMethod c одним параметром типа FirstClassObject. Внутри метода вызовите метод thirdMethod с аргументом "Yo-Yo". Запустите программу и убедитесь что в консоле есть "Yo-Yo".

6. Повторить материалы урока Строгая статическая типизация