asit_dhal
asit_dhal

Reputation: 1269

visitor pattern and violation of encapsulation

void accept(CarElementVisitor *visitor) {
        visitor->visit(this);
    }
}

Here we are passing an object to a function which adds an operation to it.Here, it violates encapsulation. Is it good object oriented design ?

Upvotes: 4

Views: 1133

Answers (2)

Tanner Sansbury
Tanner Sansbury

Reputation: 51871

If one measures encapsulation by the count of functions that could be directly affected if the internals of an object changes, then the pattern reduces the encapsulation of ConcreteElement by a minimum of 1 when implementing the Element::accept(Visitor) interface. Encapsulation may be decreased further if ConcreteElement's interface needs to be expanded to provide operations to fulfill the functionality of ConcreteVisitor.

Patterns are neither inherently good nor bad, as it is often the context that results in the pattern being beneficial or a liability. Thus, a designer should consider a pattern's intention, motivation, applicability, and consequences. The Visitor pattern's intention is to allow new operations to be defined without changing the classes on which the operation operates. While not guaranteed, it is possible that it increases the relative encapsulation for the Element-family compared to adding the operations to the Element itself.

For example, consider the case where an operation wants to check the fluid levels for CarElement objects. Without a Visitor, the CarElement interface declares a check_fluid_levels() function:

CarElement                  { check_fluid_levels() }
Break: CarElement           { check_fluid_levels() }
WindshieldWiper: CarElement { check_fluid_levels() }
Wheel: CarElement           { check_fluid_levels() }

for e in car:
  e.check_fluid_levels()

While it may make sense to check the fluid level for someCarElements like Break and WindshieldWiper, it likely does not make sense for all CarElement objects, such as Wheel. Nevertheless, the relative encapsulation of Wheel has been decreased, due to check_fluid_levels() being declared on the CarElement interface.

On the other hand, if the Visitor pattern is used, then only the elements for which checking the fluid level makes sense will provide a check_fluid_levels() function.

CarElement                  { accept(Visitor) }
Break: CarElement           { accept(Visitor); check_fluid_levels() }
WindshieldWiper: CarElement { accept(Visitor); check_fluid_levels() }
Wheel: CarElement           { accept(Visitor) }

FluidChecker: Visitor       { ... }

FluidChecker checker
for e in car:
  e.accept(checker)

While the relative encapsulation decreases by 1 for the accept() function, the relative encapsulation scales better than adding the operation to CarElement. Consider adding a new operation to check the emission levels for CarElements. In this case, none of the above CarElements would provide a meaningful check_emissions() function. With a Visitor, the new operation does not chance the encapsulation level; on the other hand, the encapsulation level changes if the operation is added to CarElement.

Here is a chart listing the count of functions with access to an Element's internals, using the above examples and two operations (checking fluid levels and checking emissions):

                CarElement         Visitor
                operations       operations
Break               2                2
WindshieldWiper     2                2
Wheel               2                1

Upvotes: 4

dkatzel
dkatzel

Reputation: 31648

I don't think this violates encapsulation because the Visitor doesn't know the internal structure of the object being visited.

In your car example, our Car object knows that it has 4 wheels and a gasoline engine, but the visitor only knows what we tell it and not how we store our data:

void accept(CarElementVisitor *visitor) {

    visitor->visit(wheel_1);
    visitor->visit(wheel_2);
    visitor->visit(wheel_3);
    visitor->visit(wheel_4);

    visitor->visit(gasEngine);
}

Later we can change the implementation of our Car object without changing the visitor's visit calls:

void accept(CarElementVisitor *visitor) {
    //wheels now stored in array

    visitor->visit(wheels[0]);
    visitor->visit(wheels[1]);
    visitor->visit(wheels[2]);
    visitor->visit(wheels[3]);

    visitor->visit(gasEngine);
}

Upvotes: 3

Related Questions