我正在研究“装饰图案”。用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());
}
}
执行我得到的测试:
浓咖啡
普通饮料
虽然我期望如此:
浓咖啡
浓咖啡,秘密成分
为什么?预先感谢。
答案 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
到以前无法实现的地方。升级Employee
和Customer
显示了如何执行此操作:
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)} ";
}
}