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

在这里,我们将一个对象传递给一个向它添加操作的函数。在这里,它违反了封装。它是好的面向对象设计吗?

4

2 回答 2

4

如果通过对象内部发生变化时可能直接受到影响的函数的数量来衡量封装,则该模式在实现接口时将封装至少减少ConcreteElement1 。Element::accept(Visitor)如果ConcreteElement需要扩展接口以提供操作以实现ConcreteVisitor.

模式本质上既不好也不坏,因为通常是上下文导致模式有利或不利。因此,设计师应该考虑模式的意图、动机、适用性和后果。访问者模式的目的是允许定义新的操作而不改变操作所操作的类。虽然不能保证,但与将操作添加到自身相比,它可能会增加-family的相对封装。ElementElement

例如,考虑操作要检查CarElement对象的液位的情况。没有访问者,CarElement接口声明一个check_fluid_levels()函数:

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()

CarElement虽然检查某些sBreak和的液位WindshieldWiper可能有意义,但它可能不适用于所有CarElement对象,例如Wheel。尽管如此,由于在接口上声明,相对封装Wheel已经减少。check_fluid_levels()CarElement

另一方面,如果使用访问者模式,那么只有检查液位有意义的元素才会提供check_fluid_levels()功能。

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)

虽然函数的相对封装减少了 1 accept(),但相对封装的缩放比将操作添加到CarElement. 考虑添加一个新操作来检查CarElements 的排放水平。在这种情况下,上述CarElements 都不会提供有意义的check_emissions()功能。对于访问者,新操作不会影响封装级别;另一方面,如果将操作添加到CarElement.

这是一个图表,列出了可以访问Element's内部的函数的数量,使用上述示例和两个操作(检查液位和检查排放):

                CarElement 访客
                操作操作
休息 2 2
挡风玻璃雨刮器 2 2
轮子 2 1
于 2013-08-27T17:56:38.550 回答
3

我不认为这违反了封装,因为Visitor它不知道被访问对象的内部结构。

在您的汽车示例中,我们的 Car 对象知道它有 4 个轮子和一个汽油发动机,但访问者只知道我们告诉它什么,而不知道我们如何存储数据:

void accept(CarElementVisitor *visitor) {

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

    visitor->visit(gasEngine);
}

稍后我们可以在不改变访问者的访问调用的情况下更改 Car 对象的实现:

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);
}
于 2013-08-27T17:54:17.793 回答