一起详细聊聊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# 多态

    关于“详解C#多态”的完整攻略,下面我会分为以下几个部分来逐步讲解。 什么是多态? 多态(Polymorphism)是面向对象编程的三大特性之一,指同一种行为具有多个不同的表现形式或状态的能力。在C#中,多态分为编译时多态和运行时多态。 编译时多态(静态多态):也叫重载,是指同一个类中的方法名称相同,但参数列表不同,也可以分为方法重载和运算符重载。 运行时多…

    C# 2023年6月1日
    00
  • 解决C# 截取当前程序窗口指定位置截图的实现方法

    要解决C#截取当前程序窗口指定位置截图的问题,我们可以使用以下方法进行实现。 方法一:使用Win32 API实现 1.引用System.Runtime.InteropServices命名空间。 2.定义下面的结构体和函数: [StructLayout(LayoutKind.Sequential)] public struct Rect { public in…

    C# 2023年6月3日
    00
  • C#中的图像Image类与打印Printing类用法

    C#中的图像Image类与打印Printing类用法攻略 概述 在C#中,Image类和Printing类都是常用的操作图像和打印的类,它们提供了丰富的方法和属性,可以方便地实现各种图像的处理和打印。 Image类:Image类是用于操作图像的类,可以将图像加载到内存中、进行绘制、剪切等操作。 Printing类:Printing类是用于打印的类,可以控制打…

    C# 2023年6月8日
    00
  • c#二维码生成的代码分享

    下面是关于“C#二维码生成的代码分享”的完整攻略: 目录 前言 准备工作 安装QRCoder 使用QRCoder生成二维码 示例说明 前言 随着移动应用和微信公众号的普及,二维码已经成为了一个不可或缺的元素。今天,我们就来学习一下如何使用C#来生成二维码。 准备工作 在开始之前,我们需要确保以下几个条件: 你已经安装了Visual Studio 2017或更…

    C# 2023年6月7日
    00
  • asp.net StreamReader 创建文件的实例代码

    首先我们来介绍一下如何使用 StreamReader 创建文件的实例。 StreamReader 是一个用于读取文本文件的类,它可以直接创建一个文件的实例,并对文件进行读取操作。在使用 StreamReader 创建文件的实例时,需要指定一个文件的路径,来表示要读取的文件的位置。在指定文件路径时,我们可以使用相对路径或绝对路径。相对路径是相对于当前程序运行的…

    C# 2023年6月3日
    00
  • 深入浅出掌握Unity ShaderLab语法基础

    请听我详细讲解“深入浅出掌握Unity ShaderLab语法基础”的完整攻略。 一、ShaderLab语法基础概述 ShaderLab是Unity中用于编写着色器的语言,它基于CG语言编写,同时又封装了一些常用的函数和数据结构,使得着色器开发变得容易而高效。在使用ShaderLab编写着色器时,需要定义一个合法的Shader程序,并且指定使用哪种渲染方式。…

    C# 2023年6月3日
    00
  • .NET使用.NET Core CLI开发应用程序

    .NET使用.NET Core CLI开发应用程序攻略 在本攻略中,我们将详细介绍如何使用.NET Core CLI开发应用程序。我们将会涵盖以下内容: 安装.NET Core SDK 创建.NET Core应用程序 编写代码 构建和运行应用程序 示例说明 1. 安装.NET Core SDK 在开始之前,您需要安装.NET Core SDK。您可以从官方网…

    C# 2023年5月16日
    00
  • Unity虚拟摇杆的实现方法

    Unity虚拟摇杆的实现方法 前言 虚拟摇杆作为移动端游戏中常用的操作方式之一,其实现方法也是比较简单的。本文主要介绍基于Unity的实现方法。 实现方法 实现虚拟摇杆的主要思路是通过输入获取到用户手指在屏幕上的滑动距离,并根据这个距离计算出摇杆的偏移量,实现游戏角色的移动操作。 具体实现步骤如下: 1. 创建虚拟摇杆预制体 在Unity中创建一个UIIma…

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