Язык F# поддерживает наследование, то есть мы можем унаследовать функционал одного класса другим классом. Наследование реализуется с помощью ключевого слова inherit. Например:
type Person() = class end type Employee() = inherit Person()
В данном случае определены два класса: класс Person и класс Employee. Класс Employee наследуется от класса Person. В этом отношении класс Person еще называют базовым классом или родительским классом, а класс Employee - поизводным классом или классом-наследником.
Для установки наследования в производном классе выполнить оператор inherit, после которого указывается вызов конструктора базового класса. Так, в классе Employee в примере выше как раз происходит вызов конструктора класса Person
inherit Person()
Наследование устанавливает отношение is a или является. То есть в примере выше класс Employee определяет новый класс, но в то же время объект этого класса также может выступать в качестве объекта класса Person. Другими слова, работник предприятия (Employee) также является человеком (Person). Например:
type Person() = class end type Employee() = inherit Person() let displayInfo(p: Person) = printfn "Объект класса Person" let tom = Person() let bob = Employee() displayInfo(tom) // передача объекта Person displayInfo(bob) // передача объекта Employee
Здесь определена функция displayInfo(), которая в качестве параметра принимает объект Person. Но поскольку объекты Employee также представляют класс Employee, то мы можем их передавать в эту функцию.
let bob = Employee() displayInfo(bob) // передача объекта Employee
Для чего нужно наследование? Установка наследования позволяет сократить объем кода. Например, пусть в базовом классе Person будет определен какой-нибудь функционал:
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) = inherit Person(name, age) let bob = Employee("Bob", 31) bob.Print() printfn $"name: {bob.Name}"
Здесь класс Person, который представляет человека, определяет два свойства Name и Age для хранения имени и возраста соответственно. И также имеет метод для вывода этой информации на консоль. Класс Employee представляет условного работника предприятия. Но работник предприятия (в зависимости от задачи) тоже может иметь имя и возраст и метод для вывода информации. И чтобы заново не определять один и тот же функционал в другом классе, легче его унаследовать от уже имеющегося. Таким образом, класс Employee здесь также будет обладать свойствами Name и Age и методом Print.
Стоит отметить, что поскольку класс Person имеет конструктор с двумя параметрами, то класс Employee при вызове этого конструктора естественно должен передать для них значения. И для этого он сам определяет конструктор с двумя параметрами и передает их значения в конструктор Person.
Производный класс при наследовании должен вызвать конструктор базового класса. Но что если производный класс имеет несколько конструкторов?
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) = inherit Person(name, age) new(name) = Employee(name, 18) new() = Employee("Undefined", 18)
В классе 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 member this.Work() = printfn $"{this.Name} works" let bob = Employee("Bob", 31, "Microsoft") bob.Work() // Bob works printfn $"Company: {bob.Company}" // Company: Microsoft
В данном случае класс Employee дополнительно определяет свойство Company для хранения места работы и метод Work - имитацию процесса работы.
Стоит обратить внимание, что производных класс не имеет доступа к тем компонентам базового класса, которые имеют модификатор доступа private
. Например:
type Person(_name, _age) = let mutable name = _name let mutable age = _age member _.Print() = printfn $"Person name: {name} age: {age}" type Employee(_name, _age, _company) = inherit Person(_name, _age) member this.Company = _company member this.GetEmpInfo() = printfn $"Person name: {name} age: {age}" // Ошибка! поля name и age недоступны
Поля, определяемые с помощью оператора let, по умолчанию имеют модификатор доступа private, поэтому они доступны только внутри своего класса. А производный класс Employee к ним доступа не имеет. То же самое относится и к методам и свойствам, которые определены с модификатором private - они также недоступны для производного класса.