Lesson Home

Polymorphism and Abstraction

 

Polymorphism

 

Introduction

Imagine you create a CCircle class as follows:

Header File: circle.h
#pragma once

namespace FlatShapes
{
    public __gc class CCircle
    {
    protected:
        double radius;

    public:
        CCircle(void);
        CCircle(double r);
        double Diameter();
        double Circumference();
        double Area();

    public:
        __property void   set_Radius(double r);
        __property double get_Radius();
    };
}

The class can be implemented as follows:

Source File: circle.cpp
#include "StdAfx.h"
#include ".\circle.h"
#using <mscorlib.dll>

namespace FlatShapes
{
CCircle::CCircle(void)
	: radius(0.00)
{
}

CCircle::CCircle(double r)
	: radius(r)
{
}

void CCircle::set_Radius(double r)
{
	radius = (r < 0) ? 0 : r;
}

double CCircle::get_Radius()
{
	return (radius < 0) ? 0 : radius;
}

double CCircle::Diameter()
{
	return Radius * 2;
}

double CCircle::Circumference()
{
	return 3.14159 * radius * 2;
}

double CCircle::Area()
{
	return radius * radius * 3.14159;
}
}

This class can be tested as follows:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"
#using <mscorlib.dll>

#include ".\circle.h"

using namespace System;
using namespace FlatShapes;

void ShowCharacteristics(CCircle *round);

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	CCircle *circle = new CCircle(35.75);

	ShowCharacteristics(circle);

    	Console::WriteLine(S"");
	return 0;
}

void ShowCharacteristics(CCircle *round)
{
    	Console::WriteLine(S"Circle Characteristics");
	Console::WriteLine(S"Radius:        {0}", __box(round->Radius));
	Console::WriteLine(S"Diameter:      {0}", __box(round->Diameter()));
	Console::WriteLine(S"Circumference: {0}", __box(round->Circumference()));
	Console::WriteLine(S"Area:          {0}", __box(round->Area()));
}

This would produce:

Circle Characteristics
Radius:        35.75
Diameter:      71.5
Circumference: 224.623685
Area:          4015.148369375

Press any key to continue

In an inheritance scenario, a base class is configured to provide its children with the basic foundation the derived classes need. Although a derived class can implement a new behavior not available on the parent class, sometimes the derived class will need a customized implementation of a behavior that has already been configured with its parent. That is what happens for example when inheriting a sphere from a circle class. Both have a characteristic called Area() but the area is calculated differently on each shape. For example, you can derive a CSphere class as follows:

Header File: Sphere.h
#pragma once
#include ".\circle.h"

namespace Volumes
{
    class CSphere : public FlatShapes::CCircle
    {
    public:
        CSphere(void);
        CSphere(double x);

        double Area();
        double Volume();
    };
}

This object can be implemented as follows:

Source File: Sphere.cpp
#include "StdAfx.h"
#include ".\sphere.h"
#using <mscorlib.dll>

namespace Volumes
{
CSphere::CSphere(void)
{
}

CSphere::CSphere(double n)
    : FlatShapes::CCircle(n)
{
}

double CSphere::Area()
{
    return 4 * 3.14 * radius * radius;
}

double CSphere::Volume()
{
    return 4 * 3.14 * radius * radius * radius / 3;
}
}

In the same way, you can derive as many classes as necessary that share the same base class. For example, you can create a CCylinder class based on the above CCircle as follows:

Header File: Cylinder.h
#pragma once

namespace Volumes
{
class CCylinder : public FlatShapes::TCircle
{
public:
    CCylinder(void);
    CCylinder(double r, double h);

    double BaseArea() const;
    double LateralArea() const;
    double Area() const;
    double Volume() const;

    __property void   set_Height(double h);
    __property double get_Height();

protected:
    double height;
};
}

#endif

This object can be implemented as follows:

Source File: Cylinder.h
#include "StdAfx.h"
#include ".\cylinder.h"
#using <mscorlib.dll>

namespace Volumes
{
CCylinder::CCylinder(void)
{
}

CCylinder::CCylinder(double x, double y)
    : FlatShapes::CCircle(x), height(y)
{
}

void CCylinder::set_Height(double h)
{
	height = (h < 0) ? 0 : h;
}

double CCylinder::get_Height()
{
	return height;
}

double CCylinder::BaseArea()
{
    return FlatShapes::CCircle::Area();
}

double CCylinder::LateralArea()
{
    double circ = radius * 2 * 3.14;
    return circ * height;
}

double CCylinder::Area()
{
    double base = FlatShapes::CCircle::Area();
    double lateral = LateralArea();
    return (2 * base) + lateral;
}

double CCylinder::Volume()
{
    double base = FlatShapes::CCircle::Area();
    return base * height;
}
}

The above three classes could be tested with the following:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"
#using <mscorlib.dll>

#include ".\circle.h"
#include ".\sphere.h"
#include ".\cylinder.h"

using namespace System;
using namespace Volumes;
using namespace FlatShapes;

void ShowCharacteristics(CCircle *round);
void ShowCharacteristics(CSphere *Sph);
void ShowCharacteristics(CCylinder *C);

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    CCircle   *circle   = new CCircle(35.75);
    CSphere   *sphere   = new CSphere(22.64);
    CCylinder *cylinder = new CCylinder(18.94, 14.58);

    ShowCharacteristics(circle);
    Console::WriteLine(S"");
    ShowCharacteristics(sphere);
    Console::WriteLine(S"");
    ShowCharacteristics(cylinder);

    Console::WriteLine(S"");
    return 0;
}

void ShowCharacteristics(CCircle *round)
{
    Console::WriteLine(S"Circle Characteristics");
    Console::WriteLine(S"Radius:        {0}", __box(round->Radius));
    Console::WriteLine(S"Diameter:      {0}", __box(round->Diameter()));
    Console::WriteLine(S"Circumference: {0}", __box(round->Circumference()));
    Console::WriteLine(S"Area:          {0}", __box(round->Area()));
}

void ShowCharacteristics(CSphere *Sph)
{
    Console::WriteLine(S"Sphere Characteristics");
    Console::WriteLine(S"Radius:        {0}", __box(Sph->Radius));
    Console::WriteLine(S"Diameter:      {0}", __box(Sph->Diameter()));
    Console::WriteLine(S"Circumference: {0}", __box(Sph->Circumference()));
    Console::WriteLine(S"Area:          {0}", __box(Sph->Area()));
    Console::WriteLine(S"Volume:        {0}", __box(Sph->Volume()));
}

void ShowCharacteristics(CCylinder *cyl)
{
    Console::WriteLine(S"Cylinder Characteristics");
    Console::WriteLine(S"Radius:       {0}", __box(cyl->Radius));
    Console::WriteLine(S"Height:       {0}", __box(cyl->Height));
    Console::WriteLine(S"Base Area:    {0}", __box(cyl->BaseArea()));
    Console::WriteLine(S"Lateral Area: {0}", __box(cyl->LateralArea()));
    Console::WriteLine(S"Area:         {0}", __box(cyl->Area()));
    Console::WriteLine(S"Volume:       {0}", __box(cyl->Volume()));
}

This would produce:

Circle Characteristics
Radius:        35.75
Diameter:      71.5
Circumference: 224.623685
Area:          4015.148369375

Sphere Characteristics
Radius:        22.64
Diameter:      45.28
Circumference: 142.2511952
Area:          6437.874176
Volume:        48584.4904482133

Cylinder Characteristics
Radius:       18.94
Height:       14.58
Base Area:    1126.962474524
Lateral Area: 1734.191856
Area:         3988.116805048
Volume:       16431.1128785599

Press any key to continue
 

Virtual Methods

When studying inheritance, we learned that there is a special bond between an inherited class and its parent. Not only does the child object have access to the public members of a class but also the child, based on this relationship, has direct access to the members of the protected section(s) of the parent. The program above shows us that, in order to access a class’ members, we can just declare an instance of the class and use either the member access operator “->”.
 
If there is such a good relationship between a class and its children, is it possible to access a member of a child object using an instance of the parent? We will declare only an instance of the parent class, CCircle, and try to access its child using such a variable:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"
#using <mscorlib.dll>

#include ".\circle.h"
#include ".\sphere.h"
#include ".\cylinder.h"

using namespace System;
using namespace Volumes;
using namespace FlatShapes;

void ShowCharacteristics(CCircle *round);

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    CSphere   *sphere   = new CSphere(22.64);
    CCylinder *cylinder = new CCylinder(12.94, 14.58);
	
    // Declare a CCircle variable and initialize it
    // with a CSphere variable
    CCircle   *circle   = sphere;
    ShowCharacteristics(circle);
    Console::WriteLine(S"");

    // Initialize a CCircle with a CCylinder variable
    circle = cylinder;
    ShowCharacteristics(circle);

    Console::WriteLine(S"");
    return 0;
}

void ShowCharacteristics(CCircle *round)
{
    Console::WriteLine(S"Circle Characteristics");
    Console::WriteLine(S"Radius:        {0}", __box(round->Radius));
    Console::WriteLine(S"Diameter:      {0}", __box(round->Diameter()));
    Console::WriteLine(S"Circumference: {0}", __box(round->Circumference()));
    Console::WriteLine(S"Area:          {0}", __box(round->Area()));
}

This would produce:

Circle Characteristics
Radius:        22.64
Diameter:      45.28
Circumference: 142.2511952
Area:          1610.283529664

Circle Characteristics
Radius:        12.94
Diameter:      25.88
Circumference: 81.3043492
Area:          526.039139324

Press any key to continue

Notice that, although the variable circle has been initialized with variables sphere and cylinder, only the characteristics of the circle are displayed.

A virtual method is a member function that makes sure that, in an inheritance scenario, the right method is accessed regardless of the expression that calls the object. The last three classes we have used have a function called Area() each. Let's consider the member function Area() that is present in all three classes. If we need an instance of the parent class to call the right member function, in this case Area, we must precede its declaration in the parent class as the virtual keyword as follows:

Header File: Circle.h
#pragma once

namespace FlatShapes
{
    public __gc class CCircle
    {
    protected:
        double radius;

    public:
        CCircle(void);
        CCircle(double r);
        double Diameter();
        double Circumference();
        virtual double Area();

    public:
        __property void   set_Radius(double r);
        __property double get_Radius();
	};
}

With just this change, the program would produce the following:

Without virtual Area() With virtual Area()
Circle Characteristics
Radius:        22.64
Diameter:      45.28
Circumference: 142.2511952
Area:          1610.283529664

Circle Characteristics
Radius:        12.94
Diameter:      25.88
Circumference: 81.3043492
Area:          526.039139324

Press any key to continue
Circle Characteristics
Radius:        22.64
Diameter:      45.28
Circumference: 142.2511952
Area:          6437.874176

Circle Characteristics
Radius:        12.94
Diameter:      25.88
Circumference: 81.3043492
Area:          2236.895734648

Press any key to continue

Notice that, this time, even though an instance of the CCircle class made the call, the characteristics of the assigned variables display. Any other member function that is implement on the parent as well as the inherited class can be made virtual in the base class also.

Once again, the right functions, as far as their having been assigned to the base class instance goes, are called. The ability for the right method to be called when a base and its children share the same method is called polymorphism. Even though you can use polymorphism to help the compiler identify which function is being called, if you want to access the method of the base class, you can qualify it with the :: operator.

As you can see, a method with the same name can be declared in both a base and a derived classes. This allows a derived class to define its custom definition of the function member. Because the derived class “re-writes” the same function, the derived class is said to override the method. A member function can be overridden only if it is virtual. A function can be virtual only if carries the same name, the same return type, and the same type(s) of argument(s) if any.

 

This is __super

In inheritance, if you are inside of a method of a derived class, you may be trying to use a method that is defined in both the base and the derived class. To indicate that you want to call the method of the base class, you can precede its call with the __super keyword. An example would be

__super::CallingMethodFromBase();

Abstraction

 

Abstract Classes

An abstract class is one whose role is only meant to lay a foundation for other classes that would need a common behavior or similar characteristics. Therefore, an abstract class is used only as a base class for inheritance. In C++, to make class become abstract, at least one of its member functions must be declared as a “pure” virtual method. Only a virtual method can be made “pure”. The syntax of declaring a pure virtual method is:

virtual ReturnType MethodName() = 0;

The virtual keyword is required to make sure that a (any) child of this class can implement this method as it judges it. The ReturnType is the data type that the function will return. The MethodName is an appropriate name for the function. The = 0 is required. It lets the compiler know that this function is a pure virtual function. Here is an example of a C++ abstract class.

struct TTent
{
    virtual int WhatIsTheCapacity() = 0;
    virtual double TentArea() = 0;
    virtual double TentVolume() = 0;
};

In Managed C++, to declare a class as abstract, type the __abstract keyword to the left of its name when creating it. Here is an example:

Header File: quadrilateral.h

#pragma once

__abstract __gc class CQuadrilateral
{
};

If you are creating an assembly class and want to specify the class' access level, you can type the __abstract keyword before or after the access level keyword. The following two are equivalent:

Header File: quadrilateral.h

#pragma once

public __abstract __gc class CQuadrilateral
{
};
#pragma once

__abstract public __gc class CQuadrilateral
{
};

You don't have to define any method for the abstract class but you can. As with C++, you can create either only regular methods, only pure virtual methods, or a mix. Here is an example with two pure virtual methods for the above class:

Header File: quadrilateral.h

#pragma once

__abstract public __gc class CQuadrilateral
{
public:
	virtual double Perimeter(void) = 0;
	virtual double Area(void) = 0;
};
 

After creating an abstract class, you can derive a class from it. The first rule you must observe is that, a derived class must implement any pure virtual method that the parent class has. In other words, if you derive a class from the above abstract class, you must define your versions of the above Perimeter() and the above Area() methods; otherwise, the derived class also would be considered abstract. As an alternative, you can declare a method simply as virtual, implement it in the abstract class, and then use it in the derived classes.

Here is an example of a class called CSquare that is based on the above CQuadrilateral class:

Header File: Square.h

#pragma once
#include "quadrilateral.h"

public __gc class CSquare :	public CQuadrilateral
{
protected:
	double side;
public:
	CSquare(void);
	virtual double Perimeter(void);
	virtual double Area(void);
public:
	__property void   set_Side(double s);
	__property double get_Side(void);
};

Source Code: Square.cpp

#include "StdAfx.h"
#include ".\square.h"
#using <mscorlib.dll>

CSquare::CSquare(void)
	: side(0)
{
}

void CSquare::set_Side(double s)
{
	side = s;
}

double CSquare::get_Side(void)
{
	return side;
}

double CSquare::Perimeter(void)
{
	return side * 4;
}

double CSquare::Area(void)
{
	return side * side;
}

If you try declaring a variable from the above CQuadrilateral class, you would receive an error:
// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;

public __abstract __gc class CQuadrilateral
{
};

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	CQuadrilateral *Quad = __gc new CQuadrilateral;

    	Console::WriteLine(S"");
	return 0;
}

The error produced is Error C3622: 'CQuadrilateral': a class declared as '__abstract' cannot be instantiated.

On the other hand, since the above CSquare class is not abstract, you can use it in your program as you see fit. Here is an example:
// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"
#include ".\square.h"

#using <mscorlib.dll>

using namespace System;

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	CSquare *Sqr = __gc new CSquare;

	Sqr->Side = 25.65;

	Console::WriteLine(S"Square Characteristics");
	Console::WriteLine(S"Side:      {0}", Sqr->Side.ToString());
	Console::WriteLine(S"Perimeter: {0}", Sqr->Perimeter().ToString());
	Console::WriteLine(S"Area:      {0}", Sqr->Area().ToString());

         Console::WriteLine(S"");
	return 0;
}

This would produce:

Square Characteristics
Side:      25.65
Perimeter: 102.6
Area:      657.9225

Press any key to continue
 

Interfaces

In order to use inheritance, one class considered the parent must have been created and made available to other classes. As done with abstract classes, sometimes you may want to create a class that would be used only to provide a foundation for other classes. Such a class would never be instantiated. Such a class is called an interface.

To create an interface class, you can use the __interface keyword. When creating an interface class, type the __interface keyword on the left of the name of the class. By (good) habit, it is usual to start the name of an interface class with I to indicate the nature of the class. Here is an example that creates an interface class:

Header File: quadrilateral.h
__interface IQuadrilateral
{
	double Perimeter();
	double Area();
};

As mentioned already, the __interface keyword indicates that the class is abstract. Therefore, you don't have to indicate that its methods are virtual. Otherwise, if you want, you can still type the virtual keyword to the left of the method:

__interface IQuadrilateral
{
	double Perimeter();
	virtual double Area();
};

Also, since an interface class is only made of pure virtual methods, you don't have to indicate this by typing = 0; to their left, but you can if you want to:

__interface IQuadrilateral
{
	double Perimeter();
	virtual double Area() = 0;
};

In case you wonder why we declared only methods in the class, an interface class can have only methods, no member variables.  While the __interface concept is primarily a re-enforcer of the notice of abstract classes, it adds some rules to set the difference with a regular C++ abstract class:

  • An interface class can inherit from only another interface class, not any regular class
  • An interface class can have only methods, no member variables; the methods are already pure virtual
  • The methods of an interface class can only be public; private or protected methods are not allowed
  • Constructors and destructors are not allowed in an interface class; when you derive a class from it, you can create the necessary constructors and destructor
  • Static methods are not allowed in an interface class

The CQuadrilateral class we used earlier can be made an interface with just two changes while keeping everything else:

Header File: quadrilateral.h

#pragma once

__abstract public __gc class IQuadrilateral
{
public:
	virtual double Perimeter(void) = 0;
	virtual double Area(void) = 0;
};

Header File: Square.h

#pragma once
#include "quadrilateral.h"

public __gc class CSquare :	public IQuadrilateral
{
protected:
	double side;
public:
	CSquare(void);
	virtual double Perimeter(void);
	virtual double Area(void);
public:
	__property void   set_Side(double s);
	__property double get_Side(void);
};
 

Sealed Classes

The most common reasons of creating an abstract class is to lay a foundation that derived classes can benefit from. Even if you don't make a class abstract, as done in the examples of the previous lesson, a user can still derive another class from it and use it as necessary. If you want to prevent classes from being derived from your class, you can seal it. A sealed class is one that other classes cannot inherit from. In other words, the class is used "as is".

When creating a class, to mark it as sealed, type the __sealed keyword on its left when creating the class. Here is an example:

#pragma once

public __sealed __gc class CTriangle
{
};

When creating a sealed class, you should provide functionality as complete as possible since other classes cannot derive from that class. Here is an example of a sealed class created and tested:
Header File: triangle.h
#pragma once

public __sealed __gc class CTriangle
{
public:
	CTriangle(void);
	double Area(void);

private:
	double base;
	double height;
public:
	__property void   set_Base(double b);
	__property double get_Base(void);
	__property void   set_Height(double h);
	__property double get_Height(void);
};
Source File: triangle.cpp
#include "StdAfx.h"
#include ".\triangle.h"
#using <mscorlib.dll>

CTriangle::CTriangle(void)
	: base(0), height(0)
{
}


void CTriangle::set_Base(double b)
{
	if( b < 0 )
		base = 0;
	else
		base = b;
}

double CTriangle::get_Base(void)
{
	return base;
}

void CTriangle::set_Height(double h)
{
	if( h < 0)
		height = 0;
	else
		height = h;
}

double CTriangle::get_Height(void)
{
	return height;
}

double CTriangle::Area(void)
{
	return base * height / 2;
}	

The class could be tested as follows:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"
#include ".\triangle.h"

#using <mscorlib.dll>

using namespace System;

CTriangle *CreateTriangle();
void DescribeTriangle(CTriangle* T);

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	CTriangle __gc *general = __gc new CTriangle;

	general = CreateTriangle();
	DescribeTriangle(general);

    	Console::WriteLine(S"");
	return 0;
}

CTriangle *CreateTriangle()
{
	CTriangle __gc *tri = __gc new CTriangle;

	tri->Base   = 32.65;
	tri->Height = 20.48;

	return tri;
}

void DescribeTriangle(CTriangle* triangle)
{
	Console::WriteLine(S"Triangle Characteristics");
	Console::WriteLine(S"Base:   {0}", triangle->Base.ToString());
	Console::WriteLine(S"Height: {0}", triangle->Height.ToString());
	Console::WriteLine(S"Area:   {0}", triangle->Area().ToString());
}

This would produce:

Triangle Characteristics
Base:   32.65
Height: 20.48
Area:   334.336

Press any key to continue

 

 


Previous Copyright © 2004 FunctionX, Inc. Next