Fundamentals

Introduction

Class inheritance is the ability to create a class that is based on another class, or to create new classes that get their primary functionalities from a certain class. The primary class is referred to as the parent or base class. A child class that is based on the parent class is also said to be derived from it. A child class can have only one parent class.

Applying an Inheritance

Before indicating an inheritance, you must have a parent class. You can use one of the many built-in classes available from the .NET Framework (in reality, not all classes can be used as parents).

Inheritance is done using the inherit keyword. The basic formula to inherit from a known class is:

type new-class-name =
    inherit parent-class-name
	body

Inheritance and the Primary Constructor

There are various ways to perform class inheritance. The most fundamental way is to use the primary constructor of the parent class. The formula to follow is:

type new-class-name(parameter(s)) =
    inherit parent-class-name(parameter(s))

With an existing class in mind, to perform inheritance, specify the name of the new class you are creating as new-class-name. The new class can (should) be followed by parentheses that would include one or more parameters. Add the = symbol followed by the inherit keyword. Then, call the primary constructor of the parent class and pass to it the argument(s) created in the parentheses of the new class. This can be done as follows:

type Circle(rad) =
    member this.Radius = rad
    
type Sphere(rad) =
    inherit Circle(rad)

The Body of a Child Class

A child class is primarily a class like any other. It has a body. The body of the child class starts after the primary construtor in the child class:

type new-class-name(parameter(s)) =
    inherit parent-class-name(parameter(s))
	body

Properties in a Child Class

In the body of a child class, you can create the behavior you want. For example, you can create one or more properties. Remember that a property is created with the member keyword.

Using a Child Class

You can use a child class just like any other: declare a variable and access the member(s) of the class. Here is an example

type Circle(rad) =
    member this.Radius = rad

type Sphere(rad) =
    inherit Circle(rad)
        
let ball = new Sphere(63.7384);

printfn "Sphere Characteristics";
printfn "----------------------";
printfn "Radius:  %f" ball.Radius;
printfn "======================";

This would produce:

Sphere Characteristics
----------------------
Radius:  63.738400
======================

Press any key to close this window . . .

In the above example, we used a parent class that has a primary constructor that takes one parameter. In the same way, you can use a parent class that uses more than one parameter. In the child class, you can make the primary constructor take the same number of parameters as the parent class. After the inherit keyword, call the primary constructor of the parent class and pass the parameters specified in the primary constructor of the child class. When creating an object of the child class, pass the parameters of the primary constructor of the child class. This can be done as follows:

type Property(number, value) =
    member self.PropertyCode = number
    member self.MarketValue = value

type SingleFamily(number, value) =
    inherit Property(number, value)
        
let house = new SingleFamily(295_370_849, 475840)

printfn "House Description";
printfn "-------------------------"
printfn "Property #:   %i" house.PropertyCode
printfn "Market Value: $%d" house.MarketValue
printfn "=========================";

This would produce:

House Description
-------------------------
Property #:   295370849
Market Value: $475840
=========================

Press any key to close this window . . .

You can also make the primary constructor of the child class have more parameters than the primary constructor of the parent class. In this class, after the inherit keyword in the child class, call the primary constructor of the parent class. Pass the parameters that correspond to those of the primary constructor of the parent class. Here is an example:

type Person(first, last) =
    member me.FirstName : string = first
    member me.LastName  : string = last

type Customer(first, last, acntNumber)=
    inherit Person(first, last)

In the body of the child class, you can create a member property and assign one of the extra parameters. Here is an example:

type Person(first, last) =
    member me.FirstName : string = first
    member me.LastName  : string = last

type Customer(first, last, acntNumber)=
    inherit Person(first, last)
        member me.AccountNumber : string = acntNumber;
        
let client = new Customer("Aaron", "Hansman", "106-03-7384");
printfn "Kolo Bank - Customer Record";
printfn "First Name: %s" client.FirstName;
printfn "Last Name:  %s" client.LastName;
printfn "Account #:  %s\n" client.AccountNumber;

This would produce:

Kolo Bank - Customer Record
First Name: Aaron
Last Name:  Hansman
Account #:  106-03-7384

Press any key to close this window . . .

Accessing the Base Class

To give you access to the members of the parent class from the child class, the F# language provides the base keyword. To use it, type base followed by a period and the parent member you want to access (you may remember that members declared with the let keyword in the parent class are private; as a result, such members are not accessible in the child class). Here are examples:

type Person(first, last) =
    member this.FirstName : string = first
    member me.LastName  : string = last

type Customer(first, last, acntNumber) =
    inherit Person(first, last)
        member me.AccountNumber : string = acntNumber;
        member me.FullName = base.LastName + ", " + base.FirstName;
        
let client = new Customer("Frank", "Justice", "148-95-8350");
printfn "Kolo Bank - Customer Record";
printfn "Full Name: %s" client.FullName;
printfn "Account #:  %s\n" client.AccountNumber;

This would produce:

Kolo Bank - Customer Record
Full Name: Justice, Frank
Account #:  148-95-8350

Press any key to close this window . . .

Transitive Inheritance

Just as a child class can inherit from a parent class, another class can inherit from the child class, making such a new class a grand-child of the first parent class. When a class C inherits from a class B that itself inherits from a class A, class C inherits the (public) members from both class B (its own parent) and from class A (its grand-parent). This is illustrated in the following example. It works as follows. This is a financial bank application where customer records are created from a Person class. A customer object gets its foundational information from the Person class. A typical employee (who works for the bank) also has a bank account. This means that an employee is also a customer, which means an employee record primarily has the exact same pieces of information as a customer, which makes an Employee class inherit from the Customer. On the other hand, an employee record has details that the bank doesn't need from customer, such as a salary or an employment status:

type EmploymentStatus =
    | Unknown  = 0
    | FullTime = 1
    | PartTime = 2
    | Intern   = 3
    | Seasonal = 4

type Person(first, last) =
    member me.FirstName : string = first
    member me.LastName  : string = last

type Customer(first, last, acntNumber)=
    inherit Person(first, last)
        member me.AccountNumber : string = acntNumber;
        member me.FullName = base.LastName + ", " + base.FirstName;

type Employee(emplNumber, first, last, acntNumber, employmentStatus, salary) =
    inherit Customer(first, last, acntNumber)
        member me.EmployeeNumber : string = emplNumber;
        member me.EmploymentStatus : EmploymentStatus = EmploymentStatus.FullTime
        member me.Salary : int = salary
        
let client = new Customer("Frank", "Justice", "6148-795-8350");
let staff = new Employee("9702-9741",
                         client.FirstName, client.LastName, client.AccountNumber,
                         EmploymentStatus.FullTime, 68000);
printfn "Kolo Bank - Employee Record";
printfn "First Name:          %s" client.FirstName;
printfn "Last Name:           %s" client.LastName;
printfn "Employee #:          %s" staff.EmployeeNumber;
printfn "Employment Status #: %A" staff.EmploymentStatus;
printfn "Yearly Salary:       %i" staff.Salary;
printfn "Bank Account #:      %s" client.AccountNumber;

This would produce:

Kolo Bank - Employee Record
First Name:          Frank
Last Name:           Justice
Employee #:          9702-9741
Employment Status #: FullTime
Yearly Salary:       68000
Bank Account #:      6148-795-8350

Press any key to close this window . . .

Methods in a Derived Class

Probably the most valuable feature of class inheritance is that a child class can acquire new behavior not available from the parent class. One of the options you have is to add new methods (and properties) to the new class. For example, you can add constructors to anticipate various ways to create objects from the derived class. Here is an example:

type EmploymentStatus =
    | Unknown  = 0
    | FullTime = 1
    | PartTime = 2
    | Intern   = 3
    | Seasonal = 4

type Person(first, last) =
    member my.FirstName : string = first
    member my.LastName  : string = last

type Customer(first, last, acntNumber) =
    inherit Person(first, last)
        member my.AccountNumber : string = acntNumber
        member my.FullName = base.LastName + ", " + base.FirstName
        new() = Customer("John", "Doe", "0000-000-0000")
        new(first, last) = Customer(first, last, "0000-000-0000")

type Employee(emplNumber, first, last, acntNumber, employmentStatus, salary) =
    inherit Customer(first, last, acntNumber)
        member my.EmployeeNumber : string = emplNumber;
        member my.EmploymentStatus : EmploymentStatus = EmploymentStatus.FullTime
        member my.Salary : int = salary
        new() = Employee("0000-0000", "John", "Doe", "0000-000-0000", EmploymentStatus.Unknown, 0)
        new(first, last) = Employee("0000-0000", first, last, "0000-000-0000", EmploymentStatus.Unknown, 0)
        new(emplNumber, first, last) = Employee(emplNumber, first, last, "0000-000-0000", EmploymentStatus.Unknown, 0)
        new(emplNumber, first, last, acntNumber) = Employee(emplNumber, first, last, acntNumber, EmploymentStatus.Unknown, 0)

You can use such constructors when creating an object. In the same way, you can expand a child class anyway you see fit.

Object Casting

Introduction

We are already familiar with conversion functions that are used to convert a value from one type to another. Here is an example that call the int() function to convert a floating point number to an integer:

let a = 628.305
let b = int a;

printfn "Number: %.03f" a;
printfn "Number: %d" b;

The most important issue about conversion is that the types must be compatible. The conversion primarily follows the logic by which the number of bits used by, or to hold the values of, a type A is compatible, that is reconcialable, with the number of bits used by, or to hold the values of, a type B. In this case, the value of type A can be converted to a type B. As a result, if the values are not compatible, or the bits are not reconcialable, the conversion will fail.

Just as conversion is possible among values of primitive types, conversion is also possible among objects created from classes. Just as primitive conversion must follow compatible types, object conversion must follow compatibility. When it comes to objects, conversion is based on class hierarchy, which means that inheritance must be established between two classes. Therefore, when it comes to classes, conversion is referred to as casting. Two options exist: downcasting and upcasting.

Up-Casting an Object

Based on a relationship between a child class and its parent, an object created from a child class can be converted to the parent class. This is referred to as up-casting or upcasting. The casting is carried by the :> operator. The formula to follow is:

derived-object :> parent-class

In this formula, the derived-object can be a variable declared from a class that is derived from another class. The parent-class is a class alligned in the upper ancestry of the class from which the variable was declared. Here is an example:

type Person(first, last) =
    member this.FirstName : string = first;
    member this.LastName  : string = last;

type Customer(first, last, acntNumber)=
    inherit Person(first, last)
        member this.AccountNumber : string = acntNumber;
        member this.FullName = base.LastName + ", " + base.FirstName;

let client = new Customer("Frank", "Justice", "6148-795-8350");

let individual = client :> Person

printfn "Kolo Bank - Customer as a Person";
printfn "First Name: %s" individual.FirstName;
printfn "Last Name:  %s\n" individual.LastName;

This would produce:

Kolo Bank - Customer as a Person
First Name: Frank
Last Name:  Justice

Press any key to close this window . . .

In the same way, an object created from any way down a hierarchy can be cast to a class up the hierarchy. Here is an example:

type EmploymentStatus =
    | Unknown  = 0
    | FullTime = 1
    | PartTime = 2
    | Intern   = 3
    | Seasonal = 4

type Person(first, last) =
    member this.FirstName : string = first
    member this.LastName  : string = last

type Customer(first, last, acntNumber)=
    inherit Person(first, last)
        member this.AccountNumber : string = acntNumber;
        member this.FullName = base.LastName + ", " + base.FirstName;

type Employee(emplNumber, first, last, acntNumber, employmentStatus, salary) =
    inherit Customer(first, last, acntNumber)
        member this.EmployeeNumber : string = emplNumber;
        member this.EmploymentStatus : EmploymentStatus = EmploymentStatus.FullTime
        member this.Salary : int = salary

let staffMember = new Employee("9792-4085", "Jonathan", "Lawson", "3741-957-9407", EmploymentStatus.PartTime, 48000);

let persFirstName = (staffMember :> Person).FirstName;
let persLastName  = (staffMember :> Person).LastName;

let clientFirstName  = (staffMember :> Customer).FirstName;
let clientLastName   = (staffMember :> Customer).LastName;
let clientAcntNumber = (staffMember :> Customer).AccountNumber;

printfn "Kolo Bank - Employee as Person"
printfn "First Name: %s" persFirstName;
printfn "Last Name:  %s\n" persLastName;

printfn "Kolo Bank - Customer as a Person";
printfn "First Name: %s" clientFirstName;
printfn "Last Name:  %s" clientLastName;
printfn "Account #:  %s\n" clientAcntNumber;

This would produce:

Kolo Bank - Employee as Person
First Name: Jonathan
Last Name:  Lawson

Kolo Bank - Customer as a Person
First Name: Jonathan
Last Name:  Lawson
Account #:  3741-957-9407

Press any key to close this window . . .

When upcasting, if you are using Microsoft Visual Studio, the Intellisense will show the members of the upper-class:

Up-Casting an Object

Up-Casting an Object

Down-Casting an Object

Down-casting is the ability to convert an object to a class that is a child of the one that served to create the object. The operator used to perform this operation is :?>. The formula to follow is:

some-object :?> Child-Class

As you can imagine, down-casting is the opposite to up-casting. Up-casting is easy as we know that, except for the Object class of the .NET Framework (the Object class is the ultimate parent or ancestor to all classes of your programs in F#), every class has a parent, but not every class has a child. This means that, when you decide to down-cast, the compiler must find out whether the class used to create your object has a child. For this reason, the compiler waits until the application executes to resolve the compatibility issue. For this reason, down-casting may require some gymnastics.

To down-cast, you can first create an object, up-cast it to the appropriate parent class, then down-cast as necessary. This can be done as follows:

type EmploymentStatus =
    | Unknown  = 0
    | FullTime = 1
    | PartTime = 2
    | Intern   = 3
    | Seasonal = 4

type Person(first, last) =
    member this.FirstName : string = first
    member this.LastName  : string = last

type Customer(first, last, acntNumber) =
    inherit Person(first, last)
        member this.AccountNumber : string = acntNumber
        member this.FullName = base.LastName + ", " + base.FirstName
        new(first, last) = Customer(first, last, "0000-000-0000")


type Employee(emplNumber, first, last, acntNumber, employmentStatus, salary) =
    inherit Customer(first, last, acntNumber)
        member this.EmployeeNumber : string = emplNumber
        member this.EmploymentStatus : EmploymentStatus = EmploymentStatus.FullTime
        member this.Salary : int = salary
        new(first, last) = Employee("0000-0000", first, last, "0000-000-0000", EmploymentStatus.Unknown, 0)
        new(emplNumber, first, last) = Employee(emplNumber, first, last, "0000-000-0000", EmploymentStatus.Unknown, 0)
        new(emplNumber, first, last, acntNumber) = Employee(emplNumber, first, last, acntNumber, EmploymentStatus.Unknown, 0)

let empl = new Employee("8204-5083", "Meghan", "Walley", "2735-038-4058")
// Getting a Customer side from an Employee object
let client = empl :> Customer;
// Getting the customer information from an Employee child object
let customer  = client :?> Employee

printfn "Kolo Bank - Customer Record from Employee Information"
printfn "=----------------------------------------------------"
printfn "First Name: %s" customer.FirstName
printfn "Last Name:  %s" customer.LastName
printfn "Account #:  %s\n" customer.AccountNumber

This would produce:

Kolo Bank - Customer Record from Employee Information
=----------------------------------------------------
First Name: Meghan
Last Name:  Walley
Account #:  2735-038-4058

Previous Copyright © 2009-2024, FunctionX Monday 14 February 2022 Next