Возможно, при наследовании нас устравает не весь унаследованный функционал. Например, возьмем классы из прошлой темы:
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
.