Возможно, при наследовании нас устравает не весь унаследованный функционал. Например, возьмем классы из прошлой темы:
type Person(name, age) =
member this.Name = name
member this.Age = age
member this.Print() = printfn $"Person name: {this.Name} age: {this.Age}"
type Employee(name, age, company) =
inherit Person(name, age)
member this.Company = company
В примере выше класс Employee имеет свойство, которое представляет компанию работника, и мы хотим, чтобы при вызове метода Print также выводилась и информации о компании работника. Что делать в этом случае? Рассмотрим возможные варианты.
Прежде всего, можно поступить доволько просто - определить новый метод в классе Employee с тем же именем:
type Person(name, age) =
member this.Name = name
member this.Age = age
member this.Print() = printfn $"Person name: {this.Name} age: {this.Age}"
type Employee(name, age, company) =
inherit Person(name, age)
member this.Company = company
// скрытие метода Print из родительского класса
member this.Print() =
printfn $"Person name: {this.Name} age: {this.Age}"
printfn $"Works in {this.Company}"
let bob = Employee("Bob", 31, "Microsoft")
bob.Print()
В этом случае определение метода Print просто скрывает реализацию этого метода базового класса Person. Консольный вывод:
Person name: Bob age: 31 Works in Microsoft
Вроде все работает. Но не все так просто. Рассмотрим следующую программу:
type Person(name, age) =
member this.Name = name
member this.Age = age
member this.Print() = printfn $"Person name: {this.Name} age: {this.Age}"
type Employee(name, age, company) =
inherit Person(name, age)
member this.Company = company
member this.Print() =
printfn $"Person name: {this.Name} age: {this.Age}"
printfn $"Works in {this.Company}"
let bob = Employee("Bob", 31, "Microsoft")
let displayInfo(p: Person) = p.Print()
displayInfo(bob)
Здесь для вывода информации об объекте Person определена функция displayInfo, которая в качестве принимает объект Person. Это значит,
что в эту функцию мы можем передать как объекты Person, так и объекты производных от Person классов. В самой функции просто вызываем метод Print.
Однако в любом случае для среды .NET это объект Person и при вызове функции будет вызывать реализацию метода Print из класса Person, что мы увидим по консольному выводу:
Person name: Bob age: 31
Чтобы выйти из этой ситуации, следует переопределить метод Print.
Для переопределения в производном классе метода базового класса нам надо соблюсти два условия:
В базовом классе метод должен быть определен с ключевым словом abstract:
// определение метода abstract member имя_метода : тип_функции default this.имя_метода параметры_метода = тело_метода
После операторов abstract member идет имя метода, после которого через двоеточие указывает тип этого метода - представляемый им
тип функции.
Далее с помощью оператора default определяется реализация этого метода в базовом классе
В производном классе переопределяемый метод определяется с ключевым словом override:
override this.имя_метода параметры_метода = тело_метода
После ключевого слова override определяется реализация этого метода в производном классе
Например, переопределим метод Print:
type Person(name, age) =
member this.Name = name
member this.Age = age
abstract member Print : unit -> unit
default this.Print() = printfn $"Person name: {this.Name} age: {this.Age}"
type Employee(name, age, company) =
inherit Person(name, age)
member this.Company = company
override this.Print() =
printfn $"Person name: {this.Name} age: {this.Age}"
printfn $"Works in {this.Company}"
let bob = Employee("Bob", 31, "Microsoft")
let displayInfo(p: Person) = p.Print()
displayInfo(bob)
Поскольку метод Print принимает только параметр типа unit и также возвращает значение этого типа, то он имеет тип unit -> unit.
Консольный вывод программы:
Person name: Bob age: 31 Works in Microsoft
Ключевое слово base позволяет в производном классе обращаться к функционалу базового класса. Например, в примере выше при переопределении метода Print мы повторяем строку кода из
базового класса:
printfn $"Person name: {this.Name} age: {this.Age}"
Но мы также можем просто вызвать эту реализацию в производном классе:
type Person(name, age) =
member this.Name = name
member this.Age = age
abstract member Print : unit -> unit
default this.Print() = printfn $"Person name: {this.Name} age: {this.Age}"
type Employee(name, age, company) =
inherit Person(name, age)
member this.Company = company
override this.Print() =
base.Print()
printfn $"Works in {this.Company}"
let bob = Employee("Bob", 31, "Microsoft")
let displayInfo(p: Person) = p.Print()
displayInfo(bob)
Теперь вместо повторения кода мы просто обращаемся к реализации в базовом классе:
base.Print()
Свойства переопределяются похожим образом и также требуют соблюдения двух условий:
В базовом классе свойство должно быть определено с ключевым словом abstract:
// определение свойства abstract member имя_свойства : тип_данных with get, set default this.имя_свойства with get() = получение_значение and set value = установка свойства
После операторов abstract member идет имя свойства, после которого через двоеточие указывает тип свойства - тип данных, которые хранит это свойство.
Далее после оператора with указываются методы доступа (get и set), которые имеет класс. Можно определить свойство как с обоими методами - get и set, так и с одним из них.
Далее с помощью оператора default определяется реализация этого свойства в базовом классе
В производном классе переопределяемые свойство определяются с ключевым словом override:
override this.имя_свойства with get() = действия при получении значения and set value = действия при установке значения
После ключевого слова override определяется реализация этого свойства в производном классе
Рассмотрим пример переопределения свойства:
type Person(name, age) =
let mutable _age: int = age
member this.Name = name
abstract member Age: int with get, set
default _.Age
with get() = _age
and set value = if value > 0 then _age <- value
abstract member Print : unit -> unit
default this.Print() = printfn $"Person name: {this.Name} age: {this.Age}"
type Employee(name, age, company) =
inherit Person(name, age)
member this.Company = company
override _.Age
with get() = base.Age
and set value = if value > 18 then base.Age <- value
override this.Print() =
base.Print()
printfn $"Works in {this.Company}"
let bob = Employee("Bob", 31, "Microsoft")
bob.Age <- 13
bob.Print()
bob.Age <- 32
bob.Print()
В данном случае переопределяется свойство Age, которое хранит данные типа int и доступно для чтения и записи.
abstract member Age: int with get, set
Соответственно в базовом и производном классе реализация свойства должна представлять данные типа int и иметь оба метода доступа - get и set.
Также можно определить свойство доступно только для чтения или записи. Например, сделаем свойство Age доступным только для чтения:
abstract member Age: int with get default _.Age with get() = _age
При переопределении этого свойства в производном классе оно также должно иметь только метод get.