Home

F# Object-Oriented Programming: Class Abstraction

 

Abstract Member Functions

 

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 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 member function in the parent class is referred to as a virtual method. Of course, you must first create the member function in the parent class. The method from the parent class is referred to as abstract.

To create an abstract member function 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 member function followed by a colon. After the colon:

  • If the member function doesn't take any argument, type unit. Here is an example:
    type Performance =
        abstract member Dance : unit . . .
  • If the member function takes one argument, type its data type. Here is an example:
    type Performance =
        abstract member Sing : string .  .  .
  • If the member function takes more than one argument, type their data type names separated by *. Here is an example:
    type Performance =
        abstract member GetPaid : int * string * double  .  .  .

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

  • If the member function will not return a value, type unit. Here are examples:
    type Performance =
        abstract member Dance : unit -> unit;
        abstract member Sing : string -> unit;
        abstract member GetPaid : int * string * double -> unit;
  • If the member function will return a value, type the return type. Here is an example:
    type Triangle(b : double, h : double) =
        abstract member Area : double * double -> double

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

default this|self.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 member function specified as abstract. Then:

  • If the abstract member function was specified as not taking any argument(s), add empty parentheses to it. Here is an example:
    type Performance =
        abstract member Dance : unit -> unit
        default mine.Dance() . . .
  • If the abstract member function was specified as taking an argument, add the parentheses. Inside the parentheses, enter the name of the parameter followed by a colon and the data type. Here is an example:
    type Performance =
        abstract member Sing : string -> unit;
        default mine.Sing(d : string) . . .
  • If the abstract member function was specified as taking more than one argument, add the parentheses. Inside the parentheses, enter a name of the parameter followed by a colon and the corresponding data type. Separate the definitions of the parameters with commas. Here is an example:
    type Performance =
        abstract member GetPaid : int * string * double -> unit;
        default mine.GetPaid(rank : int, methodOfPayment : string, amount : double) . . .

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

  • If the abstraction specified that the member function will not return a value, type unit followed by =. Here is an example:
    type Performance =
        abstract member Dance : unit -> unit
        default mine.Dance() : unit =
  • If the abstraction specified that the member function will return a value, type the data type followed by =. Here is an example:
    type Triangle(b : double, h : double) =
        abstract member Area : double * double -> double
        default this.Area(b : double, h : double) : double =

After the = sign:

  • If you don't want/have to implement the method, add empty parentheses. Here are examples:
    type Performance =
        abstract member Dance : unit -> unit
        default mine.Dance() : unit = ()
        abstract member Sing : string -> unit;
        default mine.Sing(d : string) : unit = ()
        abstract member GetPaid : int * string * double -> unit;
        default mine.GetPaid(rank : int, methodOfPayment : string, amount : double) : unit = ()
  • If you want/have to implement the member function, do it. Here is an example:
    type Triangle(b : double, h : double) =
        member this.Base : double = b
        member this.Height : double = h
        abstract member Area : double * double -> double
        default this.Area(b : double, h : double) : double = b * h / 2.00

Using an Abstract Member Function

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

open System
open System.Windows.Forms

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

// Form: Geometric Triangle
let geometricTriangle = new Form()
geometricTriangle.Width  <- 265
geometricTriangle.Height <- 138
geometricTriangle.Text <- "Triangle"

// Label: Base
let lblBase = new Label()
lblBase.Left   <- 22
lblBase.Top    <- 19
lblBase.Width  <- 40
lblBase.Text   <- "Base:"
geometricTriangle.Controls.Add lblBase

// Text Box: Base
let txtBase = new TextBox()
txtBase.Left  <- 72
txtBase.Top   <- 16
txtBase.Width <- 54
txtBase.Text   <- "0.00"
geometricTriangle.Controls.Add txtBase

// Label: Height
let lblHeight = new Label()
lblHeight.Left <- 22
lblHeight.Top <- 50
lblHeight.Width <- 45
lblHeight.Text <- "Height:"
geometricTriangle.Controls.Add lblHeight

// Text Box: Height
let txtHeight = new TextBox()
txtHeight.Left  <- 72
txtHeight.Top   <- 43
txtHeight.Width <- 54
txtHeight.Text  <- "0.00"
geometricTriangle.Controls.Add txtHeight

// Button: Calculate
let btnCalculate = new Button()
btnCalculate.Left <- 133
btnCalculate.Top  <-  43
btnCalculate.Text <- "Calculate"

// Label: Area
let lblArea = new Label()
lblArea.Left  <-  22
lblArea.Top   <- 78
lblArea.Width <-  40
lblArea.Text  <- "Area:"
geometricTriangle.Controls.Add lblArea

// Text Box: Area
let txtArea = new TextBox()
txtArea.Left  <- 72
txtArea.Top   <- 75
txtArea.Width <- 84
txtArea.Text   <- "0.00"
geometricTriangle.Controls.Add txtArea

// Button: Close
let btnClose = new Button()
btnClose.Left <- 163
btnClose.Top  <- 72
btnClose.Text <- "Close"
let btnCloseClick e = geometricTriangle.Close()
btnClose.Click.Add btnCloseClick
geometricTriangle.Controls.Add btnClose

let btnCalculateClick e =
    let length = float txtBase.Text
    let height = float txtHeight.Text

    let tri = new Triangle(length, height)
    let strArea = sprintf "%f" (tri.Area())
    txtArea.Text <- strArea

btnCalculate.Click.Add btnCalculateClick
geometricTriangle.Controls.Add btnCalculate
    
do Application.Run geometricTriangle

Here is an example of executing the program:

The Data Type of a Parameter

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

open System
open System.Windows.Forms

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))

// Form: Geometric Triangle
let geometricTriangle = new Form(Width = 290, Height = 240, Text = "Geometry: Right Triangle")

// Base
geometricTriangle.Controls.Add(new Label(Left = 20, Top = 19, Width = 40, Text = "Base:"))
let txtBase = new TextBox(Left = 73, Top = 16, Text = "0.00")
geometricTriangle.Controls.Add txtBase
// Height
geometricTriangle.Controls.Add(new Label(Left = 20, Top = 45, Width = 45, Text = "Height:"))
let txtHeight = new TextBox(Left = 73, Top = 42, Text = "0.00")
geometricTriangle.Controls.Add txtHeight
// Button: Calculate
let btnCalculate = new Button(Left = 187, Top = 39, Text = "Calculate")
// Line
geometricTriangle.Controls.Add(new Label(Left = 12, Top = 65, Width = 285, Height = 13, Text = "---------------------------------------------------------------------"))
// Area
geometricTriangle.Controls.Add(new Label(Left = 20, Top = 86, Width = 40, Text = "Area:"))
let txtArea = new TextBox(Left = 73, Top = 83, Text = "0.00")
geometricTriangle.Controls.Add txtArea

geometricTriangle.Controls.Add(new Label(Left = 20, Top = 117, Width = 240, Text = "For Angle Opposite Height"))

// Sine
geometricTriangle.Controls.Add(new Label(Left = 20, Top = 146, Width = 45, Text = "Sine:"))
let txtSine = new TextBox(Left = 73, Top = 143, Text = "0.00")
geometricTriangle.Controls.Add txtSine
// Cosine
geometricTriangle.Controls.Add(new Label(Left = 20, Top = 172, Width = 45, Text = "Cosine:"))
let txtCosine = new TextBox(Left = 73, Top = 169, Text = "0.00")
geometricTriangle.Controls.Add txtCosine

// Button: Close
let btnClose = new Button(Left = 187, Top = 167, Text = "Close")
let btnCloseClick e = geometricTriangle.Close()
btnClose.Click.Add btnCloseClick
geometricTriangle.Controls.Add btnClose

let btnCalculateClick e =
    let length = float txtBase.Text
    let height = float txtHeight.Text

    let tri = new RightTriangle(length, height)
    let strArea = sprintf "%0.04f" (tri.Area())
    let strSine = sprintf "%f" tri.SineOppositeHeightAngle
    let strCosine = sprintf "%f" tri.CosineOppositeHeightAngle

    txtArea.Text   <- strArea
    txtSine.Text   <- strSine
    txtCosine.Text <- strCosine

btnCalculate.Click.Add btnCalculateClick
geometricTriangle.Controls.Add btnCalculate

do Application.Run geometricTriangle

Here is an example of executing the program:

Using an Abstract Member Function

     
 

Overriding an Abstract Member Function

In some cases, a method in the child class may need a new behavior different from the member function 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 member function in an instance (an object) of the child class. Here is an example:

Geometry.fs:

module Geometry

type Circle(rad : double) =
    let r = ref rad
    member this.Radius : double = rad
    member this.Circumference : double = rad * 2.00 * 3.14156
    abstract member Area : unit -> double
    default this.Area()  : double = rad * rad * 3.14156

type Sphere(rad : double) =
    inherit Circle(rad)
    let r = ref rad
    override this.Area() : double = 4.00 * rad * rad * 3.14156

Program.fs:

open System
open System.Windows.Forms

let exercise = new Form(Width = 240, Height = 240, Text = "Geometry: Sphere")

// Label: Radius
exercise.Controls.Add(new Label(Left = 23, Top = 21, Width = 75, Text = "Radius:"))

// Text Box: Radius
let txtRadius = new TextBox(Left = 110, Top = 18, Text = "0.00")
exercise.Controls.Add txtRadius

// Button: Calculate
let btnCalculate = new Button(Left = 110, Top = 49, Width = 100, Text = "Calculate")
exercise.Controls.Add btnCalculate

// Label: Circumference
exercise.Controls.Add(new Label(Left = 23, Top = 86, Width = 85, Text = "Circumference:"))

// Text Box: Circumference
let txtCircumference = new TextBox(Left = 110, Top = 83, Text = "0.00")
exercise.Controls.Add txtCircumference

// Label: Disc Area
exercise.Controls.Add(new Label(Left = 23, Top = 112, Width = 85, Text = "Disc Area:"))

// Text Box: Disc Area
let txtDiscArea = new TextBox(Left = 110, Top = 109, Text = "0.00")
exercise.Controls.Add txtDiscArea

// Label: Ball Area
exercise.Controls.Add(new Label(Left = 23, Top = 138, Width = 85, Text = "Ball Area:"))

// Text Box: Ball Area
let txtBallArea = new TextBox(Left = 110, Top = 135, Text = "0.00")
exercise.Controls.Add txtBallArea

let btnCalculateClick e =
    let radius = float txtRadius.Text

    let disc = new Geometry.Circle(radius)
    let ball = new Geometry.Sphere(disc.Radius)
    let discArea = disc.Area()
    let ballArea = ball.Area()

    let strCircumference = sprintf "%f" disc.Circumference
    let strDiscArea      = sprintf "%f" discArea
    let strArea          = sprintf "%f" ballArea 

    txtCircumference.Text <- strCircumference
    txtDiscArea.Text <- strDiscArea
    txtBallArea.Text <- strArea
btnCalculate.Click.Add btnCalculateClick

let btnClose = new Button(Left = 110, Top = 168, Width = 100, Text = "Close")
let btnCloseClick e = exercise.Close()
btnClose.Click.Add btnCloseClick
exercise.Controls.Add btnClose

do Application.Run exercise

Here is an example of running the program:

Overriding an Abstract Member Function

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 constructors. 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 member function 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 abstract class.

In order to create an 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 member functions. 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 member functions. A non-implemented member function 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 ]

Remember that the member keyword is optional. This means that 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 -> string

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

open System
open System.Windows.Forms

[<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 -> string

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() = "A right triangle is a polygon with three sides. The angle \
                                between two of the sides must form a right angle."

// Form: Geometric Triangle
let geometricTriangle = new Form(Width = 290, Height = 290, Text = "Geometry: Right Triangle")

// Base
geometricTriangle.Controls.Add(new Label(Left = 20, Top = 19, Width = 40, Text = "Base:"))
let txtBase = new TextBox(Left = 73, Top = 16, Text = "0.00")
geometricTriangle.Controls.Add txtBase
// Height
geometricTriangle.Controls.Add(new Label(Left = 20, Top = 45, Width = 45, Text = "Height:"))
let txtHeight = new TextBox(Left = 73, Top = 42, Text = "0.00")
geometricTriangle.Controls.Add txtHeight
// Button: Calculate
let btnCalculate = new Button(Left = 187, Top = 39, Text = "Calculate")
// Line
geometricTriangle.Controls.Add(new Label(Left = 12, Top = 65, Width = 285, Height = 13, Text = "---------------------------------------------------------------------"))
// Area
geometricTriangle.Controls.Add(new Label(Left = 20, Top = 86, Width = 40, Text = "Area:"))
let txtArea = new TextBox(Left = 73, Top = 83, Text = "0.00")
geometricTriangle.Controls.Add txtArea

geometricTriangle.Controls.Add(new Label(Left = 20, Top = 117, Width = 240, Text = "For Angle Opposite Height"))

// Sine
geometricTriangle.Controls.Add(new Label(Left = 20, Top = 146, Width = 45, Text = "Sine:"))
let txtSine = new TextBox(Left = 73, Top = 143, Text = "0.00")
geometricTriangle.Controls.Add txtSine
// Cosine
geometricTriangle.Controls.Add(new Label(Left = 20, Top = 172, Width = 45, Text = "Cosine:"))
let txtCosine = new TextBox(Left = 73, Top = 169, Text = "0.00")
geometricTriangle.Controls.Add txtCosine

let lblDescription = new Label(Left = 20, Top = 200, Width = 165, Height = 60, Text = "Description")
geometricTriangle.Controls.Add lblDescription

// Button: Close
let btnClose = new Button(Left = 187, Top = 200, Height = 40, Text = "Close")
let btnCloseClick e = geometricTriangle.Close()
btnClose.Click.Add btnCloseClick
geometricTriangle.Controls.Add btnClose

let btnCalculateClick e =
    let length = float txtBase.Text
    let height = float txtHeight.Text

    let tri = new RightTriangle(length, height)
    let strArea = sprintf "%0.04f" (tri.Area())
    let strSine = sprintf "%f" tri.SineOppositeHeightAngle
    let strCosine = sprintf "%f" tri.CosineOppositeHeightAngle

    txtArea.Text   <- strArea
    txtSine.Text   <- strSine
    txtCosine.Text <- strCosine
    lblDescription.Text <- tri.Describe()

btnCalculate.Click.Add btnCalculateClick
geometricTriangle.Controls.Add btnCalculate

do Application.Run geometricTriangle

Here is an example of running the program:

Abstract Methods Abstract Methods

Abstract Properties

 

Abstract Read-Only Properties

A property is called abstract if it is not defined. Abstract classes support all three categories of properties. Remmber 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 the this keyword or a self-identified of your choice, including 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)

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, you 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
   
   
 

Home Copyright © 2009-2015, FunctionX Home