- 921. Minimum Add to Make Parentheses Valid 使括号有效的最少添加
- 915. Partition Array into Disjoint Intervals 分割数组
- 932. Beautiful Array 漂亮数组
- 940. Distinct Subsequences II 不同的子序列 II
亦称: Visitor
访问者模式是一种行为设计模式, 它能将算法与其所作用的对象隔离开来。
假如你的团队开发了一款能够使用巨型图像中地理信息的应用程序。 图像中的每个节点既能代表复杂实体 (例如一座城市), 也能代表更精细的对象 (例如工业区和旅游景点等)。 如果节点代表的真实对象之间存在公路, 那么这些节点就会相互连接。 在程序内部, 每个节点的类型都由其所属的类来表示, 每个特定的节点则是一个对象。
将图像导出为 XML。
一段时间后, 你接到了实现将图像导出到 XML 文件中的任务。 这些工作最初看上去非常简单。 你计划为每个节点类添加导出函数, 然后递归执行图像中每个节点的导出函数。 解决方案简单且优雅: 使用多态机制可以让导出方法的调用代码不会和具体的节点类相耦合。
但你不太走运, 系统架构师拒绝批准对已有节点类进行修改。 他认为这些代码已经是产品了, 不想冒险对其进行修改, 因为修改可能会引入潜在的缺陷。
所有节点的类中都必须添加导出至 XML 文件的方法, 但如果在修改代码的过程中引入了任何缺陷, 那么整个程序都会面临风险。
此外,他还质疑在节点类中包含导出 XML 文件的代码是否有意义。 这些类的主要工作是处理地理数据。 导出 XML 文件的代码放在这里并不合适。
还有另一个原因, 那就是在此项任务完成后, 营销部门很有可能会要求程序提供导出其他类型文件的功能, 或者提出其他奇怪的要求。 这样你很可能会被迫再次修改这些重要但脆弱的类。
访问者模式建议将新行为放入一个名为访问者的独立类中, 而不是试图将其整合到已有类中。 现在, 需要执行操作的原始对象将作为参数被传递给访问者中的方法, 让方法能访问对象所包含的一切必要数据。
如果现在该操作能在不同类的对象上执行会怎么样呢? 比如在我们的示例中, 各节点类导出 XML 文件的实际实现很可能会稍有不同。 因此, 访问者类可以定义一组 (而不是一个) 方法, 且每个方法可接收不同类型的参数, 如下所示:
class ExportVisitor implements Visitor is
method doForCity(City c) {
…… }
method doForIndustry(Industry f) {
…… }
method doForSightSeeing(SightSeeing ss) {
…… }
// ……
但我们究竟应该如何调用这些方法 (尤其是在处理整个图像方面) 呢? 这些方法的签名各不相同, 因此我们不能使用多态机制。 为了可以挑选出能够处理特定对象的访问者方法, 我们需要对它的类进行检查。 这是不是听上去像个噩梦呢?
foreach (Node node in graph)
if (node instanceof City)
exportVisitor.doForCity((City) node)
if (node instanceof Industry)
exportVisitor.doForIndustry((Industry) node)
// ……
}
你可能会问, 我们为什么不使用方法重载呢? 就是使用相同的方法名称, 但它们的参数不同。 不幸的是, 即使我们的编程语言 (例如 Java 和 C#) 支持重载也不行。 由于我们无法提前知晓节点对象所属的类, 所以重载机制无法执行正确的方法。 方法会将 节点基类作为输入参数的默认类型。
但是,访问者模式可以解决这个问题。 它使用了一种名为双分派的技巧, 不使用累赘的条件语句也可下执行正确的方法。 与其让客户端来选择调用正确版本的方法, 不如将选择权委派给作为参数传递给访问者的对象。 由于该对象知晓其自身的类, 因此能更自然地在访问者中选出正确的方法。 它们会 “接收” 一个访问者并告诉其应执行的访问者方法。
// 客户端代码
foreach (Node node in graph)
node.accept(exportVisitor)
// 城市
class City is
method accept(Visitor v) is
v.doForCity(this)
// ……
// 工业区
class Industry is
method accept(Visitor v) is
v.doForIndustry(this)
// ……
我承认最终还是修改了节点类, 但毕竟改动很小, 且使得我们能够在后续进一步添加行为时无需再次修改代码。
现在,如果我们抽取出所有访问者的通用接口, 所有已有的节点都能与我们在程序中引入的任何访问者交互。 如果需要引入与节点相关的某个行为, 你只需要实现一个新的访问者类即可。
优秀的保险代理人总能为不同类型的团体提供不同的保单。
假如有这样一位非常希望赢得新客户的资深保险代理人。 他可以拜访街区中的每栋楼, 尝试向每个路人推销保险。 所以, 根据大楼内组织类型的不同, 他可以提供专门的保单:
如果建筑是居民楼, 他会推销医疗保险。
如果建筑是银行, 他会推销失窃保险。
如果建筑是咖啡厅, 他会推销火灾和洪水保险。
1、访问者 (Visitor) 接口声明了一系列以对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的。
2、具体访问者 (Concrete Visitor) 会为不同的具体元素类实现相同行为的几个不同版本。
3、元素 (Element) 接口声明了一个方法来 “接收” 访问者。 该方法必须有一个参数被声明为访问者接口类型。
4、具体元素 (Concrete Element) 必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 请注意, 即使元素基类实现了该方法, 所有子类都必须对其进行重写并调用访问者对象中的合适方法。
5、客户端 (Client) 通常会作为集合或其他复杂对象 (例如一个组合树) 的代表。 客户端通常不知晓所有的具体元素类, 因为它们会通过抽象接口与集合中的对象进行交互。
在本例中, 访问者模式为几何图像层次结构添加了对于 XML 文件导出功能的支持。
通过访问者对象将各种类型的对象导出为 XML 格式文件。
// 元素接口声明了一个accept(接收)方法,它会将访问者基础接口作为一个参
// 数。
interface Shape is
method move(x, y)
method draw()
method accept(v: Visitor)
// 每个具体元素类都必须以特定方式实现accept方法,使其能调用相应元素类的
// 访问者方法。
class Dot implements Shape is
// ……
// 注意我们正在调用的visitDot(访问点)方法与当前类的名称相匹配。
// 这样我们能让访问者知晓与其交互的元素类。
method accept(v: Visitor) is
v.visitDot(this)
class Circle implements Shape is
// ……
method accept(v: Visitor) is
v.visitCircle(this)
class Rectangle implements Shape is
// ……
method accept(v: Visitor) is
v.visitRectangle(this)
class CompoundShape implements Shape is
// ……
method accept(v: Visitor) is
v.visitCompoundShape(this)
// 访问者接口声明了一组与元素类对应的访问方法。访问方法的签名能让访问者准
// 确辨别出与其交互的元素所属的类。
interface Visitor is
method visitDot(d: Dot)
method visitCircle(c: Circle)
method visitRectangle(r: Rectangle)
method visitCompoundShape(cs: CompoundShape)
// 具体访问者实现了同一算法的多个版本,而且该算法能与所有具体类进行交互。
//
// 访问者模式在复杂对象结构(例如组合树)上使用时能发挥最大作用。在这种情
// 况下,它可以存储算法的一些中间状态,并同时在结构中的不同对象上执行访问
// 者方法。这可能会非常有帮助。
class XMLExportVisitor implements Visitor is
method visitDot(d: Dot) is
// 导出点(dot)的 ID 和中心坐标。
method visitCircle(c: Circle) is
// 导出圆(circle)的 ID 、中心坐标和半径。
method visitRectangle(r: Rectangle) is
// 导出长方形(rectangle)的 ID 、左上角坐标、宽和长。
method visitCompoundShape(cs: CompoundShape) is
// 导出图形(shape)的 ID 和其子项目的 ID 列表。
// 客户端代码可在不知晓具体类的情况下在一组元素上运行访问者操作。“接收”操
// 作会将调用定位到访问者对象的相应操作上。
class Application is
field allShapes: array of Shapes
method export() is
exportVisitor = new XMLExportVisitor()
foreach (shape in allShapes) do
shape.accept(exportVisitor)
如果你并不十分理解为何本例中需要使用 accept接收方法, 我的一篇文章Visitor and Double Dispatch详细解释了这个问题。
1、如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式。
访问者模式通过在访问者对象中为多个目标类提供相同操作的变体, 让你能在属于不同类的一组对象上执行同一操作。
2、可使用访问者模式来清理辅助行为的业务逻辑。
该模式会将所有非主要的行为抽取到一组访问者类中, 使得程序的主要类能更专注于主要的工作。
3、当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式。
你可将该行为抽取到单独的访问者类中, 只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。
1、 在访问者接口中声明一组“访问”方法,分别对应程序中的每个具体元素类;
2、 声明元素接口如果程序中已有元素类层次接口,可在层次结构基类中添加抽象的“接收”方法该方法必须接受访问者对象作为参数;
3、 在所有具体元素类中实现接收方法这些方法必须将调用重定向到当前元素对应的访问者对象中的访问者方法上;
4、 元素类只能通过访问者接口与访问者进行交互不过访问者必须知晓所有的具体元素类,因为这些类在访问者方法中都被作为参数类型引用;
5、 为每个无法在元素层次结构中实现的行为创建一个具体访问者类并实现所有的访问者方法;
你可能会遇到访问者需要访问元素类的部分私有成员变量的情况。 在这种情况下, 你要么将这些变量或方法设为公有, 这将破坏元素的封装; 要么将访问者类嵌入到元素类中。 后一种方式只有在支持嵌套类的编程语言中才可能实现。
6、 客户端必须创建访问者对象并通过“接收”方法将其传递给元素;
你可以将访问者模式视为命令模式的加强版本, 其对象可对不同类的多种对象执行操作。
你可以使用访问者对整个组合模式树执行操作。
可以同时使用访问者和迭代器模式来遍历复杂数据结构, 并对其中的元素执行所需操作, 即使这些元素所属的类完全不同。
访问者是一种行为设计模式, 允许你在不修改已有代码的情况下向已有类层次结构中增加新的行为。
使用示例: 访问者不是常用的设计模式, 因为它不仅复杂, 应用范围也比较狭窄。
本例说明了访问者设计模式的结构并重点回答了下面的问题:
它由哪些类组成?
这些类扮演了哪些角色?
模式中的各个元素会以何种方式相互关联?
/**
* The Visitor Interface declares a set of visiting methods that correspond to
* component classes. The signature of a visiting method allows the visitor to
* identify the exact class of the component that it's dealing with.
*/
class ConcreteComponentA;
class ConcreteComponentB;
class Visitor {
public:
virtual void VisitConcreteComponentA(const ConcreteComponentA *element) const = 0;
virtual void VisitConcreteComponentB(const ConcreteComponentB *element) const = 0;
};
/**
* The Component interface declares an accept method that should take the base
* visitor interface as an argument.
*/
class Component {
public:
virtual ~Component() {
}
virtual void Accept(Visitor *visitor) const = 0;
};
/**
* Each Concrete Component must implement the Accept method in such a way that
* it calls the visitor's method corresponding to the component's class.
*/
class ConcreteComponentA : public Component {
/**
* Note that we're calling visitConcreteComponentA, which matches the
* current class name. This way we let the visitor know the class of the
* component it works with.
*/
public:
void Accept(Visitor *visitor) const override {
visitor->VisitConcreteComponentA(this);
}
/**
* Concrete Components may have special methods that don't exist in their base
* class or interface. The Visitor is still able to use these methods since
* it's aware of the component's concrete class.
*/
std::string ExclusiveMethodOfConcreteComponentA() const {
return "A";
}
};
class ConcreteComponentB : public Component {
/**
* Same here: visitConcreteComponentB => ConcreteComponentB
*/
public:
void Accept(Visitor *visitor) const override {
visitor->VisitConcreteComponentB(this);
}
std::string SpecialMethodOfConcreteComponentB() const {
return "B";
}
};
/**
* Concrete Visitors implement several versions of the same algorithm, which can
* work with all concrete component classes.
*
* You can experience the biggest benefit of the Visitor pattern when using it
* with a complex object structure, such as a Composite tree. In this case, it
* might be helpful to store some intermediate state of the algorithm while
* executing visitor's methods over various objects of the structure.
*/
class ConcreteVisitor1 : public Visitor {
public:
void VisitConcreteComponentA(const ConcreteComponentA *element) const override {
std::cout << element->ExclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor1\n";
}
void VisitConcreteComponentB(const ConcreteComponentB *element) const override {
std::cout << element->SpecialMethodOfConcreteComponentB() << " + ConcreteVisitor1\n";
}
};
class ConcreteVisitor2 : public Visitor {
public:
void VisitConcreteComponentA(const ConcreteComponentA *element) const override {
std::cout << element->ExclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor2\n";
}
void VisitConcreteComponentB(const ConcreteComponentB *element) const override {
std::cout << element->SpecialMethodOfConcreteComponentB() << " + ConcreteVisitor2\n";
}
};
/**
* The client code can run visitor operations over any set of elements without
* figuring out their concrete classes. The accept operation directs a call to
* the appropriate operation in the visitor object.
*/
void ClientCode(std::array<const Component *, 2> components, Visitor *visitor) {
// ...
for (const Component *comp : components) {
comp->Accept(visitor);
}
// ...
}
int main() {
std::array<const Component *, 2> components = {
new ConcreteComponentA, new ConcreteComponentB};
std::cout << "The client code works with all visitors via the base Visitor interface:\n";
ConcreteVisitor1 *visitor1 = new ConcreteVisitor1;
ClientCode(components, visitor1);
std::cout << "\n";
std::cout << "It allows the same client code to work with different types of visitors:\n";
ConcreteVisitor2 *visitor2 = new ConcreteVisitor2;
ClientCode(components, visitor2);
for (const Component *comp : components) {
delete comp;
}
delete visitor1;
delete visitor2;
return 0;
}
The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1
It allows the same client code to work with different types of visitors:
A + ConcreteVisitor2
B + ConcreteVisitor2
我正在尝试为玩具语言实现一个解析器。 我已经编写了语法,但是当我尝试从 CST 创建 AST 时遇到了问题。 我定义了一个继承自 MyParserVisitor 的类哪里ASTNode是一个虚拟类,我
我想创建子类对象,它会与其他子类对象做出不同的 react (类可以欢迎另一个类,但不是全部)代码原理源自访客设计模式: class A { public : virtual bool isW
我正在玩 boost A* 算法,从在以下位置找到的示例开始:http://www.boost.org/doc/libs/1_37_0/libs/graph/example/astar-cities.
我正在为我的网站构建一个访问者和点击率计数器,当然,访问者每天是一个唯一的 ip,点击率是每次请求页面时! 我已经创建了数据库和系统来插入和更新我保存统计信息的表! 我正在苦苦挣扎的是,如何对日期进行
如何放弃使用 IE6 浏览我的网站? 就像是 : 如果 ie6 => 死 我正在使用 ASP.Net 谢谢 最佳答案 在asp.net中你可以查看Request.Browser在 Session_st
我目前正在编写解析器。解析器生成一个 AST,然后我使用各种遍历来处理它。 AST 是(简化的): type LiteralExpr = { readonly kind: 'literal',
我正在使用 Antlr4 的 C++ 访问者 api 来遍历解析树。但是,我正在努力使其正常运行。也就是说,我不确定如何使用 visitChildren(ParseTree *tree) 调用。 我为
我的问题是理论性的,而不是如何去做。我想知道专家们如何处理我将在下面描述的情况。 我有一个使用 Angular 和 Breeze 的 SPA。身份验证是基于 token 的。我在 Angular 中设
我有一棵树(在图形意义上)表示一棵树(在物理意义上)。树表示为 BGL 邻接列表,其中每个顶点包含半径和位置属性,即,我的图以以下形式定义 struct TreeVertexType { doub
关闭。这个问题是off-topic .它目前不接受答案。 想改进这个问题吗? Update the question所以它是on-topic用于堆栈溢出。 关闭 10 年前。 Improve thi
是否可以将域限制为仅允许使用特定浏览器的访问者? 我正在构建一个应用程序,到目前为止它只在 Chrome 中进行过测试,所以我只想在 Beta 测试期间允许 Chrome 用户。基本上,我想在进行测试
你好,我正在尝试实现一个 AST Clang 访问者,这是我的代码。 class ExampleVisitor : public RecursiveASTVisitor { private:
我想构建一个访问者(用于 dikstra),并将 initialise_vertex 用作“颜色映射”修饰符。我想根据条件从搜索中排除一些顶点。所以我想在算法的初始部分将一些顶点设置为“黑色”。 cl
我正在尝试编写一个网站,提示每 10,000 名访问者输入一封可以存储在文本文件中的电子邮件。 我设置了一个点击计数器,它将访问者总数输出到一个文本文件中,因此是否可以将脚本设置为类似于 "如果 nu
我正在尝试将 AST 与 ANTLR4 一起使用,并使用以下文件: 生成器.java import org.antlr.v4.runtime.ANTLRInputStream; import org.
在 BGL 中,我不太明白如何在 bfs/dfs 搜索期间访问图中顶点的固有颜色(白色表示未触及,灰色表示已访问,黑色表示已完成)。 有人可以说明如何从 dfs/bfs 访问者中访问顶点的颜色吗?例如
当用户访问您的网站时,如何获取用户的信息? IP地址 苹果地址 用户个人资料名称 操作系统名称 操作系统版本 操作系统注册到(姓名/公司) 计算机名 浏览器名称 浏览器版本 ISP 名称/Intern
我最近开始使用 Amazon S3 为访问者提供图像,因为这会减少服务器负载。现在,出现了一个新问题:今天我查看了我的 AWS 账单。我注意到我有一大笔账单等着我——20 天内总共有 4TB 的 AW
session 劫持 所以我有一个小问题。我正在尝试识别访问者,这很难通过 $_SERVER veriables 来实现,如以下问题所述:Preventing session hijacking .
我有一个带有两个边定义的图,如下所示: isDepartment: [organisation] -> [organisation] hasAccess: [user] -> [organisatio
我是一名优秀的程序员,十分优秀!