Подходящий дизайн / шаблон дизайна для этой проблемы? - PullRequest
4 голосов
/ 30 июня 2011

Ранее я публиковал это , но, думаю, это было слишком многословно и неактуально.Мой вопрос также, как это .Один из плакатов во второй ссылке сказал, что ответ (почему вы не можете сделать код ниже) был проблемой дизайна, в частности, «неправильное использование наследования».Поэтому я хотел бы еще раз проверить эту проблему с экспертами в StackOverflow и посмотреть, действительно ли это проблема «плохого наследования», но что более важно, как исправить дизайн.

Как и плакат, яЯ также запутался в методе Фабрики и в том, как я могу его применить.Кажется, фабричный метод предназначен для нескольких конкретных классов, которые имеют точно такую ​​же реализацию , что и абстрактный базовый класс, и не добавляют свои собственные свойства .Но, как вы увидите ниже, мои конкретные классы основаны на абстрактном базовом классе и добавляют дополнительные свойства .

Базовый класс, который мы строим:

public abstract class FlatScreenTV
{
     public string Size { get; set; }
     public string ScreenType { get; set; }
}

Примеры классов расширения:

public class PhillipsFlatScreenTV : FlatScreenTV
{
     // Specific to Phillips TVs. Controls the backlight intensity of the LCD screen.
     public double BackLightIntensity { get; set; }
}

public class SamsungFlatScreenTV : FlatScreenTV
{
     // Specific to Samsung TVs. Controls the time until the TV automatically turns off.
     public int AutoShutdownTime { get; set; }
}

Допустим, существует больше классов расширения для большего количества брендов телевизоров с плоским экраном.А потом, скажем, мы вставляем их все в общий список:

public static void Main()
{
     List<FlatScreenTV> tvList = new List<FlatScreenTV>();

     tvList.Add(new PhillipsFlatScreenTV());
     tvList.Add(new SamsungFlatScreenTV());
     tvList.Add(new SharpFlatScreenTV());
     tvList.Add(new VizioFlatScreenTV());

     FlatScreenTV tv = tvList[9]; // Randomly get one TV out of our huge list
}

Проблема:

Я хочу получить доступ к определенным свойствам любого «оригинала»бренд телевизора, к которому принадлежит эта переменная.Я знаю бренд, потому что, если я позвоню tv.GetType(), он вернет правильный «оригинальный» тип - , а не FlatScreenTV.Но мне нужно иметь возможность преобразовать tv из FlatScreenTV в исходный тип, чтобы получить доступ к специфическим свойствам телевизоров с плоским экраном каждой марки.

Вопрос № 1:Как я могу динамически привести это, правильно - без самодельных хаков и огромных цепочек if-else, чтобы грубо угадать «оригинальный» тип?

После просмотра похожих вопросов дизайна, большинство ответов: вы не можете .Некоторые люди говорят, что нужно взглянуть на Factory Pattern , а другие говорят, что пересматривают дизайн с использованием интерфейсов , но я не знаю, как использовать любой из них для решения этой проблемы.

Вопрос № 2: Итак, как мне спроектировать эти классы, чтобы я мог получить доступ к специфическим свойствам исходного типа в контексте выше?

Вопрос №3: действительно ли это плохое наследство?

Ответы [ 5 ]

5 голосов
/ 30 июня 2011

Ваш дизайн нарушает " принцип замещения Лискова ". Другими словами, код, который работает с элементами из вашего списка FlatScreenTV, не должен знать или заботиться о том, что является производным типом.

Допустим, ваш код должен создать пользовательский графический интерфейс для удаленного управления. Может быть достаточно просто знать имена и типы свойств каждого телевизора, чтобы автоматически генерировать пользовательский интерфейс. В этом случае вы можете сделать что-то вроде этого, чтобы выставить пользовательские свойства из базового класса:

public abstract class FlatScreenTV
{
    public FlatScreenTV()
    {
        CustomProperties = new Dictionary<string,object>();
    }

    public Dictionary<string,object> CustomProperties { get; private set; }
    public string Size { get; set; }
    public string ScreenType { get; set; }
}

public class PhillipsFlatScreenTV : FlatScreenTV
{
    public PhillipsFlatScreenTV()
    {
        BackLightIntensity = 0;
    }

    // Specific to Phillips TVs. Controls the backlight intensity of the LCD screen.
    public double BackLightIntensity 
    { 
        get { return (double)CustomProperties["BackLightIntensity"]; }
        set { CustomProperties["BackLightIntensity"] = value; }
    }
}

public class SamsungFlatScreenTV : FlatScreenTV
{
    public SamsungFlatScreenTV()
    {
        AutoShutdownTime = 0;
    }

    // Specific to Samsung TVs. Controls the time until the TV automatically turns off.
    public int AutoShutdownTime 
    {
        get { return (int)CustomProperties["AutoShutdownTime"]; }
        set { CustomProperties["AutoShutdownTime"] = value; }
    }
}

Если вам действительно нужно работать непосредственно с производными типами, вам следует вместо этого рассмотреть переход на архитектуру на основе плагинов. Например, у вас может быть такой метод фабрики:

IRemoteControlGUI GetRemoteControlGUIFor(FlatScreenTV tv)

, который будет сканировать ваши плагины и найти тот, который знает, как создать пользовательский интерфейс для конкретного типа FlatScreenTV, который вы передали. Это означает, что для каждого добавляемого вами нового FlatScreenTV вам также необходимо создать плагин, который знает, как сделать свой пульт дистанционного управления графическим интерфейсом.

1 голос
/ 30 июня 2011

Могу предложить частичный ответ:

Сначала прочитайте принцип подстановки Лискова.

Во-вторых, вы создаете объекты, которые наследуются от FlatScreenTV, но, по-видимому, ни с чем наследство лол.

Если ваш код хочет получить доступ к свойствам, специфичным для данного типа, тогда вы действительно хотите, чтобы этот код был инкапсулирован в этот тип. В противном случае каждый раз, когда вы добавляете новый тип телевизора, весь код, который обрабатывает список ТВ, должен будет обновляться, чтобы отразить это.

Таким образом, вы должны включить метод на FlatScreenTV, который выполняет x, и переопределить его в телевизорах, как требуется.

Таким образом, в основном в вашем методе Main, приведенном выше, вместо того, чтобы думать, что я хочу иметь дело с TVTypeX, вы всегда должны ссылаться на базовый тип, и пусть наследование и переопределение метода обрабатывают конкретное поведение для подтипа, с которым вы на самом деле имеете дело.

Код например.

  public abstract class FlatScreenTV
  {
      public virtual void SetOptimumDisplay()
      {
         //do nothing - base class has no implementation here
      }
  }


  public class PhilipsWD20TV
  {
      public int BackLightIntensity {get;set;}

      public override void SetOptimumDisplay()
      {
          //Do Something that uses BackLightIntensity
      }

  }
1 голос
/ 30 июня 2011

Заводская модель будет лучшим способом пойти

0 голосов
/ 15 декабря 2011

Вы все еще можете использовать фабрику. Задача IMO на заводе - собрать всю тяжелую работу по созданию ваших телевизоров в одном месте. Категорически сказать, что «фабрика предназначена для нескольких конкретных классов, которые имеют ту же самую реализацию, что и абстрактный базовый класс», забывает о полиморфизме.

Нет закона, согласно которому вы не можете использовать фабричный шаблон, потому что подклассы объявляют уникальные свойства и методы. Но чем больше вы можете использовать полиморфизм, тем больше смысла имеет заводской шаблон. Кроме того, в качестве общего руководства, IMHO, чем больше сложностей при создании базы, тем лучше для вас в конечном итоге использование фабрики, потому что вы «инкапсулируете изменения», то есть создание конкретных классов может измениться из-за к различным требованиям и внутренней сложности конструкции (решение проектного анализа, чтобы быть уверенным). И это изменение в одном классе - фабрике.

Попробуйте это: определите все в абстрактном классе, а затем для данного подкласса TV либо напишите конкретный код, а для тех, кто не применяется, напишите некоторый стандартный код «Я не делаю».

Подумайте обо всех вещах, которые ваши телевизоры делают в общих терминах: включите, выключите и т. Д. Напишите оболочку виртуального метода в базовом классе для всех общих вещей, которые делает телевизор - это простой пример метода шаблона картина кстати. Затем переопределите их в конкретных классах соответствующим образом.

Есть и другие вещи, которые вы можете сделать в базовом классе, чтобы сделать его более доступным (это технический термин, означающий «ссылаться на подклассы как базовый класс, но делать вещи подкласса»).

  • Определение методов делегата (очень мощный, но недостаточно используемый)
  • использовать params [] для списков параметров динамического метода
  • Сделать делегатов собственности
  • Статические методы
  • Объявить свойства и методы «абстрактными» - форсирует реализацию подкласса по отношению к «виртуальным»
  • Скрыть унаследованные вещи в подклассе (обычно используя ключевое слово «new», чтобы сообщить, что это специально)
  • Если параметры построения многочисленны или сложны, создайте класс, специально разработанный для передачи конфигурации в метод сборки фабрики.



    public class TVFactory {

    public TV BuildTV(Brands thisKind) {
        TV newSet;

        switch (thisKind) {
            case Brands.Samsung :
                Samsung aSamsungTV = new Samsung();
                aSamsungTV.BacklightIntensity = double.MinVal;
                aSamsungTV.AutoShutdownTime = 45;    //oops! I made a magic number. My bad
                aSamsungTV.SetAutoShutDownTime = new delegate (newSet.SetASDT);
                newSet = aSamsungTV;

                break;
            . . .
        } // switch
    }

    //more build methods for setting specific parameters
    public TV BuildTV (Brands thisKind, string Size) { ... }

    // maybe you can pass in a set of properties to exactly control the construction.
    // returning a concrete class reference violates the spirit of object oriented programming 
    public Sony BuildSonyTV (...) {}

    public TV BuildTV (Brands thisKind, Dictionary buildParameters) { ... }
}

public class TV {
    public string Size { get; set; }
    public string ScreenType { get; set; }
    public double BackLightIntensity { get; set; }
    public int AutoShutdownTime { get; set; }

    //define delegates to get/set properties
    public delegate  int GetAutoShutDownTime ();
    public delegate void SetAutoShutDownTime (object obj);

    public virtual TurnOn ();
    public virtural TurnOff();

    // this method implemented by more than one concrete class, so I use that
    // as an excuse to declare it in my base.
    public virtual SomeSonyPhillipsOnlything () { throw new NotImplementedException("I don't do SonyPhillips stuff"); }

}

public class Samsung : TV {
    public Samsung() {
        // set the properties, delegates, etc. in the factory
        // that way if we ever get new properties we don't open umpteen TV concrete classes
        // to add it. We're only altering the TVFactory.
        // This demonstrates how a factory isolates code changes for object construction.
    }

    public override void TurnOn() { // do stuff }
    public override void TurnOn() { // do stuff }

    public void SamsungUniqueThing () { // do samsung unique stuff }

    internal void  SetASDT (int i) {
        AutoShutDownTime = i;
    }
}

// I like enumerations. 
//   No worries about string gotchas
//   we get intellense in Visual Studio
//   has a documentation-y quality
enum Brands {
    Sony
    ,Samsung
    ,Phillips
}
0 голосов
/ 01 июля 2011

"фабричный метод предназначен для нескольких конкретных классов, которые имеют ту же реализацию, что и абстрактный базовый класс [interface], и ​​не добавляют свои собственные свойства."

Нет, говоря более практично, чем теоретически,метод фабрики может предоставить вам объекты конкретных классов, в которых конкретные классы должны иметь некоторые общие методы и интерфейсы, а также некоторые дополнительные специфические атрибуты.

Иногда я использую метод, который создает тот же классобъект каждый раз, когда я вызывал, и мне нужно вызывать его несколько раз, и иногда я использую метод, который создает несколько различных объектов класса, и это может сбивать с толку, может быть, другой вопрос.

И, ваш дальнейший комментарий о предложении переключателя, с большим количеством опций, при использовании фабричного шаблона вы обычно предоставляете идентификатор для конкретного класса / конкретного объекта.Это может быть строка, целое число, идентификатор специального типа или перечислимый тип.

Вместо этого можно использовать целочисленный / перечислимый идентификатор и использовать коллекцию для поиска конкретного класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...