Переопределение методов и свойств

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

Возможно, при наследовании нас устравает не весь унаследованный функционал. Например, возьмем классы из прошлой темы:

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 и обращение к функционалу базового класса

Ключевое слово 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.

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