I got this code as an example of use of double dispatch, but I don't really understand one part of the code. creating the "abstract class" Printer, why I need to add:
virtual void print(PDFDoc *d)=0;
virtual void print(DocDoc *d)=0;
As I understand it, at run time p.print(docA);
will send me to virtual void print(Document *d)
of myPrinter, then d->printMe(this)
will send me to printMe of PDFDoc and then it will call at run time to virtual void print(PDFDoc *d)
of my printer?
So why defining
virtual void print(PDFDoc *d)=0;
virtual void print(DocDoc *d)=0;
is necessary for the abstract class?
class Document{
public:
//this is the accept function
virtual void printMe(Printer *p)=0;
};
class Printer{
public:
virtual void print(Document *d)=0;
//the visitors
virtual void print(PDFDoc *d)=0;
virtual void print(DocDoc *d)=0;
};
class PDFDoc : public virtual Document{
public:
virtual void printMe(Printer *p){
std::cout << "PDFDoc accepting a print call" << std::endl;
p->print(this);
}
};
class DocDoc : public virtual Document{
public:
virtual void printMe(Printer *p){
std::cout << "DocDoc accepting a print call" << std::endl;
p->print(this);
}
};
class MyPrinter : public virtual Printer{
public:
virtual void print(Document *d){
std::cout << "dispatching function <print> called" << std::endl;
d->printMe(this);
}
virtual void print(PDFDoc *d){
std::cout << "printing a PDF doc" << std::endl;
}
virtual void print(DocDoc *d){
std::cout << "printing a Doc doc" << std::endl;
}
};
int main(){
MyPrinter p;
Document *docA = new PDFDoc();
Document *docB = new DocDoc();
p.print(docA);
p.print(docB);
delete docA;
delete docB;
return 0;
}
Because the argument to printMe()
is the pointer to the abstract base class, Printer
:
virtual void printMe(Printer *p){
And the purpose of the "double-dispatch" design pattern is to implement print()
passing the appropriate derived Document
class as a parameter.
Without the overloads for the derived Document
classes, the only method in the base class is the one that takes the abstract Document
base class:
p->print(this);
And without the additional overloads, this just calls the same virtual method that takes the virtual Document
base class as a parameter.
The sequence of events is:
The virtual base class Printer
gets called with the parameter being the virtual document class Document
.
The actual printer implementations use on the actual class that's derived from Document.
So, Document
's pure virtual printMe()
method gets called, from print()
that takes the Document
pointer as a parameter.
The only parameter that printMe()
takes is the virtual Printer
base class pointer.
So, whatever printMe()
calls, it can only call the methods defined in the virtual Printer
base class.
Therefore, if the actual printer implementation needs to use the derived Document
class, those methods have to be virtual methods in the Printer
base class.
Those virtual methods don't really have to be print()
overloads. They can be anything. It might be more clear, to some, to name them something different, like printPDF()
and printDoc
(). If you were to rewrite them, as such, it might be clearer what's going on.