Это потому, что у вас 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
является ссылками как таковыми. Посмотрите на этот похожий пример:
Сначала обратите внимание, что теперь 'Люк' (ранее это был Марк, но с той же логикой) печатает ... Почему, когда мы ссылаемся на 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
к свойству Name Person
, а затем используете 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, которое не является реальным миром, но выполняет свою работу. Поэтому, чтобы позволить динамически обновлять 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)} ";
}
}