Классы обычно представляют некий план определенного рода объектов или сущностей. Например, мы можем определить класс Car для преставления машин или класс Person для представления людей, вложив в эти классы соответствующие свойства, поля, методы, которые будут описывать данные объекты. Однако некоторые сущности, которые мы хотим выразить с помощью языка программирования, могут не иметь конкретного воплощения. Например, в реальности не существует геометрической фигуры как таковой. Есть круг, прямоугольник, квадрат, но просто фигуры нет. Однако же и круг, и прямоугольник имеют что-то общее и являются фигурами.
Для реализации подобных сущностей, которые не имеют конкретного воплощения, предназначены абстрактные классы. Как правило, абстрактные классы объявляют некоторые компоненты - методы и свойства без реализации А реализацию для этих свойств и методов предоставляют производные классы.
Стоит отметить, что восприятие или рассмотривание сущностей как абстрактных или конкретных во многом зависит от задачи и угла зрения, для каких-то задач одни сущности могут быть представлены в виде конкретных классов, а в других задачах те ще сущности можно определить в виде абстрактных классов.
Абстрактный класс определяется с помощью атрибута [<AbstractClass>]
[<AbstractClass>] type имя_класса = тело класса
Рассмотрим на примере абстрактного класса, который представляет геометрическую фигуру:
[<AbstractClass>] type Shape() = class end
Отличительной особенностью абстрактных классов является то, что мы не можем использовать конструктор абстрактного класса для создания его объекта. Например, следующим образом:
[<AbstractClass>] type Shape() = class end let someShape = Shape() // ! Ошибка
Тем не менее абстрактные классы полезны тем, что могут определять некоторый общий функционал для классов наследников. Например,
[<AbstractClass>] type Shape(startX, startY) = class let mutable x = startX let mutable y = startY member _.printStartPoint()= printfn $"x: {x} y: {y}" end type Rectangle(width, height, startX, startY) = inherit Shape(startX, startY) member this.Width = width member this.Height = height type Circle(radius, startX, startY) = inherit Shape(startX, startY) member this.Radius = radius let rectangle = Rectangle(50, 40, 4, 5) rectangle.printStartPoint() let circle = Circle(10, 20,7) circle.printStartPoint()
Здесь определен абстрактный класс фигуры Shape. Он имеет два поля, которые хранят координаты точки начала фигуры (например, для окружности это может быть координаты центра окружности, а для прямоугольника - координаты верхнего левого угла)
Этот класс наследуется классами Rectangle и Circle, которые представляют соответственно прямоугольник и окружность. Каждый из этих классов может иметь какие-то свои дополнительные свойства. Например, в классе Circle можно определить свойство/поле для хранения радиуса. А класс Rectangle мог бы иметь дополнительные свойства для ширины и высоты и т.д. То есть каждый конкретный тип сообщений может иметь свои отличительные признаки. Но оба этих класса будут иметь общие поля для хранения координат начальной точки, а также метод, который выводит эти координаты. И для хранения общего функционала мы как раз можем определить абстрактный класс.
Отличительной чертой абстрактных классов является то, что они могут иметь абстрактные методы и свойства без реализации. Абстрактный метод должен быть определен с ключевым словом abstract:
abstract member имя_метода : тип_функции
После операторов abstract member
идет имя метода, после которого через двоеточие указывает тип этого метода - представляемый им тип функции.
В производном классе мы должны определить для этого метода реализацию с помощью ключевого слова override:
override this.имя_метода параметры_метода = тело_метода
Например, в примере выше и прямоугольник, и окружность могут иметь площадь и соответственно метод для вычисления площади. Но каждый из них по своему определяет вычисление площади. Так, для прямоугольника это произведение ширины на высоту, а для окружности - произведение квадрата радиуса на число PI. И мы можем определить этот метод как абстрактный, но без конкретной реализации в абстрактном классе
[<AbstractClass>] type Shape(startX, startY) = class let mutable x = startX let mutable y = startY member _.printStartPoint()= printfn $"x: {x} y: {y}" abstract CalculateArea: unit->float end type Rectangle(width, height, startX, startY) = inherit Shape(startX, startY) member this.Width = width member this.Height = height override this.CalculateArea() = this.Width * this.Height type Circle(radius, startX, startY) = inherit Shape(startX, startY) member this.Radius = radius override this.CalculateArea() = this.Radius * System.Math.PI let printArea(shape: Shape) = printfn $"Area = {shape.CalculateArea()}" let rectangle = Rectangle(50.0, 40.0, 4, 5) let circle = Circle(10.0, 20, 7) printArea(rectangle) // Area = 2000 printArea(circle) // Area = 31.41.........
Здесь в классе Shape определен абстрактный метод CalculateArea
, который принимает параметр типа unit
и возвращает значение типа
float
. Причем этот метод не имеет реализации. Конкретную реализацию предоставляют классы-наследники Rectangle и Circle.
Если базовый класс определяет абстрактный метод, то классы-наследники обязаны реализовать этот метод (кроме тех случаев, когда классы-прямые наследники сами являются абстрактными).
Также стоит отметить, что абстрактный метод можно определить только в абстрактном классе.
Подобным образом также можно определять абстрактные свойства. Они определяются с ключевым словом abstract:
abstract member имя_свойства : тип_данных with get, set
После операторов abstract member
идет имя свойства, после которого через двоеточие указывается тип свойства - тип данных,
которые хранит это свойство. Затем после оператора with указываются методы доступа (get и set), которые имеет класс.
Можно определить свойство как с обоими методами - get и set, так и с одним из них.
В производном классе для абстрактного свойства определяется реализация с помощью ключевого слова override. Но стоит отметить, что абстрактные свойства могут быть только в абстрактных классах. Кроме того, как и с абстрактными методами, неабстрактные классы-наследники обязаны предоставить для этого свойства реализацию.
Например, изменим предыдущий пример и вынесем вычисление площади в свойство:
[<AbstractClass>] type Shape(startX, startY) = class let mutable x = startX let mutable y = startY member _.printStartPoint()= printfn $"x: {x} y: {y}" abstract Area: float with get end type Rectangle(width, height, startX, startY) = inherit Shape(startX, startY) member this.Width = width member this.Height = height override this.Area = this.Width * this.Height type Circle(radius, startX, startY) = inherit Shape(startX, startY) member this.Radius = radius override this.Area = this.Radius * System.Math.PI let printArea(shape: Shape) = printfn $"Area = {shape.Area}" let rectangle = Rectangle(50.0, 10.0, 4, 5) let circle = Circle(10.0, 20, 7) printArea(rectangle) // Area = 500 printArea(circle) // Area = 31.41.........
В данном случае класс Shape определяет свойство Area
, которое имеет только метод get
, то есть доступно только для чтения. Соответственно в классах наследниках
нам надо определить подобное свойство, которое имеет только метод get