访问者模式(行为型)
原创大约 5 分钟
模式概述
访问者模式是一种行为设计模式,它封装了某些施加于某种数据结构元素之上的操作。即使这些操作需要修改,也可以让接受这个操作的数据结构保持不变。
问题
教师节到了,李星云作为儿子的家长,想给学校老师每人送一份教师节礼物,他想来想去,决定给老师们送花和附有感谢的明信片。
但是他不知道该送哪些花,因为他不知道每种花都代表什么意义。而且学校老师有男有女,所授课程也不同,例如数学、物理、化学、生物、地理、语文、英语、历史、体育等,他不知道那种课程适合哪种花。
方案
访问者模式建议将新行为放入一个名为访问者的独立类中,而不是试图将其整合到已有类中。只需要将被执行操作的原始对象作为参数传递给访问者中的方法,让方法访问对象所包含的一切必要数据。
现在,当李星云的需求被花店老板了解到之后,凭借她对每种不同的花所蕴含的意义的了解,以及李星云对老师所授课程、性格和气质的描述,为李星云选出了适合每位老师的花朵。

结构

实现
Java
/**
* 形状接口
*
*/
public interface Shape {
void move(int x, int y);
void draw();
String accept(Visitor visitor);
}
/**
* 访问者接口
*
*/
public interface Visitor {
String visitCircle(Circle circle);
String visitRectangle(Rectangle rectangle);
}
/**
* 圆形
*
*/
public class Circle implements Shape {
private int id;
private int x;
private int y;
private int radius;
public Circle(int id, int x, int y, int radius) {
super(id, x, y);
this.radius = radius;
}
@Override
public void move(int x, int y) {
// move shape
}
@Override
public void draw() {
// draw shape
}
@Override
public String accept(Visitor visitor) {
return visitor.visitCircle(this);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getId() {
return id;
}
public int getRadius() {
return radius;
}
}
/**
* 矩形
*
*/
public class Rectangle implements Shape {
private int id;
private int x;
private int y;
private int width;
private int height;
public Rectangle(int id, int x, int y, int width, int height) {
this.id = id;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public String accept(Visitor visitor) {
return visitor.visitRectangle(this);
}
@Override
public void move(int x, int y) {
// move shape
}
@Override
public void draw() {
// draw shape
}
public int getId() {
return id;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
}
/**
* 具体访问者:将所有形状导出为JSON文件
*
*/
public class ExportVisitor implements Visitor {
public String export(Shape... args) {
StringBuilder sb = new StringBuilder();
sb.append("{\n");
for (Shape shape : args) {
sb.append(shape.accept(this));
}
sb.append("}");
return sb.toString();
}
public String visitCircle(Circle c) {
return "\"shape\":\"circle\"" + "\n" +
"\"id\":" + c.getId()+ "\n" +
"\"x\":" + c.getX()+ "\n" +
"\"y\":" + c.getY()+ "\n" +
"\"radius\":" + c.getRadius()+ "\n";
}
public String visitRectangle(Rectangle r) {
return "\"shape\":\"rectangle\"" + "\n" +
"\"id\":" + r.getId() + "\n" +
"\"x\":" + r.getX() + "\n" +
"\"y\":" + r.getY() + "\n" +
"\"width\":" + r.getWidth() + "\n" +
"\"height\":" + r.getHeight() + "\n";
}
}
/**
* 客户端
*
*/
public class Client {
public static void main(String[] args) {
Circle circle = new Circle(2, 23, 15, 10);
Rectangle rectangle = new Rectangle(3, 10, 17, 20, 30);
export(circle, rectangle);
}
private static void export(Shape... shapes) {
ExportVisitor exportVisitor = new ExportVisitor();
System.out.println(exportVisitor.export(shapes));
}
}
Go
package main
import "fmt"
/**
* 形状接口
*
*/
type Shape interface {
getType() string
accept(Visitor)
}
/**
* 正方形
*
*/
type Square struct {
side int
}
func (s *Square) accept(v Visitor) {
v.visitForSquare(s)
}
func (s *Square) getType() string {
return "正方形"
}
/**
* 圆形
*
*/
type Circle struct {
radius int
}
func (c *Circle) accept(v Visitor) {
v.visitForCircle(c)
}
func (c *Circle) getType() string {
return "圆形"
}
/**
* 长方形
*
*/
type Rectangle struct {
l int
b int
}
func (t *Rectangle) accept(v Visitor) {
v.visitForrectangle(t)
}
func (t *Rectangle) getType() string {
return "长方形"
}
/**
* 访问者接口
*
*/
type Visitor interface {
visitForSquare(*Square)
visitForCircle(*Circle)
visitForrectangle(*Rectangle)
}
/**
* 具体访问者:计算面积
*
*/
type AreaCalculator struct {
area int
}
func (a *AreaCalculator) visitForSquare(s *Square) {
fmt.Println("计算正方形面积")
}
func (a *AreaCalculator) visitForCircle(s *Circle) {
fmt.Println("计算圆形面积")
}
func (a *AreaCalculator) visitForrectangle(s *Rectangle) {
fmt.Println("计算长方形面积")
}
/**
* 具体访问者:计算中点坐标
*
*/
type MiddleCoordinates struct {
x int
y int
}
func (a *MiddleCoordinates) visitForSquare(s *Square) {
fmt.Println("计算正方形中点坐标")
}
func (a *MiddleCoordinates) visitForCircle(c *Circle) {
fmt.Println("计算圆形中点坐标")
}
func (a *MiddleCoordinates) visitForrectangle(t *Rectangle) {
fmt.Println("计算长方形中点坐标")
}
/**
* 客户端
*
*/
func main() {
square := &Square{side: 2}
circle := &Circle{radius: 3}
rectangle := &Rectangle{l: 2, b: 3}
areaCalculator := &AreaCalculator{}
square.accept(areaCalculator)
circle.accept(areaCalculator)
rectangle.accept(areaCalculator)
fmt.Println()
middleCoordinates := &MiddleCoordinates{}
square.accept(middleCoordinates)
circle.accept(middleCoordinates)
rectangle.accept(middleCoordinates)
}
适用场景
如果需要对一个的复杂对象结构(例如对象树)中的所有元素执行某些操作时,可以使用访问者模式。
可通过访问者模式来清理某些辅助行为的业务逻辑。
优缺点
访问者模式的优点。
可将同一行为的不同版本移到同一个类中,符合
单一职责原则
。可以在不同类对象上引入新的行为且无需对这些类做出修改,符合
开闭原则
。
访问者模式的缺点。
每次在元素类的层次结构中添加或移除一个类时,都不得不更新所有的访问者。
在访问者同某个元素进行交互时,它们可能无法获得该元素私有成员变量和方法的必要权限。
相关性
可以将
访问者模式
视为命令模式
的加强版本,它可对不同类的多种对象执行操作。可以使用
访问者模式
对整个组合模式
树执行操作。可以同时使用
访问者模式
和迭代器模式
来遍历复杂数据结构,并对其中的元素执行所需操作,即使这些元素所属的类完全不同。
感谢支持
更多内容,请移步《超级个体》。