2

I have different visitors for specific types of objects. I have a problem implementing a general interface which can be used for all types. What is the best architecture to use in this situation? I've came up with 3 different solutions, but all of them look pretty ugly to me: (some things like virtual destructors are dropped for simplicity)

class IObject {
    virtual void Accept(IVisitor& visior) = 0;
};

class Text: IObject {
    void Accept(IVisitor& visitor) {
        visitor.Visit(*this);
    }
};

class Image: IObject {
    void Accept(IVisitor& visitor) {
        visitor.Visit(*this);
    }
};

class IVisitor {
    virtual void Visit(Text& text) = 0;
    virtual void Visit(Image& image) = 0;
};

class TextVisitor: IVisitor {
    void Visit(Text& text) {
        // Do some stuff with text
    }

    void Visit(Image& image) {
        // Image not supported, throw exception
    }
};

OR

class IObject {};
class Text: IObject {};
class Image: IObject {};

class IVisitor {
    virtual void Visit(IObject& object) = 0;
};

class TextVisitor: IVisitor {
    void Visit(IObject& object) {
        Text& text = dynamic_cast<Text&>(object);
        // Do some stuff with text
    }
};

OR

template <typename T>
class IVisitor {
    virtual void Visit(T& object) = 0;
};

class TextVisitor: IVisitor<Text> {
    void Visit(Text& text) {
        // Do some stuff
    }
};

class ImageVisitor: IVisitor<Image> {
    void Visit(Image& image) {
        // Do some stuff
    }
};

class ITextImagelVisitor: IVisitor<Text>, IVisitor<Image> {};

class VisitorDispatcher: ITextImageVisitor {
    void Visit(Text& text) {
        text_visitor_->Visit(text);
    }

    void Visit(Image& image) {
        image_visitor_->Visit(image);
    }

    std::shared_ptr<IVisitor<Text>> text_visitor_;
    std::shared_ptr<IVisitor<Image>> image_visitor_;
};

class IObject {
    virtual void Accept(ITextImageVisitor& visior) = 0;
};

class Text: IObject {
    void Accept(ITextImageVisitor& visitor) {
        visitor.Visit(*this);
    }
};

class Image: IObject {
    void Accept(ITextImageVisitor& visitor) {
        visitor.Visit(*this);
    }
};
1
  • The classic (GoF) Visitor pattern uses the approach #1. It is also certainly preferable to #2. What is it you don't like about it? Commented Jun 18, 2013 at 9:54

2 Answers 2

3

The answer clearly depends on what you are trying to achieve here. The visitor pattern is used to work with composed objects like e.g. trees and calls itself on subobjects of that composition. In your example, that would be a text that contains texts and images that contain texts and images... That clearly does not seem to make much sense, so if you are in fact working with texts and images, visitor might not be what you need, and you should provide some more information on what you are trying to achieve.

To your different codes:

  1. looks good, it is a valid visitor implementation. The visitor will work on any composite that does not contain images.
  2. looks bad, due to the dynamic_cast. The whole thing about visitor pattern is to avoid such casts, and this one is not extendable. Consider e.g. some more types in your object hierarchy, e.g. sound files, video and so on. using dynamic_cast here will not help you much. And if you want to support only exactly one type of object, you don't need a visitor.
  3. looks even worse. Your VisitorDispatcher inherits from Visitor<Text> and contains a Visitor<Text> as well. That's a weird design at best.
0

Notice how your Visitor class needs information about derived classes of Object. You want to do polymorphism yet you're stuck creating a separate visitor for each new derived Object. In order to make this work you'd need to have your Object be more versatile. I'm assuming you're trying to create some sort of mechanism for drawing Objects on the screen, so every object also needs virtual int width() = 0 and virtual int height() = 0 which are implemented by derivers. An abstract method virtual void draw() = 0 for Object would then also be necessary. This way, you don't need a class TextVisitor and also not a class ImageVisitor, the Visitor can simply call object->draw() on every Object it visits.

3
  • The point is that I don't want to put this implementation into the Object, because there could be many drawing methods for the same data type (by the way, it's not drawing on the screen, but it doesn't matter). E.g. in case of text the object is just a string, and it shouldn't know about various processing methods that could be applied to this string.
    – lizarisk
    Commented Jun 18, 2013 at 10:23
  • Indeed Object shouldn't know about various processing methods that could be applied to Text, but it should expose the possibility of processing via a pure virtual method that gets implemented by Text.
    – rwols
    Commented Jun 18, 2013 at 10:26
  • I think the Text itself shouldn't know about it's processing methods either.
    – lizarisk
    Commented Jun 18, 2013 at 10:59

Not the answer you're looking for? Browse other questions tagged or ask your own question.