Абстрактные классы

Последнее обновление: 12.12.2023

Классы обычно представляют некий план определенного рода объектов или сущностей. Например, мы можем определить класс 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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850