一起详细聊聊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日

相关文章

  • 实现ASP.NET多文件上传程序代码

    实现ASP.NET多文件上传程序是一个常见的需求,可以通过以下步骤来实现: 页面设计 首先,在ASP.NET页面上添加文件上传控件,代码如下: <div> <asp:Label ID="lblUpload" runat="server" Text="Upload files:"&g…

    C# 2023年5月31日
    00
  • c# 获取计算机硬件信息的示例代码

    这里提供一份C#获取计算机硬件信息的示例代码,可以使用System.Management命名空间中的ManagementObject类来获取计算机硬件信息。 步骤1:添加命名空间 首先,在代码文件中添加以下命名空间: using System.Management; 这个命名空间提供了可以获取WMI(Windows Management Instrument…

    C# 2023年5月31日
    00
  • .NET使用Collections.Pooled提升性能优化的方法

    .NET使用Collections.Pooled提升性能优化的方法 简述 在进行 .NET 开发过程中,尤其在一些高并发、大量数据操作的场景下,很容易出现内存泄漏和性能问题。而使用 C# 中的 Collections.Pooled 可以有效地缓解此类问题,从而提高程序的性能。本文将详细介绍 Collections.Pooled 的使用方法及优化效果。 Col…

    C# 2023年6月3日
    00
  • C#字符串常见操作总结详解

    C#字符串常见操作总结详解 本文将为您详细介绍C#中关于字符串的常见操作,包括字符串的创建、比较、连接、替换、分割、转换等操作。 字符串的创建 在C#中,字符串可以通过以下方式创建: 字符串字面量 csharpstring str1 = “hello, world”; 使用关键字new创建字符串对象 csharpstring str2 = new strin…

    C# 2023年5月15日
    00
  • C/C++函数的调用约定的使用

    C/C++中的函数实现和调用都是基于特定的调用约定。调用约定定义了函数参数传递和返回值的方式,以确保不同模块间的函数调用操作的相互兼容性,是编译器与操作系统中必须共同遵循的一组规则。 常见的调用约定包括stdcall、cdecl、fastcall、thiscall和vectorcall。其中,stdcall和cdecl是最常用的调用约定。下面就以两条具体的例…

    C# 2023年6月7日
    00
  • C# 使用动态库DllImport(“kernel32”)读写ini文件的步骤

    C# 中使用动态库 DllImport 功能可以调用 Win32 API 库中的函数。其中,kernel32.dll 是 Windows 系统默认提供的 DLL 动态链接库,包含一些系统 API 函数。INI 文件是一种文本格式的配置文件,在 Windows 系统中使用广泛。 以下是 C# 使用动态库 DllImport 调用 kernel32.dll 中提…

    C# 2023年6月1日
    00
  • XAML如何获取元素的位置

    获取元素的位置是在开发XAML应用过程中很常见的需求,可以通过多种方式实现。下面是两种常见的方法: 1. 使用RenderTransformOrigin属性获取元素相对于父元素的位置 利用RenderTransformOrigin属性可以获取元素相对于父元素的位置。RenderTransformOrigin指定元素变形发生的中心点,而元素的位置在这个中心点附…

    C# 2023年6月6日
    00
  • C# Random类随机函数实例详解

    C# Random类随机函数实例详解 在C#编程中,经常需要使用到随机数,C#中提供了Random类,可以非常方便地生成伪随机数。本文将针对C# Random类进行详细讲解,并附上两个示例说明。 1. Random类概述 Random类可以生成一个伪随机数序列。 随机数是一些不可预测的数字,它们是通过算法生成的,而不是通过任何物理过程生成的。 随机类的构造函…

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