Несмотря на то, что F# - функциональный язык программирования, тем не менее он поддерживает объектно-ориентированную парадигму. То есть F# позволяет создавать и использовать объекты, которые могут хранить некоторое состояние с помощью свойств и реализовать некоторое поведение посредством методов.
Ключевым инструментом для реализации объектно-ориентированной парадигмы в F# представляют классы. Формальное определение класса:
type название_класса() = class // здесь располагаются свойства и методы класса end
После оператора type идет название класса, после которого располагаются в скобках параметры основного конструктора.
После знака равно (=) между ключевыми словами class и end располагается тело класса - собственно его свойства и методы.
Определим простейший класс:
type Person() = class end
Данный класс называется Person
. Его конструктор не принимает никаких параметров, поэтому после названия класса идут пустые скобки. Кроме того, этот класс не имеет
никаких членов - свойств и методов.
Определив класс, мы можем создать его объекты. Объекты представляют экземпляры класса. Можно еще провести следующую параллель: класс Person представляет условного человека, точно наше общее представление о человеке. Например, каждый человек может иметь имя, возраст, вес, цвет глаз, место работы и т.д.: какие-то общие признаки. Но у каждого конкретного человека эти признаки могут отличаться. В этом случае класс (здесь класс Person) будет выступать в качестве общего представления о человеке, его абстрактный план. А объекты этого класса будут представлять конкретных людей.
Так, определим объекты класса Person:
type Person() = class end // определение класса Person let tom: Person = Person() // определение объекта tom let bob: Person = Person() // определение объекта bob
Здесь определено два объекта класса Person: tom и bob.
Определение объекта аналогично определению значения: после оператора let указывается имя объекта. Далее через двоеточие (как и в общем случае) мы можем указать тип объекта. Затем после знака равно идет создание объекта с помощью вызова конструктора.
Конструктор представляет специальную функцию, которая возвращает значение типа класса. Синтаксически это выглядит так: сначала указывается имя класса, а затем в скобках
передаются значения для его параметров: название_класса(значения_для _параметров)
.
В данном случае мы определили класс Person с пустым конструктором без параметров: type Person()
, поэтому для создания объекта Person
после названия класса также указываем пустые скобки:
let tom: Person = Person() // пустые скобки - вызываем конструктор без параметров
Стоит отметить, что указание типа объекта в примерах выше в принципе избыточно, поскольку опять же, как и в случае с простыми типами, компилятор может вывести тип объекта на основе присваиваемого ему значения:
type Person() = class end // определение класса Person let tom = Person() // определение объекта tom let bob = Person() // определение объекта bob
Привязка выражения do в теле класса позволяет определить действия, которые выполняются при создании объекта. Например, определим вывод на консоль некоторого сообщения при создании объекта класса Person:
type Person() = class do printfn "Создание объекта класса Person" end let tom = Person() let bob = Person()
Для определения действий после ключевого слова do определяются действия, которые будут выполняться при создании каждого объекта. То есть в данном случае при выполнении программы на консоль будет два раза выводиться строка "Создание объекта класса Person"
Стоит отметить, что если мы определяем содержимое класса, то примение операторов class и end, которые определяют границы определения класса необязательно. И мы можем сократить код примера выше следующим образом:
type Person() = do printfn "Создание объекта класса Person" let tom = Person() let bob = Person()
При этом код после оператора do также может быть многострочным:
let mutable count = 0 type Person() = do printfn "Создание объекта класса Person" count <- count + 1 printfn $"Person {count}" let tom = Person() let bob = Person()
Обратите внимание на отступы.