Notes for week 11

For this week's links click here.

Introduction

We are finally ready to return to the problems involved in creating and maintaining files of mixed objects in a Windows environment. First we will discuss the use of factories and abstract factories to create instances of objects in a hierarchy. Then we will construct a windows program that reads files of objects and uses list boxes of objects to display file contents.

Abstract Factories and Factories

In programs that maintain a hierarchy of objects, creating a new object in a way that will only require minor changes if new classes are added to the hierarchy is a challenge. First, objects may perform operations on themselves once they exist, but if you want an object to create itself you have the chicken and egg delemma. Which comes first?

In the language SmallTalk there are two types of functions, instance functions and class functions. Instance functions are used by objects to manipulate themselves, while class functions are used in cases when a member of the class must be created. Static functions play the role of class functions in c++.

Static Functions

Static functions are also used to set values of static variables. Static variables hold data that is common to all members of a class. If for example you have a class SavingsAccount, and all of the accounts have the same interest rate, that rate can be stored as a static variable. There is only one copy and that copy cannot be changed by an individual SavingsAccount, it must be changed by the class itself.

Given the partial definition of SavingsAccount:

class SavingsAccount
{
public:
....
	static setInterestRate(double rate);
....
private:
	static double _rate;
...
};

The function setInterestRate is called in the following manner:

SavingsAccount::setInterestRate(0.07);

Factories

Static functions are ideal for creating new class members since they are called by the class which exists, not the object which doesn't. Up until now whenever we wanted an object (say SavingsAccount) all we had to do was define it.

SavingsAccount newAccount;

Now if we have more than one type of account, it is necessary to create a pointer to the desired type of object at run time, so it cannot be created by the programmer. We will put the base class in charge of creating these pointers using an abstract factory. This is a static function that calls on a virtual static function that belongs to the class of the desired object and creates an object from that class. These virtual functions are called concrete factories

C++ Implementation of Factories

In c++ abstract factories are created as static functions in the parent class of a hierarchy. If the following diagram represents your hierarchy, then the class Parent is created as described below. An example of a header for such a hierarchy can be seen by clicking here.

The static function create receives a pointer of the type you want to generate and then calls on the virual function of that class to generate a new member of the class.

Parent* Parent::create(Parent *p)
{
	return p->clone();
}
The function clone in the Parent classis an abstract factory, while the clone function in each of the children is a concrete factory. These concrete factories are very simple:
Parent* ChildX::clone()
{
	return new ChildX;
}

Using Factories

Creating a New Object

So far this seems like a complex series of actions that results in a pointer you already have creating another pointer of the same type. Creating the first pointer should have been enough. Consider the problem of creating a new Child of a given type in response to a request. If you use the menu introduced in week 5, and require that the choice 0 corresponds to Child, 1 to Child2, etc, then the following code will create the appropriate type of child in response to this request if there are 4 child classes.

Parent* type[] = {&Child1(), &Child2(), &Child3(), &Child4()}
int choice = ChoiceMenu.makeChoice();
RcPointer < Parent > newOne = Parent::create( type[choice]);

Reading From Files

In order to read a list of mixed types, before reading an item you must determine its type. Once you know the type of thing you are reading, you can create an object of that type and have it read itself. To see how this works in a specific example click here.

Calling on Different Operations

One of the goals of object-oriented programming is clearification of logic. Menus such as that used in the last example are usually used to request a call to a function that completes a set of actions. Normally these functions are called either in a series of if-then-else statements or a switch statement. Can we apply the methods used to create children to create different functions? Yes. We create a hierarchy of classes each with a function that does one of the operations that the menu controls. All of these functions are virtual and have the same name. These classes are called functors.

class Operation
{
public:
	Operation();
	static Operation* create(Operation *);
	virtual execute(/*possibly parameters*/)const=0;
private:
	virtual Operation* clone()=0;
};

class Function1 :public Operation //Function1 should describe the nature of the operation
{
public:
	Function1();
	virtual execute(/*same parameters as execute in parent*/)const;
private:
	virtual Operation* clone();
};

The main program then becomes:

	Operation* type[] = {&Function1(), &Function2(), &Function3(), &Function4()}
	int choice = ChoiceMenu.makeChoice();
	RcPointer < Operation > function = Operation::create( type[choice]);
	function->execute(/*appropriate parameters);

Complete Project Using This Technique

To get the example click here.