C#中的装饰器模式——3个版本

目录

介绍

组合与继承:如何选择?

经典装饰器模式——第1版

动态选择组件类型

装饰周期

胖经典装饰器的问题

泛型装饰器模式——第2版

动态选择组件类型的问题

动态装饰器模式——第3版

结论

参考


介绍

装饰器模式是一种引人入胜且非常流行的图案。该模式旨在动态地向现有对象添加附加功能。装饰器为扩展功能提供了子类化的替代方法。虽然可以通过对象类的子类化来为整个对象类添加功能,但装饰器模式旨在仅向单个对象添加功能,而使该类的其他对象保持不变。

有时有人评论说,通过在不修改原始类型的情况下增强现有类型,该模式遵循开闭原则。

非常有趣的是,该模式通过违背OO建议的错误方式来实现其目标。

组合与继承:如何选择?

OOA/OOD课程中的一个典型问题是,对于给定的类/对象A,在创建要重用类/对象A的新类/对象B时,如何以及基于什么标准在组合和继承之间进行选择?通常会给出建议:如果您打算重用现有类APublic接口,请使用继承。如果您打算仅重用现有类A的功能,请选择组合。

所以,装饰器模式正好相反。它计划重用现有类的Public接口,但使用组合。这个决定对Public接口扩展的问题有一些影响,我们稍后会看到。

经典装饰器模式——第1

经典装饰器是GoF书中提供的模式的一个版本,在文献中经常提到。通常,您会有一些接口IComponent抽象一些真实的类ComponentA。我们的愿望是用一些可以提供更多/增强功能的对象替换类ComponentA的对象,我们称之为DecoratorB,其实现相同接口IComponent的对象。我们希望它替换的内容:

IComponent comp= new ComponentA();

和:

IComponent comp= new DecoratorB(/*--some parameters--*/);

其中,DecoratorB以某种方式改进object ComponentA

在这种模式中,DecoratorB用于组合,将其添加为ComponentA组件,并从头开始重新实现IComponent接口,有时只是将ComponentA方法调用传递给方法。

所以,让我们假设我们有这个IComponent接口和ComponentA类实现:

public interface IComponent                   //(1)
{
    string StateI { get; set; }               //(2)
    string MethodI1(bool print = true);       //(3)
}

public class ComponentA : IComponent          //(4)
{
    public string StateI { get; set; }        //(5)
    public string StateA { get; set; }        //(6)

    public ComponentA()                       //(7)
    { }

    public string MethodI1(bool print = true) //(8)
    {
        string text = "ComponentA.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodA1()                  //(9)
    {
        string text = "ComponentA.MethodA1";
        Console.WriteLine(text);
        return text;
    }
}

如果您查看该IComponent接口,您会看到它公开了一个属性(2)和一个方法(3)

ComponentA是我们的核心组件/功能类。可以看到,它当然实现了IComponent (5), (8)public接口,但除此之外,在(6)(9)处还对类public接口做了一些补充。当通接口IComponent访问类时,我们当然受限于该接口公开的属性/方法,这意味着属性(6)和方法(9)将无法访问。

上述类的典型用法如下所示:

Console.WriteLine("IComponent================================");
IComponent I = new ComponentA();      //(20)
I.StateI = "123";
//I.StateA = "123";                   //not possible
string tmpI1 = I.MethodI1();          //(21)
//string tmpI2 = I.MethodA1();        //not possible  //(22)

Console.WriteLine("ComponentA================================");
ComponentA A = new ComponentA();      //(23)
A.StateI = "123";
A.StateA = "123";
string tmpA1 = A.MethodI1();
string tmpA2 = A.MethodA1();

现在让我们看看示例装饰器类的样子:

public class DecoratorB : IComponent             //(10)
{
    public string StateI { get; set; }           //(11)
    public string StateB { get; set; }           //(12)

    private IComponent component = null;         //(13)

    public DecoratorB(IComponent comp)           //(14)
    {
        component = comp;
    }

    public string MethodI1(bool print = true)    //(15)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorB.MethodI1";
        if (print) Console.WriteLine(text);
        return (text);
    }

    public string MethodB1()                     //(16)
    {
        string text = "DecoratorB.MethodB1";
        Console.WriteLine(text);
        return text;
    }
}

Decorator类中要注意的关键是传递IComponent的构造函数(14)如何分配给private属性(13)。这就是我们所说的组合,装饰器的所有魔力都由此而来。

Decorator(10)现在可以完全重用(13)中包含的对象的功能并公开其自己的接口,在该接口中可以自由地重用或修改对象(13)的行为。

(15)方法MethodI1中,我们看到DecoratorB如何提供自己的接口(10)实现,并且在该过程中,它重用MethodI1包含的对象(13)

除了它在(10)中继承的public接口之外,DecoratorB还可以添加自己的属性(12)和方法(16),从而为类添加一些新功能。

DecoratorB的典型用法如下所示:

DecoratorB B = new DecoratorB(new ComponentA()); //(24)
B.StateI = "123";
//B.StateA = "123";                              //not possible
B.StateB = "123";
string tmpB1 = B.MethodI1();                     //(25)
//string tmpB2 = B.MethodA1();                   //not possible //(26)
string tmpB3 = B.MethodB1();

看调用(25)。这就是装饰器模式的使用和强度的真正原因。那就是对类DecoratorBMethodI1调用,就是使用和增强对类ComponentAMethodI1调用。由于MethodI1是接口IComponent的一部分,即使结果对象(24)是通过对IComponent的引用访问的,该调用也将可用并且它将起作用。引用IComponent的消费者甚至不需要知道他使用的是原始ComponentA或装饰版本。我们为什么要使用这个装饰器模式的全部意义在于这个调用的力量。当然,这是教程级别的代码,在现实生活中会有更多的方法MethodI1MethodI2MethodI3,等等,都具有这种能力。

我们可以看到,在(26)中,无法从ComponentA访问未通过接口IComponent公开的方法。这是一个问题,因为我们可能有多个组件,比如ComponentAComponentA2ComponentA3等,并且每个组件都可能有自己特定的设置器来配置自己。发生这种情况的主要原因是,同样的,我们不继承自ComponentA,我们包含类ComponentA的对象,我们只能通过接口IComponent访问它。

这是装饰器模式的一个严重缺陷,因为ComponentA在准备好被DecoratorB重用之前可能需要一些配置/设置。它通常以两种方式绕过:

  1. ComponentA在传递到DecoratorB(24)之前配置
  2. ComponentA构造函数将有参数来配置自己

让我们再创建一个Decorator类:

public class DecoratorC : IComponent    //(40)
{
    public string StateI { get; set; }
    public string StateC { get; set; }  //(41)

    private IComponent component = null;

    public DecoratorC(IComponent comp)
    {
        component = comp;
    }

    public string MethodI1(bool print = true)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorC.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodC1()            //(42)
    {
        string text = "DecoratorC.MethodC1";
        Console.WriteLine(text);
        return text;
    }
}

如您所见,DecoratorC几乎与DecoratorB一样。 这是为了本文的目的而故意这样做的。

请注意,在(41)(42)DecoratorC中定义了一些特定于自身的public属性/方法。

这是现在DecoratorC的用法:

DecoratorC C = new DecoratorC(new DecoratorB(new ComponentA()));  //(27)
C.StateI = "123";
//C.StateA = "123";               //not possible
//C.StateB = "123";               //not possible
C.StateC = "123";
string tmpC1 = C.MethodI1();      //(28)
//string tmpC2 = C.MethodA1();    //not possible  //(29)
//string tmpC3 = C.MethodB1();    //not possible  //(30)
string tmpC4 = C.MethodC1();

您可以在(27)中看到如何将多个装饰器应用于一个组件。让我们立即说,从代码设计的角度来看,没有什么能阻止我们以不同的顺序应用装饰器,而不是 第一个是DecoratorB之后是DecoratorC,我们可以以不同的顺序应用它们。但是,生成的功能可能会有所不同,因此顺序是相关的。

看调用(28)。同样,这也是装饰器模式的使用和强度的真正原因。那是对类DecoratorCMethodI1调用,即使用和增强对类DecoratorBMethodI1调用,也就是使用和增强对类ComponentAMethodI1调用。同样,如果我们将(27)分配给对IComponent的引用,则引用IComponent的消费者甚至不需要知道他使用的是原始版本的ComponentA还是修饰版本。我们为什么要使用这种Decorator模式的全部意义在于这个调用的力量。再一次,在现实生活中,会有更多的方法MethodI1MethodI2, MethodI3等都具有这种能力。

您可以在(29)(30)中看到,访问ComponentADecoratorB的某些public方法是不可能的。如上所述,这是装饰器模式的严重缺陷。

下面是整个项目经典装饰器的类图和代码。

public interface IComponent  //(1)
{
    string StateI { get; set; }   //(2)
    string MethodI1(bool print = true);   //(3)
}

public class ComponentA : IComponent   //(4)
{
    public string StateI { get; set; }   //(5)
    public string StateA { get; set; }   //(6)

    public ComponentA()  //(7)
    { }

    public string MethodI1(bool print = true)   //(8)
    {
        string text = "ComponentA.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodA1()   //(9)
    {
        string text = "ComponentA.MethodA1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorB : IComponent   //(10)
{
    public string StateI { get; set; }   //(11)
    public string StateB { get; set; }   //(12)

    private IComponent component = null;    //(13)

    public DecoratorB(IComponent comp)   //(14)
    {
        component = comp;
    }

    public string MethodI1(bool print = true)  //(15)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorB.MethodI1";
        if (print) Console.WriteLine(text);
        return (text);
    }

    public string MethodB1()  //(16)
    {
        string text = "DecoratorB.MethodB1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorC : IComponent  //(40)
{
    public string StateI { get; set; }
    public string StateC { get; set; }  //(41)

    private IComponent component = null;

    public DecoratorC(IComponent comp)
    {
        component = comp;
    }

    public string MethodI1(bool print = true)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorC.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodC1()  //(42)
    {
        string text = "DecoratorC.MethodC1";
        Console.WriteLine(text);
        return text;
    }
}

class Client
{
    static void Main(string[] args)
    {
        Console.WriteLine("IComponent================================");
        IComponent I = new ComponentA();    //(20)
        I.StateI = "123";
        //I.StateA = "123";                 //not possible
        string tmpI1 = I.MethodI1();        //(21)
        //string tmpI2 = I.MethodA1();      //not possible  //(22)

        Console.WriteLine("ComponentA================================");
        ComponentA A = new ComponentA();    //(23)
        A.StateI = "123";
        A.StateA = "123";
        string tmpA1 = A.MethodI1();
        string tmpA2 = A.MethodA1();

        Console.WriteLine("DecoratorB================================");
        DecoratorB B = new DecoratorB(new ComponentA()); //(24)
        B.StateI = "123";
        //B.StateA = "123";                 //not possible
        B.StateB = "123";
        string tmpB1 = B.MethodI1();        //(25)
        //string tmpB2 = B.MethodA1();      //not possible //(26)
        string tmpB3 = B.MethodB1();

        Console.WriteLine("DecoratorC================================");
        DecoratorC C = new DecoratorC(new DecoratorB(new ComponentA()));  //(27)
        C.StateI = "123";
        //C.StateA = "123";                //not possible
        //C.StateB = "123";                //not possible
        C.StateC = "123";
        string tmpC1 = C.MethodI1();       //(28)
        //string tmpC2 = C.MethodA1();     //not possible  //(29)
        //string tmpC3 = C.MethodB1();     //not possible  //(30)
        string tmpC4 = C.MethodC1();

        Console.WriteLine("Collection================================");
        List<IComponent> list = new List<IComponent>();
        list.Add(new ComponentA());
        list.Add(new DecoratorB(new ComponentA()));
        list.Add(new DecoratorC(new DecoratorB(new ComponentA())));

        foreach (IComponent iComp in list)
        {
            iComp.StateI = "123";
            string tmpII1 = iComp.MethodI1();
        }

        Console.ReadLine();
    }
}

以下是示例执行的结果:

动态选择组件类型

装饰器模式使成为可能的是在运行时动态选择将使用哪种类型的组件应用程序。让我们看一下这段代码:

ComponentA compA = new ComponentA();
compA.StateA = "123"; // some configuring

IComponent comp = null;
int selection = GetSelectionFromGui();

switch (selection)
{
    case 1:
        comp = new DecoratorB(compA);
        break;
    case 2:
        comp = new DecoratorC(compA);
        break;
    default:
        comp = compA;
        break;
}

string result = comp.MethodI1();

您可以看到,用户输入将在运行时决定组件类型。

装饰周期

代码中没有任何东西阻止我们多次应用同一个装饰器。例如,看代码:

IComponent cc = new DecoratorB(new DecoratorC(new DecoratorB(new ComponentA())));

在文献[1]中,您可以找到讨论如何检测装饰器循环并可能禁止它们的文章。我们不会在这里讨论这个。

胖经典装饰器的问题

我们说过装饰器模式是关于可组合性的,即重用的类不是继承而是组合成装饰器类。这样做的一个副作用是装饰器不会继承重用类的public接口,而是需要显式地实现每个需要公开重用/公开的方法。如果数量更大,创建和维护所有这些方法需要付出一些努力。

让我们看看类图在胖经典观察者的情况下是什么样子的,它有五个 public方法可以公开。

可以看出,解决方案中的方法数量显着增长。大多数方法可能只是传递对所包含对象的调用,正如在DecoratorB的这个示例实现中所看到的那样。

public class DecoratorB : IComponent
{
    public string StateI { get; set; }
    public string StateB { get; set; }

    private IComponent component = null;

    public DecoratorB(IComponent comp)
    {
        component = comp;
    }

    public string MethodI1(bool print = true)
    {
        return component.MethodI1();
    }

    public string MethodI2(bool print = true)
    {
        return component.MethodI2();
    }

    public string MethodI3(bool print = true)
    {
        return component.MethodI3();
    }

    public string MethodI4(bool print = true)
    {
        return component.MethodI4();
    }

    public string MethodI5(bool print = true)
    {
        return component.MethodI5();
    }

    public string MethodB1()
    {
        string text = "DecoratorB.MethodB1";
        Console.WriteLine(text);
        return text;
    }
}

更现实的是,在实践中,每个类将有20多个方法需要重用/公开。有一些像ReSharper这样的开发人员工具可以帮助开发人员自动创建和维护所有这些方法,但责任仍然在于开发人员/实施者。

泛型装饰器模式——第2

泛型装饰器模式版本可以在最近的文献中找到。装饰器基于可组合性的思想保持不变,只是传递类型信息的方法不同。从模式的目的和主要设计思想来看,很多东西都是相似的。我从[2]中得到了这个版本的装饰器模式和核心代码的想法。

主要技巧是通过泛型参数类型传递类/类型信息。包含的组件对象的创建被传递给类型参数类的默认构造函数。因此,与我们之前的代码类似的代码示例现在看起来像这样:

IComponent C = new DecoratorC<DecoratorB<ComponentA>>();

由于使用方面的许多事情与以前的版本相似,我们将立即进入类图和代码:

public interface IComponent
{
    string StateI { get; set; }
    string MethodI1(bool print = true);
}

public class ComponentA : IComponent
{
    public string StateI { get; set; }
    public string StateA { get; set; }

    public ComponentA()
    { }

    public string MethodI1(bool print = true)
    {
        string text = "ComponentA.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodA1()
    {
        string text = "ComponentA.MethodA1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorB<T> : IComponent  //(50)
        where T : IComponent, new()
{
    public string StateI { get; set; }
    public string StateB { get; set; }

    private T component = new T();   //(51)

    public DecoratorB()
    {
    }

    public string MethodI1(bool print = true)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorB.MethodI1";
        if (print) Console.WriteLine(text);
        return (text);
    }

    public string MethodB1()
    {
        string text = "DecoratorB.MethodB1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorC<T> : IComponent
        where T : IComponent, new()
{
    public string StateI { get; set; }
    public string StateC { get; set; }

    private T component = new T();

    public DecoratorC()
    {
    }

    public string MethodI1(bool print = true)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorC.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodC1()
    {
        string text = "DecoratorC.MethodC1";
        Console.WriteLine(text);
        return text;
    }
}

public class Client
{
    static void Main(string[] args)
    {
        Console.WriteLine("ComponentA================================");
        ComponentA A = new ComponentA();
        A.StateI = "123";
        A.StateA = "123";
        string tmpA1 = A.MethodI1();
        string tmpA2 = A.MethodA1();

        Console.WriteLine("DecoratorB================================");
        DecoratorB<ComponentA> B = new DecoratorB<ComponentA>();
        B.StateI = "123";
        //B.StateA = "123";                //not possible
        B.StateB = "123";
        string tmpB1 = B.MethodI1();
        //string tmpB2 = B.MethodA1();     //not possible
        string tmpB3 = B.MethodB1();

        Console.WriteLine("DecoratorC================================");
        DecoratorC<DecoratorB<ComponentA>> C = new DecoratorC<DecoratorB<ComponentA>>();
        C.StateI = "123";
        //B.StateA = "123";                //not possible
        //C.StateB = "123";                //not possible
        C.StateC = "123";
        string tmpC1 = C.MethodI1();
        //string tmpC2 = C.MethodA1();     //not possible
        //string tmpC3 = C.MethodB1();     //not possible
        string tmpC4 = C.MethodC1();

        Console.WriteLine("Collection================================");
        List<IComponent> list = new List<IComponent>();
        list.Add(new ComponentA());
        list.Add(new DecoratorB<ComponentA>());
        list.Add(new DecoratorC<DecoratorB<ComponentA>>());

        foreach (IComponent comp in list)
        {
            comp.StateI = "123";
            comp.MethodI1();
        }

        Console.ReadLine();
    }
}

这是执行的结果:

需要注意的主要新事物是Decorator类在(50)中是如何定义的,作为一个泛型类,其类型参数表示重用(装饰)类/对象。还要注意(51)中如何创建需要具有默认构造函数的组件。

其余代码,尤其是装饰器的用法是相同的。

请注意,在类图(和代码)中,我们没有触及或更改IComponentComponentA类,而只是DecoratorBDecoratorC类。

这个版本的装饰器模式并没有带来太多新的东西。它甚至是限制性的,因为它严格依赖于装饰(重用)类的默认构造函数。在学术上,使用泛型展示装饰器版本很有吸引力,但实际上并没有获得新的/更好的功能。

动态选择组件类型的问题

泛型装饰器存在类类型在编译时静态解析的问题。因此,创建类似于之前为经典装饰器显示的代码会产生问题,该代码将在运行时选择组件的类型。可能有一个大的switch声明,列举静态不同的可能组合,但它不像经典装饰器那样自然和容易。

动态装饰器模式——第3

动态装饰器模式利用动态对象和反射的C#技术来实现比经典装饰器更多的功能。此版本由C#技术专门启用,可能无法在某些不支持类似技术的其他OO语言中实现。

我不知道是否有人独立发布过这样的版本,但是当我阅读有关动态代理的内容时,我想到了它。我想到类似的技术可以用来克服经典装饰器模式的局限性。

关键思想是拦截对未知方法的调用,并将调用重定向到包含的对象进行处理。然后,如果包含的对象无法解析方法调用,则会递归地将方法调用传递给其包含的对象,等等。我们要强调的是,这里的主要技巧是传递方法调用满足层次结构,而不是继承等级制度。

此处显示的代码是演示概念级别的质量,对于生产中的使用可能需要一些改进。

让我们先展示类图和代码,然后我们将讨论:

public interface IComponent
{
    string StateI { get; set; }
    string MethodI1(bool print = true);
}

public class ComponentA : IComponent
{
    public string StateI { get; set; }
    public string StateA { get; set; }

    public ComponentA()
    { }

    public string MethodI1(bool print = true)
    {
        string text = "ComponentA.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodA1()
    {
        string text = "ComponentA.MethodA1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorB : DynamicObject, IComponent          //(60)
{
    public string StateI { get; set; }
    public string StateB { get; set; }

    private dynamic component;

    public DecoratorB(IComponent comp)
    {
        component = comp;
    }

    public override bool TryInvokeMember(                    //(61)
       InvokeMemberBinder binder, object[] args, out object result)
    {
        try
        {
            result = null;
            MethodInfo mInfo =
                component.GetType().GetMethod(binder.Name);  //(62)

            if (mInfo != null)
            {
                result = mInfo.Invoke(component, args);      //(63)
            }
            else
            {
                if (component is DynamicObject)
                {
                    component.TryInvokeMember(               //(64)
                        binder, args, out result);
                }
            }
            return true;
        }
        catch
        {
            result = null;
            return false;
        }
    }

    public override bool TrySetMember(   //(65)
                SetMemberBinder binder, object value)
    {
        try
        {
            PropertyInfo prop = component.GetType().GetProperty(
                binder.Name, BindingFlags.Public | BindingFlags.Instance);

            if (prop != null && prop.CanWrite)
            {
                prop.SetValue(component, value, null);
            }
            else
            {
                if (component is DynamicObject)
                {
                    component.TrySetMember(binder, value);
                }
            }
            return true;
        }
        catch
        {
            return false;
        }
    }

    public override bool TryGetMember(  //(66)
                GetMemberBinder binder, out object result)
    {
        try
        {
            result = null;
            PropertyInfo prop = component.GetType().GetProperty(
                binder.Name, BindingFlags.Public | BindingFlags.Instance);

            if (prop != null && prop.CanWrite)
            {
                result = prop.GetValue(component, null);
            }
            else
            {
                if (component is DynamicObject)
                {
                    component.TryGetMember(binder, out result);
                }
            }
            return true;
        }
        catch
        {
            result = null;
            return false;
        }
    }

    public string MethodI1(bool print = true)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorB.MethodI1";
        if (print) Console.WriteLine(text);
        return (text);
    }

    public string MethodB1()
    {
        string text = "DecoratorB.MethodB1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorC : DynamicObject, IComponent
{
    //similar to code DecoratorB
    //removed for brevity
}

class Client
{
    static void Main(string[] args)
    {
        Console.WriteLine("ComponentA================================");
        ComponentA A = new ComponentA();
        A.StateI = "III";
        Console.WriteLine(A.StateI);
        A.StateA = "AAA";      // not possible in Classic Decorator
        Console.WriteLine(A.StateA);
        string tmpA1 = A.MethodI1();
        string tmpA2 = A.MethodA1();

        Console.WriteLine("DecoratorB================================");
        dynamic B = new DecoratorB(new ComponentA());
        B.StateI = "III";
        Console.WriteLine(B.StateI);
        B.StateA = "AAA";      // not possible in Classic Decorator
        Console.WriteLine(B.StateA);
        B.StateB = "BBB";
        Console.WriteLine(B.StateB);
        B.StateXXX = "XXX";    // property does not exist, but no exception
        string tmpB1 = B.MethodI1();
        string tmpB2 = B.MethodA1();
        string tmpB3 = B.MethodB1();
        B.MethodXXX();        // method does not exist, but no exception

        Console.WriteLine("DecoratorC================================");
        dynamic C = new DecoratorC(new DecoratorB(new ComponentA())); //(70)
        C.StateI = "III";
        C.StateA = "AAA";     // not possible in Classic Decorator
        Console.WriteLine(C.StateA);
        C.StateB = "BBB";     // not possible in Classic Decorator
        Console.WriteLine(C.StateB);
        C.StateC = "CCC";
        Console.WriteLine(C.StateC);
        C.StateXXX = "XXX";   // property does not exist, but no exception
        string tmpC1 = C.MethodI1();
        string tmpC2 = C.MethodA1();    //(71)
        string tmpC3 = C.MethodB1();    //(72)
        string tmpC4 = C.MethodC1();
        C.MethodXXX();        // method does not exist, but no exception //(73)

        Console.WriteLine("Collection================================");
        List<IComponent> list = new List<IComponent>();
        list.Add(new ComponentA());
        list.Add(new DecoratorB(new ComponentA()));
        list.Add(new DecoratorC(new DecoratorB(new ComponentA())));

        foreach (IComponent iComp in list)
        {
            iComp.StateI = "III";
            Console.WriteLine(iComp.StateI);
            string tmpI1 = iComp.MethodI1();
        }

        Console.ReadLine();
    }
}

以下是示例执行的结果:

首先,注意在类图和代码类 IComponentComponentA中没有改变。只有装饰器的代码发生了变化。这表明该模式可以在需要时替代经典装饰器模式,而无需更改组件代码。

让我们看看DecoratorB(60)DecoratorB现在继承自DynamicObject,这给我们带来了创建像(61)这样的方法的魔力。在(67)中,我们将组件保存/包含为动态对象本身,因此我们可以在其上调用(64)中需要的任何方法。

我们将只讨论方法(61),因为在(65)(66)中适用类似的逻辑。当在DecoratorB中找不到合适的方法名称时调用方法(61) 。在(62)中,借助反射魔法,我们想看看我们的组件是否有这样的名称的方法,如果有,我们在(63)中调用它。如果不是,那么我们看看我们的组件本身是否是 DynamicObject,如果是,我们将调用传递给它的组件(64)

这是一个聪明的设计,递归地将方法调用传递给包含的组件。我把它留给读者研究一下。

实际结果见(70)。以前不可能的调用现在可以工作了。特别是,在(71)(72)中,我们调用了包含层次结构,这在经典装饰器中是不可能的。有趣的是(73)对不存在的方法的调用被简单地忽略了。

动态装饰器模式是经典装饰器设计的强大替代方案。但当然,动态对象的使用会给应用程序带来性能成本。

结论

装饰器模式是一种非常流行和重要的模式。它经常用于文件和IO流。顺便提一下,C#自己的IO流库是围绕装饰器模式构建的。

首先,我们查看了经典装饰器模式,该模式在文献和GoF书中都有描述。我们讨论了它存在的问题,特别是需要手动实现通过包含层次结构传递功能所需的所有方法。然后我们查看了泛型装饰器模式版本,它在学术上很有趣,但并没有带来太多新的东西。

最后,我们查看了动态装饰器模式,它是该模式最有效的版本。它是由c#特性DynamicObject实现的,并解决了经典装饰器模式存在的许多问题。

参考

https://www.codeproject.com/Articles/5328817/Decorator-Pattern-in-Csharp-3-Versions

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
原型模式是一种创建型设计模式,其提供了一种复制已有对象的方法来生成新对象的能力,而不必通过实例化的方式来创建对象。原型模式是通过克隆(浅复制或深复制)已有对象来创建新对象的,从而可以避免对象创建时的复杂过程。 在C#,可以通过实现ICloneable接口来实现原型模式。ICloneable接口定义了Clone方法,该方法用于复制当前对象并返回一个新对象。需要注意的是,Clone方法返回的是Object类型,需要进行强制类型转换才能得到复制后的对象。 以下是一个简单的示例代码: ```csharp public class Person : ICloneable { public string Name { get; set; } public int Age { get; set; } public object Clone() { return MemberwiseClone(); } } // 使用示例 var person1 = new Person { Name = "Tom", Age = 20 }; var person2 = (Person)person1.Clone(); person2.Name = "Jerry"; Console.WriteLine(person1.Name); // 输出 "Tom" Console.WriteLine(person2.Name); // 输出 "Jerry" ``` 在上面的示例代码,实现了一个Person类,并实现了ICloneable接口的Clone方法来实现原型模式。复制对象时,使用MemberwiseClone方法进行浅复制,即只复制值类型的字段和引用类型字段的引用,而不复制引用类型字段所引用的对象。在使用示例,首先创建一个Person对象person1,然后通过Clone方法复制一个新的对象person2,修改person2的Name属性后,输出person1和person2的Name属性,可以看到person1的Name属性并没有改变,说明person2是一个全新的对象。 需要注意的是,如果要实现深复制,即复制引用类型字段所引用的对象,需要在Clone方法手动将引用类型字段复制一份。另外,使用原型模式时,需要注意复制后的对象和原对象之间的关系,如果复制后的对象修改了原对象的状态,可能会对系统产生意想不到的影响。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值