一起详细聊聊C#中的Visitor模式

当我们在开发C#程序时,或多或少遇到过对象结构和操作之间互相依赖的情况,比如需要对某一组对象进行相同的操作。而当我们需要添加一个新的操作时,又不希望去修改原本的对象结构,因为这样做很容易引入新的错误,势必会导致系统不稳定。这个时候,我们可以考虑使用访问者模式(Visitor Pattern)来解决这个问题。

什么是Visitor模式

在C#中,访问者模式是一种行为型设计模式,它允许你对不同类型的对象结构中的元素添加新的操作,而不用改变这些元素的类。

访问者模式定义了两个类:Visitor和Element。Visitor是所有访问者的基类。它声明了一些Visit方法,每个方法对应了Element的一个派生类。

在Visitor模式中,元素被声明为抽象的或者基于接口的,该元素包含接受操作的接口,这样所有的元素共享一个accept()方法。

当一个对象结构中的元素被访问时,访问者对象通过调用适当的访问者方法,去做与元素的操作,从而实现了两个对象之间松耦合。

为什么需要Visitor模式

我们来看一个例子:假设我们有一个对象结构,这个对象结构由三个类组成:Employee, Manager和CEO。这其中,Employee是一个基类,Manager和CEO派生于Employee。该对象结构定义好了,我们需要对其中每个对象进行操作。

首先,我们可以在每个Employee的派生类中定义一个方法,来执行员工相应的操作。但是这种情况下我们很难统一维护整个对象结构,因为我们每新增一个派生类的时候,都必须修改已有类,这样会对代码的可扩展性造成负面影响。另外,我们还容易因为修改已有类而引入新的错误。

另一种方式是:我们将操作逻辑提取到一个独立的类Visitor中,每个访问员工对象的方法都被转移到了访问者中。它不会对对象结构造成任何影响,使得在不修改已有代码的情况下,通过添加一个新的操作,我们可以轻易地扩展整个中对象结构。

接下来,我们将使用Visitor模式的实例来更好地说明Visitor模式的作用。

示例1:管理动态类型(动态调用)

来看一个实例,假设我们有一个代码库的API,我们想对代码库中的变量、类型和方法进行动态分析。这里,我们可以使用Reflection.Emit类库动态加载和编译C#脚本,并且使用动态绑定来获取类型和方法,以便我们能够调用代码库中尚未知道的类型和方法。我们需要分别对Type、Method以及Variable进行处理。让我们来看一下对于代码分析的实现。

定义一个抽象的元素Element类,包括以下代码:

abstract class Element
{
    public abstract void Accept(Visitor v);
}

每个Element类型都有一个Accept()方法,把自己作为参数传递给它,以便访问者能够进行相应的操作。

实现一个Type类,代码如下:

class TypeElement : Element
{
    public override void Accept(Visitor v)
    {
        v.Visit(this);
    }

    public string Name { get; set; }
}

Type同样有一个Accept()方法,将自己参数传递给访问者。

为了进一步细化我们的实现,为Method和Variable添加相应的方法。代码如下:

class MethodElement : Element
{
    public override void Accept(Visitor v)
    {
        v.Visit(this);
    }

    public string Name { get; set; }
}

class VarElement : Element
{
    public override void Accept(Visitor v)
    {
        v.Visit(this);
    }

    public string Name { get; set; }
}

现在,我们需要编写Visitor类,它包含访问Element和派生类的方法。代码如下:

class Visitor
{
    public void Visit(TypeElement t)
    {
        Console.WriteLine("Type: {0}", t.Name);
    }

    public void Visit(MethodElement m)
    {
        Console.WriteLine("Method: {0}", m.Name);
    }

    public void Visit(VarElement v)
    {
        Console.WriteLine("Variable: {0}", v.Name);
    }
}

现在,我们就可以使用这些类对代码库进行动态分析。假设我们有一个代码库,其中包含以下内容:

class Person
{
    public int Age { get; set; }

    public void DoSomething()
    {
    }
}

class Program
{
    static void Main(string[] args)
    {
        var myType = new TypeElement { Name = typeof(Person).FullName };
        var myMethod = new MethodElement { Name = typeof(Person).GetMethod("DoSomething").Name };
        var myVar = new VarElement { Name = "Age" };

        var v = new Visitor();

        myType.Accept(v);
        myMethod.Accept(v);
        myVar.Accept(v);
    }
}

我们通过反射获取了Person的Type、Method和Variable,然后依次将他们作为参数传递给访问者。Visitor就会去调用相应的Visit方法,并输出结果:

Type: ConsoleApp1.Person
Method: DoSomething
Variable: Age

示例2:使用Visitor转换XML

在这个例子中,我们将使用访问者模式来将XML文件转换为HTML字符串。XML文件将表示一个库存列表,其中可能包含以下几个节点:Products, Product, Name, Type和Price。

首先,我们定义一个抽象的Element类:

abstract class XmlElement
{
    public abstract void Accept(XmlVisitor v);
}

与前面不同的是,这里的Accept方法要传递一个XmlVisitor对象。然后我们为每个节点类型定义类。

class ProductsElement : XmlElement
{
    List<ProductElement> products = new List<ProductElement>();

    public void Add(ProductElement d)
    {
        products.Add(d);
    }

    public override void Accept(XmlVisitor v)
    {
        v.Visit(this);
    }

    public IEnumerable<ProductElement> GetProducts()
    {
        return products;
    }
}

class ProductElement : XmlElement
{
    public string Name { get; set; }
    public string Type { get; set; }
    public string Price { get; set; }

    public override void Accept(XmlVisitor v)
    {
        v.Visit(this);
    }
}

class NameElement : XmlElement
{
    public string Name { get; set; }

    public override void Accept(XmlVisitor v)
    {
        v.Visit(this);
    }
}

class TypeElement : XmlElement
{
    public string Type { get; set; }

    public override void Accept(XmlVisitor v)
    {
        v.Visit(this);
    }
}

class PriceElement : XmlElement
{
    public string Price { get; set; }

    public override void Accept(XmlVisitor v)
    {
        v.Visit(this);
    }
}

然后创建Visitor:

class XmlVisitor
{
    StringBuilder sb;

    public string Result { get { return sb.ToString(); } }

    public XmlVisitor()
    {
        sb = new StringBuilder();
    }

    public void Visit(ProductsElement p)
    {
        sb.AppendLine("<ul>");
        foreach (var d in p.GetProducts())
        {
            d.Accept(this);
        }
        sb.AppendLine("</ul>");
    }

    public void Visit(ProductElement p)
    {
        sb.AppendLine("<li>");
        sb.AppendLine($"<span>{p.Name}</span>");
        sb.AppendLine($"<span>{p.Type}</span>");
        sb.AppendLine($"<span>{p.Price}</span>");
        sb.AppendLine("</li>");
    }

    public void Visit(NameElement n)
    {
        sb.AppendLine($"<span>{n.Name}</span>");
    }

    public void Visit(TypeElement t)
    {
        sb.AppendLine($"<span>{t.Type}</span>");
    }

    public void Visit(PriceElement p)
    {
        sb.AppendLine($"<span>{p.Price}</span>");
    }
}

最后,我们创建一个XmlParser类,用于解析XML文件并生成对象结构:

class XmlParser
{
    public ProductsElement Parse(string xml)
    {
        var products = new ProductsElement();//伪代码
        return products;
    }
}

现在我们就可以像下面这样,正确解析XML并生成HTML了:

string xmlString = @"
    <products>
        <product>
            <name>Product 1</name>
            <type>Type 1</type>
            <price>10.00</price>
        </product>
        <product>
            <name>Product 2</name>
            <type>Type 2</type>
            <price>20.00</price>
        </product>
    </products>";

var parser = new XmlParser();
var products = parser.Parse(xmlString);
var htmlVisitor = new XmlVisitor();
products.Accept(htmlVisitor);
Console.WriteLine(htmlVisitor.Result);

输出结果如下:

<ul>
    <li>
        <span>Product 1</span>
        <span>Type 1</span>
        <span>10.00</span>
    </li>
    <li>
        <span>Product 2</span>
        <span>Type 2</span>
        <span>20.00</span>
    </li>
</ul>

总结

访问者模式将操作(即Visitor)从被操作对象中抽离开来,让其自成一体,并根据Element继承结构进行分配。使用访问者模式,我们可以通过派生新的类(具体的访问者),实现对元素结构的新增操作。然而,如果我们在要求解析每个新的XML时,每次都要添加一个新的类,这可能会导致代码的膨胀。

因此,对于一些真正的大型项目,我们可能还要考虑其它的结构化数据访问和序列化技术,例如XPath、XML DOM、JSON序列化等。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:一起详细聊聊C#中的Visitor模式 - Python技术站

(0)
上一篇 2023年6月7日
下一篇 2023年6月7日

相关文章

  • C#基于Socket套接字的网络通信封装

    C#基于Socket套接字的网络通信封装 本文将介绍如何使用C#基于Socket套接字的网络通信封装进行网络编程,包括创建Socket、连接服务器、传输数据等操作。 创建Socket 在C#中,使用Socket类来创建Socket。其中SocketType指定了Socket的类型(Stream、Dgram、Raw等),ProtocolType指定了使用的协议…

    C# 2023年6月6日
    00
  • C#开源类库SimpleTCP使用方法

    C#开源类库SimpleTCP使用方法 SimpleTCP是一款轻量级的C# TCP类库,主要用于帮助用户快速在C#应用程序中实现TCP通信。下面是SimpleTCP的使用方法: 概述 SimpleTCP可以用于开发TCP客户端和TCP服务端。作为客户端,它可以帮助你向远程TCP服务器发送数据并接收响应。作为服务端,它可以帮助你监听并处理来自客户端的请求。 …

    C# 2023年6月1日
    00
  • C# memcache 使用介绍

    C#memcache使用介绍 Memcache是一种基于内存的缓存服务,通过存储在内存中的数据来提升Web应用程序的性能。在C#中,可以通过使用开源的MongoDB.Driver.Net软件包来进行Memcache的使用。 1. 安装MongoDB.Driver.Net 在C#中使用Memcache需要先安装MongoDB.Driver.Net软件包,可以通…

    C# 2023年5月15日
    00
  • C#中ValueTuple的原理详解

    C#中ValueTuple的原理详解 什么是ValueTuple? ValueTuple是.NET Framework 4.7版本中的一种值类型,它可以让我们在不创建类和结构体的情况下定义带有名称的元组。元组是一组有序但不像数组和列表那样可扩展的项,每个项都可以是不同类型的数据。 这使得有时我们不需要定义一个类或结构体来存储多个值。 ValueTuple原理…

    C# 2023年5月31日
    00
  • C#调用CMD命令实例

    下面我将详细讲解”C#调用CMD命令实例”的完整攻略。 1. 背景介绍 在开发过程中,有时需要使用命令行来执行一些操作,比如编译、打包、部署等,这时我们可以使用C#来调用CMD命令,实现命令行操作的自动化。 2. 实现步骤 下面介绍C#调用CMD命令实现的步骤: 2.1 引入命名空间 在C#代码中,我们使用Process类来调用CMD命令,所以需要引入Sys…

    C# 2023年6月7日
    00
  • c#:CTS类型系统

    C#中的CTS类型系统(Common Type System)是一种规范,用于确保不同类型的语言在互相交互时能够进行正确的类型转换和操作。下面将分别从三个方面对CTS类型系统进行讲解。 CTS数据类型 C#的数据类型分为值类型和引用类型两类。值类型用于存储简单数据类型如数字、字符等,而引用类型则用于所有需要动态分配内存的复杂数据类型,如字符串、数组、类等。下…

    C# 2023年6月8日
    00
  • ASP.NET Core按用户等级授权的方法

    以下是关于“ASP.NET Core 按用户等级授权的方法”的完整攻略: 1. ASP.NET Core 授权 ASP.NET Core 授权是一种用于限用户访问应用程序中某些资源的机制。通过授权,我们限制用户访问某些页面、API 或其他资源,以保护应用的安全性和完整性。 2. ASP.NET Core 按等级授权 ASP.NET Core 按用户等级授权是…

    C# 2023年5月12日
    00
  • .Net设计模式之单例模式(Singleton)

    .Net设计模式之单例模式(Singleton) 什么是单例模式? 在软件系统中,有些类只需要存在唯一的一个实例对象,比如系统中的窗口管理器、文件系统、计时器等,这些对象在系统中只允许存在一个实例。单例模式就是为了满足这类需求而生的一种设计模式。 单例模式是指在整个应用程序中只能有一个实例对象的类。通常情况下,单例模式是指全局社区共享的一个唯一对象实例,比如…

    C# 2023年5月31日
    00
合作推广
合作推广
分享本页
返回顶部