Abstract Methods

Introduction

By default, a child class that derives from a parent class gets the behavior(s) of the member(s) from its parent class, and the child class can simply or directly use the member(s) of the parent class without any modification. In some cases, a derived class would need to implement a new behavior for a member function from the parent class. When in a derived class, creating a new behavior for a member function (a method) from the parent class is referred to as overriding. This means that the child class must override the member function from the parent class. The method in the parent class is referred to as a virtual member function. Of course, you must first create the method in the parent class. The member function from the parent class is called abstract.

Creating an Abstract Method

To create an abstract method in a (parent) class, you use the abstract keyword. The formula to create an abstract member function is:

abstract [member] method-name : [ unit | data-type] -> [unit | data-type ]

The abstract keyword is required. The member keyword is optional. The default keyword is required. After the abstract keyword or the abstract member expression, specify the name of the method followed by a colon. After the colon:

After unit or the data type(s), type ->. Then:

After creating an abstraction for the method, you must provide a default implementation for the method. It uses the default keyword. The formula to follow is:

default |self-identifier.method-name(parameter(s) = body

You start with the default keyword. This is followed by either the this keyword or a self identifier of your choice, as a letter or a word. This is followed by a period and the same name of the method specified as the abstract. Then:

The closing of the parentheses is followed by a colon. Then:

After the = sign:

Using an Abstract Method

If an abstract member function has been defined, you can use it in a program. To start, declare a variable as we have done so far. When necessary, access the abstract member(s) just as we have done so far.

type Triangle(b : double, h : double) =
    member this.Base : double = b
    member this.Height : double = h
    abstract member Area : unit -> double
    default this.Area() : double = b * h / 2.00

let tri = new Triangle(28.18, 42.06)
let discArea = tri.Area()

printfn "Triangle Characteristics"
printfn "Base:   %.02f" tri.Base
printfn "Height: %.02f" tri.Height
printfn "Area:   %.04f\n" discArea

This would produce:

Triangle Characteristics
Base:   28.18
Height: 42.06
Area:   592.6254

Press any key to close this window . . .

In a class that derives from a class that contains an abstract method, if the abstract method was implemented and its behavior is acceptable enough, you can ignore the abstract method in the derived class and you can use it in a variable declared from the child class. Here is an example:

type Triangle(b : double, h : double) =
    member this.Base : double = b;
    member this.Height : double = h;
    abstract member Area : unit -> double;
    default this.Area() : double = b * h / 2.00;

type RightTriangle(width, height) =
    inherit Triangle(width, height)
    member this.SineOppositeHeightAngle   : double = height / (sqrt(width * width + height * height))
    member this.CosineOppositeHeightAngle : double = width  / (sqrt(width * width + height * height))

let tri = new RightTriangle(28.18, 42.06);
let discArea = tri.Area();

printfn "Triangle Characteristics";
printfn "Base:     %.02f" tri.Base;
printfn "Height:   %.02f" tri.Height;
printfn "Area:     %.04f" discArea
printfn "Sine of Angle Opposite Height:   %f" tri.SineOppositeHeightAngle;
printfn "Cosine of Angle Opposite Height: %f\n" tri.CosineOppositeHeightAngle;

This would produce:

Triangle Characteristics
Base:     28.18
Height:   42.06
Area:     592.6254
Sine of Angle Opposite Height:   0.830772
Cosine of Angle Opposite Height: 0.556613

Press any key to close this window . . .

Overriding an Abstract Member Function

In some cases, a method in a child class may need a new behavior different from the method in the parent class. In this case, you must provide a new implementation for the same method in the child class. In this case, you are said to override the method from the parent class. This operation is performed using the override keyword. After overriding the member function, you can access the method in an instance (an object) of the child class. Here is an example:

type Circle(radius : double) =
    member this.Radius : double = radius;
    member this.Circumference : double = radius * 2.00 * 3.14156;
    abstract member Area : double -> double
    default this.Area(r : double) : double = r * r * 3.14156;

type Sphere(radius : double) =
    inherit Circle(radius)
    override this.Area(r : double) : double = 4.00 * r * r * 3.14156;

let disc = new Circle(28.14);
let ball = new Sphere(disc.Radius);
let discArea = disc.Area(28.14);
let ballArea = ball.Area(28.14);

printfn "Sphere Characteristics";
printfn "Radius:        %.02f" disc.Radius;
printfn "Circumference: %.03f" disc.Circumference;
printfn "Disc Area:     %.03f" discArea;
printfn "Ball Area:     %.03f\n" ballArea;

This would produce:

Sphere Characteristics
Radius:        28.14
Circumference: 176.807
Disc Area:     2487.674
Ball Area:     9950.698

Press any key to close this window . . .

Inheritance and New Constructors

So far, we were using only the primary constructor of a class to create objects. In reality, you can create as many constructors as you want, using the new keyword. Here is an example of a class with two new() constructors:

type Circle =
    val Radius : double
    new() = { Radius = 0.00 }
    new(rad) = { Radius = rad }

    abstract member Area : double -> double
    default this.Area(r : float) : double = r * r * 3.14156;

In a derived class, you can add new constructor. If you add a constructor that takes more parameters than any constructor of the parent class, to initialize a parameter using a parent constructor, you can precede that constructor with the inherit keyword. Here is an example:

type Circle =
    val Radius : double
    new() = { Radius = 0.00 }
    new(rad) = { Radius = rad }

    abstract member Area : double -> double
    default this.Area(r : float) : double = r * r * 3.14156;

type Cone =
    inherit Circle
    val Height : float
    new(rad, hgt) = { inherit Circle(rad); Height = hgt }

In the same way, you can create as many constructors as you want in the child class and make sure you initialize them appropriately.

Abstract Classes

Introduction

A class is called abstract if it contains at least one abstract method that is not defined. If all of the methods are implemented, even if they provide default implementations that have empty parentheses, such a class is not a true F# abstract class. Here is an example:

type Performance() = // Not a true F# abstract class
    abstract member Dance : unit -> unit
    default my.Dance() : unit = ()
    abstract member Sing : string -> unit;
    default my.Sing(d : string) : unit = ()
    abstract member GetPaid : int * string * double -> unit;
    default my.GetPaid(rank : int, methodOfPayment : string, amount : double) : unit = ()

type Competition() =
    inherit Performance()
    override my.Dance() : unit = printfn "=-=-=-= Dance Competition =-=-=-=";
    override my.Sing(title : string) : unit = printfn "Song Title: %s" title;
    override my.GetPaid(rank : int, methodOfPayment : string, amount : double) : unit =
        printfn "Rank:              %d" rank;
        printfn "Method of Payment: %s" methodOfPayment;
        printfn "Amount Paid:       $%.02f" amount;

let party = new Competition();
printfn "=-------------------------------=";
party.Dance();
party.Sing "We Screamed Together";
printfn "=-------------------------------=";
printfn "Competition Results and Summary";
printfn "----------------------------------";
party.GetPaid(2, "Cash", 3250.00);

This would produce:

=-------------------------------=
=-=-=-= Dance Competition =-=-=-=
Song Title: We Screamed Together
=-------------------------------=
Competition Results and Summary
----------------------------------
Rank:              2
Method of Payment: Cash
Amount Paid:       $3250.00

Press any key to close this window . . .

In order to create a true F# abstract class, the class must be marked with the [<AbstractClass>] attribute. Here is an example:

[<AbstractClass>]
type Vehicle =
    class
    end

Abstract Methods

The abstract member functions we saw earlier were simple regular methods. Normally, an abstract class can have all types of regular members, such as normal methods. Once a class has been made abstract, you can complete its abstraction by adding members that are not implemented. For example, you can add one or more non-implemented methods. A non-implemented method has only the abstract section without a default implementation. Its formula follows the same we saw earlier:

abstract [member] method-name : [ unit | data-type] -> [unit | data-type ]

You can start with either the member keyword or the abstract member expression. The rules we saw for the [ unit | data-type] section apply exactly here. Here is an example:

[<AbstractClass>]
type Triangle(b : double, h : double) =
    member this.Base : double = b;
    member this.Height : double = h;
    abstract member Area : unit -> double;
    default this.Area() : double = b * h / 2.00;
    abstract Describe : unit -> unit

To use an abstract method, you must implement it in a derived class, which is done by overriding it using the override keyword. Here is an example:

[<AbstractClass>]
type Triangle(b : double, h : double) =
    member this.Base : double = b;
    member this.Height : double = h;
    abstract member Area : unit -> double;
    default this.Area() : double = b * h / 2.00;
    abstract Describe : unit -> unit

type RightTriangle(width, height) =
    inherit Triangle(width, height)
    member this.SineOppositeHeightAngle   : double = height / (sqrt(width * width + height * height))
    member this.CosineOppositeHeightAngle : double = width  / (sqrt(width * width + height * height))
    override this.Describe() =
        printfn "A right triangle is a polygon with three sides. The \
                between tow of the sides must form a right angle."
let tri = new RightTriangle(28.18, 42.06);
let discArea = tri.Area();

printfn "Triangle Characteristics";
printf  "Description: ";
tri.Describe();
printfn "Base:     %.02f" tri.Base;
printfn "Height:   %.02f" tri.Height;
printfn "Area:     %.04f" discArea
printfn "Sine of Angle Opposite Height:   %f" tri.SineOppositeHeightAngle;
printfn "Cosine of Angle Opposite Height: %f\n" tri.CosineOppositeHeightAngle;

This would produce:

Triangle Characteristics
Description: A right triangle is a polygon with three sides. The between tow of the sides must form a right angle.
Base:     28.18
Height:   42.06
Area:     592.6254
Sine of Angle Opposite Height:   0.830772
Cosine of Angle Opposite Height: 0.556613

Press any key to close this window . . .

Abstract Properties

Abstract Read-Only Properties

A property is described as abstract if it is not defined. Abstract classes support all three categories of properties. Remember that a read-only property is one that can only provide a value that client objects can access. The formula to create an abstract read-only property is:

abstract property-name : data-type with get

Here is an example:

[<AbstractClass>]
type Vehicle =
    class
        abstract Make : string with get
    end

Before using the property, you must define it in a derived class. To do this, start with the override keyword followed by either a self-identified of your choice (this, etc) or the name of the abstract class. This is followed by a the name of the property and the = sign. Probably the easiest way to implement an abstract read-only property is to assign a constructor argument to it. Here is an example:

[<AbstractClass>]
type Vehicle(make : string) =
    abstract Make  : string with get
    
type Car(make : string) =
    inherit Vehicle(make)
    override me.Make = make

When overriding the property, you can also specify its data type after the name of the property. After overriding the property, you can access it from an object of the class. Here are examples:

[<AbstractClass>]
type Vehicle(make : string, model : string, year : int) =
    class
        abstract Make  : string with get
        abstract Model : string with get
        abstract Year  : int with get
    end
    
type Car(make : string, model : string, year : int) =
    class
        inherit Vehicle(make, model, year)
	override me.Make    : string = make
	override mine.Model : string = model
	override this.Year  : int    = year
    end

let sedan = Car("Dodge", "Charger", 2014)

printfn "Vehicle Information"
printfn "-------------------"
printfn "Make:  %s" sedan.Make
printfn "Model: %s" sedan.Model
printfn "Year:  %d\n" sedan.Year

This would produce:

Vehicle Information
-------------------
Make:  Dodge
Model: Charger
Year:  2014

Press any key to close this window . . .

Abstract Write-Only Property

A write-only property is one that can only receive values from client objects. It cannot provide its value. The formula to create an abstract write-only property is:

abstract property-name : data-type with set

Before using the property, the must override it in a derived class. Here is an example:

[<AbstractClass>]
type Vehicle(make : string, model : string, year : int) =
    class
        abstract TagNumber : string with set
        abstract Make  : string with get
        abstract Model : string with get
        abstract Year  : int with get
    end
    
type Car(tag : string, make : string, model : string, year : int) =
    class
        inherit Vehicle(make, model, year)
        let mutable tagNbr = tag
        override this.TagNumber  with set (value) = tagNbr <- value
        override me.Make    : string = make
        override mine.Model : string = model
        override this.Year  : int    = year
        new(tag : string) = Car(tag, "", "", 1960)
        new(make : string, model : string, year : int) = Car("000000", make, model, year)
    end

Abstract Read-Write Properties

A read-write property is one that can be used to store a value into an object at one time or to read a value at another time. Such a property is abstract if it is not defined in an abstract class. Th formula to create an abstract read-write property is:

abstract property-name [ : Data-Type ] with get, set

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