Re: NVI idiom and patterns such as Decorator and Proxy
On Jun 11, 6:17 am, Christian Hackl <h...@sbox.tugraz.at> wrote:
I've got a design question related to the combination of the NVI idiom
(non-virtual interfaces, [1]) and popular object-oriented patterns such
as Proxy or Decorator, i.e. those which have the basic idea of deriving
from a base class and delegating to an object of it at the same time.
My problem is that I cannot seem to combine those two techniques in a
flawless way. For a very simple, non real life example (for which I
shall omit smart pointers and std::strings :)), let's say I've got an
abstract base class Printer, from which ConcretePrinter is derived. I'd
express this in C++ as follows:
class Printer
{
//...
public:
virtual ~Printer() {}
void print(char *str);
private: // could also be protected; doesn't matter for my problem
virtual void doPrint(char *str) = 0;
};
class ConcretePrinter : public Printer
{
//...
public:
virtual ~ConcretePrinter() {}
private:
virtual void doPrint(char *str);
};
The point is that Printer::print can do some parameter checking before
delegating to doPrint:
void Printer::print(char *str)
{
if (str)
doPrint(str);
}
The derived class then actually performs the operation:
void ConcretePrinter::doPrint(char *str) { cout << str << endl; }
So far, that's fine. But now let's say I want to use a Decorator to add
some extra output:
class PrinterDecorator : public Printer
{
public:
virtual ~PrinterDecorator() {}
PrinterDecorator(Printer *decorated_printer) :
decorated_printer_(decorated_printer) {}
//...
private:
virtual void doPrint(char *str)
{
cout << "some decoration..." << endl;
decorated_printer_->print(str); // <-- line that bugs me
cout << "some decoration..." << endl;
}
Printer *decorated_printer_;
};
The print() call in this piece of code is what bugs me. I cannot call
decorated_printer_'s doPrint() because it is non-public in this context,
but calling print() means that all parameter checking performed in
Printer::print() is uselessly duplicated, and it would be duplicated
again for all further decorators or proxies I might add. After all, by
the time PrinterDecorator::doPrint() is called, all necessary checking
already took place in Printer::print(). It's like the derived class
telling the base class, "I know you already checked the data, but please
check it again anyway."
Granted, in this stupid example, it's just a pointer check, but think of
more expensive operations such as "do files exist", "can server be
accessed", or "lock for other threads". A program that duplicates such
operations "by design" doesn't strike me as very well designed.
How do I cope with this situation? Is it just an unfortunate fact of
life that NVI and Decorator/Proxy-like patterns don't mix? Or am I
missing something?
The solution is to declare PrinterDecorator a friend of the Printer
class. As a friend of the Printer class, PrinterDecorator's doPrint()
method will be able to call doPrint() on its wrapped Printer object:
class Printer
{
friend class PrinterDecorator;
...
};
virtual void doPrint(char *str)
{
cout << "some decoration..." << endl;
decorated_printer_->doPrint(str); // OK
cout << "some decoration..." << endl;
}
Furthermore, doPrint() should really be declared protected instead of
private - since a derived class may wish to call its base class'
version of doPrint() in the course of executing its own doPrint().
(Note that - even if doPrint() were declared protected instead of
private - the friend declaration would still be needed in order for
PrinterDecorator to be able to call its decorated_printer's doPrint()
method).
Greg