Introduction to Class Polymorphism and Abstraction
Introduction to Class Polymorphism and Abstraction
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:
type Performance =
abstract member Dance : unit . . .
type Performance =
abstract member Sing : string . . .
type Performance =
abstract member GetPaid : int * string * double . . .
After unit or the data type(s), type ->. Then:
type Performance = abstract member Dance : unit -> unit; abstract member Sing : string -> unit; abstract member GetPaid : int * string * double -> unit;
type Triangle(b : double, h : double) =
abstract member Area : double * double -> double
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:
type Performance =
abstract member Dance : unit -> unit
default mine.Dance() . . .
type Performance =
abstract member Sing : string -> unit;
default mine.Sing(d : string) . . .
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:
type Performance =
abstract member Dance : unit -> unit
default mine.Dance() : unit =
type Triangle(b : double, h : double) =
abstract member Area : double * double -> double
default this.Area(b : double, h : double) : double =
After the = sign:
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 = ()
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 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 |
|