Home

Generics and Inheritance

 

Introduction

Consider the following geometric figures:

Square Rectangle Trapezoid Parallelogram
Square Rectangle Trapezoid Parallelogram

Notice that these are geometric figures with each having four sides. From what we learned in Lesson 21, we can create a base class to prepare it for inheritance. If the class is very general, we can make it a generic one. We can set a data type as an unknown type, anticipating that the dimensions of the figure can be considered as integer or double-precision types. Here is an example:

Header File: Quadrilateral.h
#pragma once

using namespace System;

generic <typename T>
public ref class CQuadrilateral
{
protected:
    T _base;
    T _height;
    String ^ _name;

public:
    virtual property T Base
    {
        T get() { return _base; }
        void set(T b) { _base = b; }
    }

    virtual property T Height
    {
        T get() { return _height; }
        void set(T h) {	_height = h; }
    }

    virtual property String ^ Name
    {
	String ^ get() { return _name; }
	void set(String ^ value) { _name = value; }
    }

public:
    CQuadrilateral();
    CQuadrilateral(String ^ name);
    CQuadrilateral(T base, T height);
    CQuadrilateral(String ^ name, T base, T height);
    virtual String ^ Describe();
    virtual void ShowCharacteristics();
};
Source File: Quadrilateral.cpp
#include "Quadrilateral.h"

generic <typename T>
CQuadrilateral<T>::CQuadrilateral()
{
    _name = L"Quadrilateral";
}

generic <typename T>
CQuadrilateral<T>::CQuadrilateral(String ^ name)
{
    _name = L"Quadrilateral";
}

generic <typename T>
CQuadrilateral<T>::CQuadrilateral(T base, T height)
    : _name(L"Quadrilateral"), _base(base), _height(height)
{
}

generic <typename T>
CQuadrilateral<T>::CQuadrilateral(String ^ name, T base, T height)
    : _name(name), _base(base), _height(height)
{
}

generic <typename T>
String ^ CQuadrilateral<T>::Describe()
{
    return L"A quadrilateral is a geometric figure with four sides";
}

generic <typename T>
void CQuadrilateral<T>::ShowCharacteristics()
{
    Console::WriteLine(L"Geometric Figure: {0}", this->Name);
    Console::WriteLine(L"Description:      {0}", this->Describe());
    Console::WriteLine(L"Base:             {0}", this->Base);
    Console::WriteLine(L"Height:           {0}", this->Height);
}
Source File: Exercise.cpp
#include "Quadrilateral.h"

using namespace System;

int main()
{
	// Trapezoid with equal sides
    CQuadrilateral<double> ^ Kite =
		gcnew CQuadrilateral<double>(L"Beach Kite", 18.64, 18.64);
    Kite->ShowCharacteristics();
    Console::WriteLine();

    // Rectangle, in meters
    CQuadrilateral<Byte> ^ BasketballStadium = gcnew CQuadrilateral<Byte>;
    BasketballStadium->Name = L"Basketball Stadium";
    BasketballStadium->Base = 15;
    BasketballStadium->Height = 28;
    BasketballStadium->ShowCharacteristics();
    Console::WriteLine();

    return 0;
}

This would produce:

Geometric Figure: Beach Kite
Description:      A quadrilateral is a geometric figure with four sides
Base:             18.64
Height:           18.64

Geometric Figure: Basketball Stadium
Description:      A quadrilateral is a geometric figure with four sides
Base:             15
Height:           28

Press any key to continue . . .

If you have a generic class that can serve as a foundation for another class, you can derive one class from the generic one. The basic formula to use is:

Options generic <typename TypeName>
AccessLevel ref/value class/struct NewClassName : public BaseClassName<TypeName>
{
};

You can start with some options; if you don't have any, you can ignore this factor. The generic keyword is required. Inside of <>, type either typename or class followed by a name for the parameter type. The AccessLevel is optional. This means that you can ignore it or set it to private or public. The optional AccessLevel is followed by either the ref or the value keyword, followed by the class or the struct keyword, and followed by the name of the new class. After the name of the new class, type the : operator required for inheritance, followed by the public keyword. Enter the name of the parent class followed by <> and, inside of this operator, enter the parameter type.

Here is an example of a generic class named CSquare that derives from another generic class named CQuadrilateral:

generic <typename T>
public ref class CSquare : public CQuadrilateral<T>
{
};

In the body of the new class, you can use the parameter type as you see fit. For example, you can declare some member variables of that type. You can create methods that return the parameter type or you can pass arguments of the parameter type. Here are examples of methods:

Header File: Square.h
generic <typename T>
public ref class CSquare : public CQuadrilateral<T>
{
public:
    CSquare();
    CSquare(String ^ name);
    CSquare(T side);
    CSquare(String ^ name, T side);
    virtual String ^ Describe() new;
    virtual void ShowCharacteristics() override;
};

When implementing the methods of the new class, use the member variables of the parameter and the argument(s) based on the parameter type as you see fit. Here are examples:

Source File: Square.cpp
#include "Square.h"

generic <typename T>
CSquare<T>::CSquare()
{
    CQuadrilateral::_name = L"Square";
}

generic <typename T>
CSquare<T>::CSquare(String ^ name)
{
    CQuadrilateral::_name = L"Square";
}

generic <typename T>
CSquare<T>::CSquare(T side)
{
    CQuadrilateral::_name   = L"Square";
    CQuadrilateral::_base   = side;
    CQuadrilateral::_height = side;
}

generic <typename T>
CSquare<T>::CSquare(String ^ name, T side)
{
    CQuadrilateral::_name   = name;
    CQuadrilateral::_base   = side;
    CQuadrilateral::_height = side;
}

generic <typename T>
String ^ CSquare<T>::Describe()
{
    return L"A square is a quadrilateral with four equal sides";
}

generic <typename T>
void CSquare<T>::ShowCharacteristics()
{
    Console::WriteLine(L"Geometric Figure: {0}", this->Name);
    Console::WriteLine(L"Description:      {0}", CQuadrilateral::Describe());
    Console::WriteLine(L"                  {0}", this->Describe());
    Console::WriteLine(L"Side:             {0}", this->Base);
}

You can then declare a variable of the class and use it as we done so far for other generic classes. Here is an example:

Source File: Exercise.cpp
#include "Quadrilateral.h"
#include "Square.h"

using namespace System;

int main()
{
    // Rectangle, in meters
    CSquare<Byte> ^ plate = gcnew CSquare<Byte>;
    plate->Name = L"Plate";
    plate->Base = 15;
    plate->Height = 28;
    plate->ShowCharacteristics();
    Console::WriteLine();

    return 0;
}

This would produce:

Geometric Figure: Plate
Description:      A quadrilateral is a geometric figure with four sides
                  A square is a quadrilateral with four equal sides
Side:             15

Press any key to continue . . .

Introduction to Generics and Interfaces

In the same way, you can create a generic interface that would serve as the base class of other generic classes. To proceed, when creating the interface, precede it with a generic<> declaration. Here is an example:

generic <typename T>
public interface class IGeometry
{
    property String ^ Name;
    void Display();
};

Since this is a generic interface, like an interface class, when deriving a class from it, follow the formula we reviewed for inheriting from a generic class. Here is an example:

generic <typename T>
public interface class IGeometry
{
    property String ^ Name;
    void Display();
};

generic <typename T>
public ref class CRound : public IGeometry<T>
{
};

When implementing the derived class, you must observe all rules that apply to interface derivation. Here is an example:

using namespace System;

generic <typename T>
public interface class IGeometry
{
    property String ^ Name;
    void Display();
};

generic <typename T>
public ref class CRound : public IGeometry<T>
{
private:
	String ^ _name;

public:
    CRound();
    CRound(String ^ name);

    property String ^ Name
    {
	virtual String ^ get() { return _name; }
	virtual void set(String ^ value) { _name = value; }
    }
	
    virtual void Display();
};

generic <typename T>
CRound<T>::CRound()
{
	_name = L"Unknown";
}

generic <typename T>
CRound<T>::CRound(String ^ name)
{
    _name = name;
}

generic <typename T>
void CRound<T>::Display()
{
    Console::WriteLine(L"Name: {0}", Name);
}

int main()
{
    CRound<double> ^ rnd = gcnew CRound<double>;

    rnd->Name = L"General Round Shape";
    rnd->Display();

    Console::WriteLine();
    return 0;
}

This would produce:

Name: General Round Shape

Press any key to continue . . .

In the same way, you can derive a generic class from another generic class that derived from a generic interface.

Constraining a Generic Class

Imagine you create a regular interface such as the following:

public interface class IGeometry
{
    property String ^ Name;
    void Display();
};

Then imagine you derive a regular class from it. Here is an example:

public ref class CRound : public IGeometry
{
private:
    String ^ _name;
    double   _rad;

public:
    CRound();
    CRound(String ^ name);
    CRound(String ^ name, double radius);

    property String ^ Name
    {
	virtual String ^ get() { return _name; }
	virtual void set(String ^ value) { _name = value; }
    }
	
    property double Radius
    {
	double get() { return _rad; }
        void set(double value)
	{
		_rad = (value <= 0) ? 0.00 : value;
	}
    }

    virtual void Display();
};

CRound::CRound()
{
    _name = L"Unknown";
}

CRound::CRound(String ^ name)
{
    _name = name;
    _rad  = 0.00;
}

CRound::CRound(String ^ name, double radius)
{
    _name = name;
    _rad  = radius;
}

void CRound::Display()
{
    Console::WriteLine(L"Name:   {0}", Name);
    Console::WriteLine(L"Radius: {0}", Radius);
}

You may be tempted to derive just any type of class from it. One of the features of generics is that you can create a class that must implement the functionality of a certain abstract class of your choice. For example, when creating a generic class, you can oblige it to implement the functionality of a certain interface or you can make sure that the class is derived from a specific base class. This would make sure that the generic class surely contains some useful functionality. This is the basis of generic constraints.

To create a constraint on a generic class, after the generic<> declaration, type where TypeName : followed by the rule that the class must follow. For example, you may want the generic class to implement the functionality of a pre-defined class. You can create the generic class as follows:

using namespace System;

public interface class IGeometry
{
    property String ^ Name;
    void Display();
};

public ref class CRound : public IGeometry
{
    . . .
};

. . .

generic <typename T>
where T : CRound
public ref class CSphere
{

};

After creating the class, you must implement the virtual members of the where class, using the rules of generic classes, the way we have done it so far.

When declaring a handle for the generic class, in its <> operator, you must enter a handle to the base class. You must also make sure that the base class has a known value before using it. If the memory is not clearly allocated for the parameter type, you would receive an error.

Here is an example:

using namespace System;

public interface class IGeometry
{
    property String ^ Name;
    void Display();
};

public ref class CRound : public IGeometry
{
private:
    String ^ _name;
    double   _rad;

public:
    CRound();
    CRound(String ^ name);
    CRound(String ^ name, double radius);

    property String ^ Name
    {
	virtual String ^ get() { return _name; }
	virtual void set(String ^ value) { _name = value; }
    }
	
    property double Radius
    {
	double get() { return _rad; }
        void set(double value)
	{
		_rad = (value <= 0) ? 0.00 : value;
	}
    }

    virtual void Display();
};

CRound::CRound()
{
    _name = L"Unknown";
}

CRound::CRound(String ^ name)
{
    _name = name;
    _rad  = 0.00;
}

CRound::CRound(String ^ name, double radius)
{
    _name = name;
    _rad  = radius;
}

void CRound::Display()
{
    Console::WriteLine(L"Name:   {0}", Name);
    Console::WriteLine(L"Radius: {0}", Radius);
}

generic <typename T>
where T : CRound
public ref class CSphere
{
private:
    T _t;

public:
    CSphere();
    CSphere(T fig);
    property T Figure
    {
	T get() { return _t; }
	void set(T value) { _t = value; }
    }
};

generic <typename T>
CSphere<T>::CSphere()
{
}
 
generic <typename T>
CSphere<T>::CSphere(T fig)
{
    _t = fig;
}

int main()
{
    CRound ^ rnd = gcnew CRound;

    rnd->Name   = L"Circle";
    rnd->Radius = 60.12;

    CSphere<CRound ^> ^sph = gcnew CSphere<CRound ^>;
    sph->Figure = rnd;
    Console::WriteLine(L"Circle Characteristics");
    Console::WriteLine(L"Name:   {0}", sph->Figure->Name);
    Console::WriteLine(L"Radius: {0}", sph->Figure->Radius);

    Console::WriteLine();
    return 0;
}

This would produce:

Circle Characteristics
Name:   Circle
Radius: 60.12

Press any key to continue . . .

You can also create a constraint so that a generic class implements an interface. You would use the following:

using namespace System;

public interface class IGeometry
{
    property String ^ Name;
    void Display();
};

generic <typename T>
where T : IGeometry
public ref class CRound
{

}; 
 
 

Previous Copyright © 2006 FunctionX, Inc. Next