Visitor 访问者模式

设计模式 2020-05-10 7274 字 1046 浏览 点赞

起步

访问者模式属于行为型。它会把操作(operator)与结构(structure)分离,使得在不改变原有结构的情况下,添加更多的其他操作。

以上解释让人很懵逼,那就结合代码细细道来吧!

操作与结构

操作(operator)与结构(structure)其实是我的理解,本来想说 method 与 class,但后者含义过分狭隘,才又决定 operator 、structure 好。

在理解面向对象过程中,初学者很容易对实例属性实例方法迷糊。要是希望新手快速掌握,打比方的见效最快。我们可以说实例属性是对象拥有的事物,实例方法是对象执行的动作。付诸代码似下面这般:

class People(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def walk(self):
        """ 走 """
        ...

    def run(self):
        """ 跑 """
        ...

一个人的姓名与年纪是他拥有的事物,跑和走是他可执行的动作。

现在再来理解 把操作(operator)与结构(structure)分离,也就是将 walk、run 之类的动作与 People 隔离开,使得给 People 添加其他“动作”更容易。或许就有人说,那我直接在 People 中定义一个方法不就得了,从工程角度来说确实如此,甚至大多时候我们都这样做。如果我说这种做法违背了开闭原则,那我“书生气”太重,之所以要学习访问者模式,只为窥探大佬们的英姿。

策略模式

假设系统需要一个解析配置的功能,并且要支持 json、yaml、xml 格式的配置文件。如果选择策略模式,代码结构如下:


class Formatter(metaclass=ABCMeta):
    @abstractmethod
    def parse(self):
        pass

class JsonFormatter(Formatter):
    def parse(self, file):
        ...  # 处理 json 格式的配置文件

class YamlFormatter(Formatter):
    def parse(self, file):
        ...  # 处理 yaml 格式的配置文件

class XmlFormatter(Formatter):
    def parse(self, file):
        ...  # 处理 xml 格式的配置文件

class ConfigSource(object):
    def load(self, file):
        _, ext = os.path.splitext(file)
        if ext == ".json":
            formatter = JsonFormatter()
        elif ext == ".yaml":
            formatter = JsonFormatter()
        elif ext == ".xml":
            formatter = JsonFormatter()
        else:
            raise NotImplementedError
        return formatter.parse(file)

现在又要求保存配置文件,就是将内存里的数据还原到配置文件中,依然要支持上述的三种格式。常见的扩展功能的方式如下:

class Formatter(metaclass=ABCMeta):
    @abstractmethod
    def parse(self):
        pass

    @abstractmethod
    def unparse(self):
        pass

class JsonFormatter(Formatter):
    def parse(self, file):
        ...  # 处理 json 格式的配置文件

    def unparse(self, file):
        ...  # 处理 json 格式的配置文件

...  # 省略其他格式代码

class ConfigSource(object):
    def load(self, file):
        _, ext = os.path.splitext(file)
        ...
        return formatter.parse(file)  # 解析配置文件

    def dump(self, file):
        _, ext = os.path.splitext(file)
        if ext == ".json":
            formatter = JsonFormatter()
        ...
        return formatter.unparse(file)  # 反解析配置文件

可能你也能看得出来,只要多一个功能,就要多写一组方法,并在 ConfigSource 中实现一个分发任务的控制代码。但是,肉眼看得出 ConfigSource 里的方法多数代码重复,我们可以做小小的优化。

class ConfigSource(object):
    def operate(self, file, operator: str):
        _, ext = os.path.splitext(file)
        if ext == ".json":
            formatter = JsonFormatter()
        ...
        return hasattr(formatter, operator) and getattr(formatter, operator)(file)

# 使用
cs = ConfigSource()
cs.operate("xxx.json", "parse")
cs.operate("xxx.json", "unparse")

这样一来每次扩展功能只需要增加对应格式的功能代码,对扩展功能的程序员来说任务轻松些了。然而功能函数分散在各个类里(JsonFormatter、YamlFormatter、XmlFormatter),而每个类不一定在同一个文件,所以还是蛮麻烦的。

访问者模式

访问者模式就是把操作与结构分离。上述的代码中,我们把结构与操作绑定在一起(Formatter,Formatter.parse,Formatter.unparse),才会每次加功能就要改动结构(Formatter)。

现在我们重新设计,Formatter 还是结构,与文件格式有关。OperatorVisitor 是操作,也是访问者模式中的访问者,用来存放对文件的操作。譬如子类 ParseVisitor 中的方法都是用来解析配置文件;UnParseVisitor 中的方法都是用来反解析配置文件。

由于 Python 不支持函数重载,我们只能用不同的方法名(visitor.json,visitor.yaml,visitor.xml)区分不同格式的相同操作。(倘若你对这句话不解,后面我会用 c++ example 进行说明)。

class Formatter(metaclass=ABCMeta):
    def __init__(self, file):
        self.file = file

    @abstractmethod
    def accpet(self, visiort):
        pass

class JsonFormatter(Formatter):
    def accpet(self, visitor):
        return visitor.json(self)

class YamlFormatter(Formatter):
    def accpet(self, visitor):
        return visitor.yaml(self)

class XmlFormatter(Formatter):
    def accpet(self, visitor):
        return visitor.xml(self)
class OperatorVisitor(metaclass=ABCMeta):
    @abstractmethod
    def json(self, elem):
        pass

    @abstractmethod
    def yaml(self, elem):
        pass

    @abstractmethod
    def xml(self, elem):
        pass

class ParseVisitor(OperatorVisitor):  # 解析操作
    def json(self, elem):
        ...  # 解析逻辑

    def yaml(self, elem):
        ...  # 解析逻辑

    def xml(self, elem):
        ...  # 解析逻辑

class UnParseVisitor(OperatorVisitor):  # 反解析操作
    def json(self, elem):
        ...  # 反解析逻辑

    def yaml(self, elem):
        ...  # 反解析逻辑

    def xml(self, elem):
        ...  # 反解析逻辑

基于上述代码设计之后,我们再通过依赖注入,就能实现解析配置,或者反解析配置的调用。

class ConfigSource(object):
    def operte(self, file, operator: OperatorVisitor):
        _, ext = os.path.splitext(file)
        if ext == ".json":
            formatter = JsonFormatter(file)
        elif ext == ".json":
            formatter = YamlFormatter(file)
        ...

        return formatter.accpet(operator)

此后还有要增加的功能,只需要写一个访问者类继承 OperatorVisitor 即可,无需去修改每个 Fomatter。将相似的功能组织在一起,也就是我们常常在说的:高内聚,低耦合

总结

从上述示例中可以得到显而易见的结论:策略模式浅显易懂,但频繁增加不同功能的场景下会违反开闭原则;访问者模式可以更优雅的增减功能,却牺牲了代码的可读性。

另外,基于 python 代码可能看不出访问者模式的简洁(python 不支持函数重载),这里我再用 c++ 写一个示例,让你更好的理解访问者模式的优雅。

#include <iostream>
#include <string>

using namespace ::std;

class JsonFormatter;
class XmlFormatter;

// 操作抽象类
class OperatorVisitor
{
public:
    // 注意,函数名相同;函数签名不同
    virtual void visit(JsonFormatter *formatter) = 0;
    virtual void visit(XmlFormatter *formatter) = 0;
};

// 解析配置操作
class ParseVisitor : public OperatorVisitor
{
public:
    virtual void visit(JsonFormatter *formatter)
    {
        cout << "parse json config" << endl;
    }
    virtual void visit(XmlFormatter *formatter)
    {
        cout << "parse xml config" << endl;
    }
};

// 反解析配置操作
class UnParseVisitor : public OperatorVisitor
{
public:
    virtual void visit(JsonFormatter *formatter)
    {
        cout << "unparse json config" << endl;
    }
    virtual void visit(XmlFormatter *formatter)
    {
        cout << "unparse xml config" << endl;
    };
};

// 格式抽象类
class Formatter
{
protected:
    string file;

public:
    Formatter(string file) : file(file){};
    virtual void accept(OperatorVisitor *visitor) = 0;
};

// json 格式
class JsonFormatter : public Formatter
{
public:
    JsonFormatter(string file) : Formatter(file) {}
    virtual void accept(OperatorVisitor *visitor)
    {
        visitor->visit(this);
    }
};

// xml 格式
class XmlFormatter : public Formatter
{
public:
    XmlFormatter(string file) : Formatter(file) {}
    virtual void accept(OperatorVisitor *visitor)
    {
        visitor->visit(this);
    }
};

int main()
{
    // 使用
    Formatter *formatter = new JsonFormatter("xxx.json");
    // 操作 1
    OperatorVisitor *parse = new ParseVisitor();
    // 操作 2
    OperatorVisitor *unparse = new UnParseVisitor();
    // 执行操作 1
    formatter->accept(parse); // parse json config
    // 执行操作 2
    formatter->accept(unparse); // unparse json config
}

参考



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论