A very important question is the control of parameters in a program. Almost every nontrivial class contributing to the overall behavior of a program contains some parameters, that should be modifiable at startup or at runtime, e.g. the size of a data structure, filename for input and output etc. All these classes play the role of a controlable , that is, they have some parameters to be controled by some ControlDevice which is the counterpart. A ControlDevice could be, for example,
A controlable could be for example a simple class like
class square : public controlable {
private:
double a, b; // sides
public:
square(ap = 0.0, bp = 0.0) : a(ap), b(bp) {}
// need some method for pulling in parameters a, b from file
// traditional solution:
void read(istream& in);
// 'controlable' solution: Let a ControlDevice know the details
// and do the work
virtual void register_at(ControlDevice&);
};
class controlable {
public:
virtual void register_at(ControlDevice&,const string& prefix) = 0;
virtual void register_at(ControlDevice&);
virtual void notify() {} // may be used to signal a change
virtual ~controlable() {}
};
The "other" side (ControlDevice) which abstracts from the paramater input mechanism has the following outlook:
class ControlDevice {
public:
ControlDevice(control_device_impl* imp = 0) : impl(imp) {}
void add(const string& nm, Mutator* value_ref);
void update();
string name() const;
void print_values(ostream&) const;
ControlDevice getSubDevice(const string& name);
private:
control_device_impl* impl;
};
Now how does it work? A controlable will register_at(..) a given ControlDevice (e.g. via the method add(..) for basic parameters). This ControlDevice will then be able to change the parameters, for example upon a call to update() , which would in the case of a parameter-file cause the file to be read in. Where's the big deal of it? The advantage of this abstract approach is twofold: First, the code does not get littered with statements to read in parameters. Second, one gains flexibility in choosing the user interface: The implementation of the classes with parameters does not have to change, wether we have a GUI-interface, a simple parameter file or something completely different.
How does it work? Looking at the interface of ControlDevice , you see the function
void add(const string& nm,Mutator* value_ref);This is a name-value-pair (nm,value_ref ). The name is used to reference the right variable from the outside (e.g. in a file or as label in a window), the value reference is used to change the underlying variable. The interface of class Mutator follows:
class Mutator {
public:
virtual void read (istream& in) = 0;
virtual void print(ostream& out) const = 0;
virtual void print(ostream& out, const string& name) const = 0;
virtual string description() const;
virtual ~Mutator() {}
};
The value is actually changed via reading from a stream, which could be a string typed in at a window. A simple implementation of this interface is supplied by the following template:
template<class T>
class TypedMutator : public Mutator
protected:
T& v;
public:
TypedMutator(T& vv) : v(vv)
T value() return v;
vir1tual void read(istream& in) in >> v;
virtual void print(ostream& out) const
out << v;
virtual void print(ostream& out, const string& prefix = "") const
out << prefix << v;
;
There are other Mutators, see the Mutator documentation. You can attach a (simple) Mutator to a variable like this:
ControlDevice Ctrl(GetFileControlDevice("myparameters.in"));
...
double x;
Ctrl.add("x",GetMutator(x));
// RegisterAt(Ctrl,"x",x) is a synomym for the above.
The file myparameters.in could then contain the name-value-pair x 0.43 , and on Ctrl.update() the variable x would be set to 0.43 . This example also shows how to create a ControlDevice More examples of how this works are found in ex1.C together with a sample input file. It shows also how to use other types of Mutators and how to build hierarchical (i.e. nested) name-value-structures.