面向对象编程中的装饰器模式

时间:2018-10-31 16:31:47

标签: c# design-patterns

我正在研究“装饰图案”。用C#做一些测试,我不明白为什么我没有得到预期的结果。这是代码:

public abstract class Drink
{
    public string description = "Generic drink";

    public string GetDescription()
    {
        return description;
    }
}

public abstract class DrinkDecorator : Drink
{
    public abstract string GetDescription();
}


public class SecretIngredient : DrinkDecorator
{
    Drink drink;

    public SecretIngredient (Drink drink)
    {
        this.drink = drink;
    }

    public override string GetDescription()
    {
        return this.drink.GetDescription() + ", SecretIngredient ";
    }
}

public class Espresso : Drink
{
    public Espresso()
    {
        description = "Espresso";
    }
}


[TestFixture]
class TestClass
{
    [Test]
    public void TestMethod()
    {
        Drink drink = new Espresso();
        System.Diagnostics.Debug.WriteLine(drink.GetDescription());

        drink = new SecretIngredient (drink);
        System.Diagnostics.Debug.WriteLine(drink.GetDescription());
    }
}

执行我得到的测试:

  

浓咖啡

     

普通饮料

虽然我期望如此:

  

浓咖啡

     

浓咖啡,秘密成分

为什么?预先感谢。

2 个答案:

答案 0 :(得分:2)

基本问题是您没有正确实现装饰器模式。装饰器模式的正确实现如下(我们将在使用它时修复更多的东西)。

public abstract class Drink
{
  // Fields should be private or protected, but the 
  // description field that was here is useless, and
  // even if it were here, it should be a constant, 
  // not a variable
  // Eliminate it.
  // public string description = "Generic drink";

  // Things that are logically properties should be
  // properties, not GetBlah methods.

  // In new versions of C# you can use compact syntax 
  // for properties.

  // In the decorator pattern the behaviour mutated by the
  // decorator should be virtual.

  public virtual string Description => "generic drink";
}

public abstract class DrinkDecorator : Drink
{
  // The decorator must override the underlying implementation.
  public abstract override string Description { get; }
}

public class SecretIngredient : DrinkDecorator
{
    Drink drink;
    public SecretIngredient (Drink drink)
    {
        this.drink = drink;
    }

    // Use interpolation.
    public override string Description =>
      $"{this.drink.Description}, SecretIngredient ";
}

public class Espresso : Drink
{
    public Espresso()
    {
       // This is just wrong. We have a mechanism for overriding
       // behaviour so **use it**.
       //   description = "Espresso";
    }
    public override string Description => "Espresso";
}


[TestFixture]
class TestClass
{
    [Test]
    public void TestMethod()
    {
        Drink drink = new Espresso();
        System.Diagnostics.Debug.WriteLine(drink.Description);
        drink = new SecretIngredient (drink);
        System.Diagnostics.Debug.WriteLine(drink.Description);
    }
}

现在正确的实现具有预期的输出。

您的错误实现得到错误输出的原因是因为它是错误的。您有{strong>的两个完全独立的实现,它们彼此无关,一个由GetDescription实现,另一个由装饰器实现,因此调用哪个依赖于接收器的编译时类型,为Drink

您应该得到一个警告,指出您可能无意中隐藏了旧方法并用新方法隐藏了它。 请注意这些警告。如果收到警告说“此方法可能是错误的”,然后在调用该方法时得到错误的结果,则警告是正确的

答案 1 :(得分:1)

这是因为您已将Drink声明为Drink类型。

在阅读我的解释之前;如果对您的代码执行此操作,它将起作用,我尝试在下面解释原因:

System.Diagnostics.Debug.WriteLine(((SecretIngredient)drink).GetDescription());

当您将Type分配给引用时,则此Type是元数据的备用。换句话说,Type拥有(或继承)了任何字段,方法,属性。以上没有什么。

在这里,我们有一个简单的Person,它也是Employee的基础。查看输出并遵循类型声明。

using System;
namespace Question_Answer_Console_App
{
    class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person() { Name = "Mathew" };
            Person employeePerson = new Employee() { Name = "Mark" };
            Person castedEmployee = new Employee() { Name = "Luke" };
            Employee employee = new Employee() { Name = "John" };
            //Compile error -> Employee personEmployee = new Person() { Name = "Acts" };    

            Console.WriteLine(person.Name);
            Console.WriteLine(employeePerson.Name); //Referenced Employee but got Person
            Console.WriteLine(((Employee)castedEmployee).Name); //Notice we cast here
            Console.WriteLine(employee.Name);

            Console.ReadKey();
        }
    }

    public class Person
    {
        public string Name { get; set; } = "No Name";
    }

    public class Employee : Person
    {
        new public string Name { get; set; }
        public string Address { get; set; }
    }
    //Output
    //Mathew
    //No Name
    //Luke
    //John
}

好吧,因此,如果您能够理解并理解如何使用Type元数据,那么现在您需要使用interface来进行研究。是同一回事,但我们可能会尴尬。

interface中,两个接口可能具有相同的属性或方法,甚至具有属性和方法名称,但逻辑不同。当Type使用多个interface并且它们共享任何一个,但是所需的逻辑不同时,我们需要显式声明该接口成员。然而;当我们这样做时,只有在Type如此引用时,我们才可以使用这些成员。看看这个类似的例子:

首先请注意,现在打印'Luke'(以前是Mark,但逻辑相同)...为什么在引用Person时却实例化为Employee。在此之前行不通。 还请注意,尽管已定义该成员,但输出中仍有一个孔;但是,在这种情况下,我们指的是IEmployee。玩一会儿所有这些代码,直到它陷入,因为以后可能会成为一个大问题。

using System;
namespace Question_Answer_Console_App
{
    class Program
    {
        static void Main(string[] args)
        {
            IPerson iPerson = new Person() { Name = "Mathew" };
            Person person = new Person() { Name = "Mark" };
            Person employeePerson = new Employee() { Name = "Luke" }; //pay attention to this!!
            IPerson iEmployeePerson = new Employee() { Name = "John" };
            IEmployee iEmployee = new Employee() { Name = "Acts" }; //And pay attention to this!!            
            Employee employee = new Employee() { Name = "Romans" };

            Console.WriteLine(iPerson.Name);
            Console.WriteLine(person.Name);
            Console.WriteLine(employeePerson.Name);
            Console.WriteLine(iEmployeePerson.Name);
            Console.WriteLine(iEmployee.Name);
            iEmployee.Name = "Corinthians"; //And pay attention to this!!
            Console.WriteLine(iEmployee.Name);
            Console.WriteLine(employee.Name);

            Console.ReadKey();
        }
    }

    public interface IPerson
    {
        string Name { get; set; }
    }

    public interface IEmployee
    {
        string Name { get; set; }
    }

    public class Person : IPerson
    {
        public string Name { get; set; } = "No Name";
    }

    public class Employee : Person, IEmployee
    {
        public string Address { get; set; }
        string IEmployee.Name { get; set; } //And pay attention to this!! (Explicit interface declaration)
    }
    //Output
    //Mathew
    //Mark
    //Luke
    //John

    //Corinthians
    //Romans
}

现在;如果到目前为止,您仍然可以了解解决问题的方法。举第一个示例:如果将virtual添加到Person的Name属性中,然后在override的Name属性中使用Employee,您将看到类型现在可以用作预期。这是因为我们没有引用两种不同的方法。我们标记一种具有重新引用(虚拟)的能力,另一种具有引用它(覆盖)的能力。这极大地改变了行为。

所有这些说和理解的东西,然后让我们做一个合适的装饰器。

首先;我们需要输入一个类型:

public class Person
{
    public virtual string Name { get; set; } = "John Doe";
}

现在,我们需要具有扩展功能的类型...(稍后需要对此进行更改)

public class Employee : Person
{
    public override string Name => $"Employee, {base.Name}";
    public string Job { get; set; }
}

public class Customer : Person
{
    public override string Name => $"Customer, {base.Name}";
    public bool IsShopping { get; set; }
}

现在,员工也有可能成为客户。基于我们当前的设计,我们有一个问题...我们应该添加接口,但是计算又如何呢?在此示例中,除了Name(名称)之外,没有任何其他内容,Name(名称)不是真实世界,但确实可以实现。因此,为了允许Person动态更新,我们应该添加一个PersonDecorator。添加此装饰器时,我们需要从其继承并使用其他类型进行实例化。

这是我们的装饰器:

public abstract class PersonDecorator : Person
{
    protected Person Person { get; }
    public PersonDecorator(Person person) => Person = person;
    public override string Name => Person.Name;
}

现在,我们可以动态扩展Person到以前无法实现的地方。升级EmployeeCustomer显示了如何执行此操作:

public class Employee : PersonDecorator
{
    public Employee(Person person = null) : base(person ?? new Person()) { }
    public override string Name => $"Employee, {base.Name}";
    public string Job { get; set; }
}

public class Customer : PersonDecorator
{
    public Customer(Person person) : base(person ?? new Person()) { }
    public override string Name => $"Customer, {base.Name}";
    public bool IsShopping { get; set; }
}

现在,我们已经升级了类型以使用装饰器(请注意,它已经回退到可能没有的类型)。让我们在一个小例子中使用它:

static void Main(string[] args)
{
    Person person = new Person() { Name = "Mathew" };
    Console.WriteLine(person.Name);

    person = new Employee(person) { Job = "Stocker" };
    Console.WriteLine(person.Name);

    person = new Customer(person) { IsShopping = true };
    Console.WriteLine(person.Name); 

    Console.ReadKey();
}
//OUTPUTS
//Mathew
//Employee, Mathew
//Customer, Employee, Mathew

请注意,我们现在如何动态扩展Person

我们还可以像这样使人充满活力:

static void Main(string[] args)
{
    Person person = new Customer(new Employee(new Person(){ Name = "Mathew" }){ Job = "Stocker" }){ IsShopping = true };
    Console.WriteLine(person.Name);
    Console.ReadKey();
}
//OUTPUTS
//Customer, Employee, Mathew

看看在没有首先实现基础的情况下它是如何工作的;它始终保持动态和真实。

static void Main(string[] args)
{
    //Person person = new Person() { Name = "Mathew" };
    //Console.WriteLine(person.Name);

    Person person = new Employee() { Job = "Stocker" };
    Console.WriteLine(person.Name);

    person = new Customer(person) { IsShopping = true }
    Console.WriteLine(person.Name); 

    Console.ReadKey();
}
//OUTPUTS
//Employee, John Doe
//Customer, Employee, John Doe

完整的代码供您参考装饰器模式

using System;
namespace Question_Answer_Console_App
{
    class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person() { Name = "Mathew" };
            Console.WriteLine(person.Name);

            person = new Employee(person) { Job = "Stocker" };
            Console.WriteLine(person.Name);

            person = new Customer(person) { IsShopping = true };            
            Console.WriteLine(person.Name);

            Console.ReadKey();
        }
        //OUTPUTS
        //Mathew
        //Employee, Mathew
        //Customer, Employee, Mathew
    }

    public class Person
    {
        public virtual string Name { get; set; } = "John Doe";
    }

    public abstract class PersonDecorator : Person
    {
        protected Person Person { get; }
        public PersonDecorator(Person person) => Person = person;
        public override string Name => Person.Name;
    }

    public class Employee : PersonDecorator
    {
        public Employee(Person person = null) : base(person ?? new Person()) { }
        public override string Name => $"Employee, {base.Name}";
        public string Job { get; set; }
    }

    public class Customer : PersonDecorator
    {
        public Customer(Person person) : base(person ?? new Person()) { }
        public override string Name => $"Customer, {base.Name}";
        public bool IsShopping { get; set; }
    }
}

这是将您的代码更新为装饰器模式的代码。请注意,如何通过将其Drink添加到装饰器来动态更新Expresso

using System;
namespace Question_Answer_Console_App
{
    class Program
    {
        static void Main(string[] args)
        {
            Drink drink = new Espresso() { Description = "Expresso" };
            Console.WriteLine(drink.Description);

            drink = new SecretIngredient(drink);
            Console.WriteLine(drink.Description);

            drink = new Ice(drink);
            Console.WriteLine(drink.Description);

            Console.ReadKey();
        }
        //OUTPUTS
        //Expresso
        //Expresso with SecretIngredient
        //Expresso with SecretIngredient with Ice
    }

    public class Drink
    {
        public virtual string Description { get; set; }
    }

    public class Espresso : Drink { }

    public abstract class DrinkDecorator : Drink
    {
        protected Drink drink;
        protected DrinkDecorator(Drink drink) => this.drink = drink;
        public override string Description => drink.Description;
    }

    public class SecretIngredient : DrinkDecorator
    {
        public SecretIngredient(Drink drink) : base(drink) { }
        public override string Description => $"{drink.Description} with {nameof(SecretIngredient)} ";
    }

    public class Ice : DrinkDecorator
    {
        public Ice(Drink drink) : base(drink) { }
        public override string Description => $"{drink.Description} with {nameof(Ice)} ";
    }
}