Дизайн класса - Базовый класс с родовым потомком - PullRequest
3 голосов
/ 22 ноября 2010

У меня есть следующие классы

  class GridBase
  {
    public object DataSource { get; set; }
  }

  class GenericGrid<T> : GridBase
  {
    public new T DataSource { get; set; }
  }

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

Считается ли это правильным / приемлемым способомреализовать такую ​​иерархию?Или вы должны пройти лишнюю милю и реализовать ее следующим образом

  class GridBase
  {
    protected object dataSource;
    public object DataSource { get { return dataSource; } set { dataSource = value; } }
  }

  class GenericGrid<T> : GridBase
  {
    public new T DataSource { get { return (T)dataSource; } set { dataSource = value; } }
  }

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

Другой случай и вопрос

  abstract class SomeBase
  {
    protected abstract void DoSomething();
  }

  class Child : SomeBase
  {
    protected override void DoSomething()
    {
      /* Some implementation here */
    }
  }

Ситуация здесь такова, что фреймворк "X" объявляет SomeBase, позволяя вам определять своих потомков.Классы, которые они создают (во время выполнения), затем наследуются от вашего класса (в данном случае Child).Однако они не вызывают ваш метод DoSomething () из своей реализации DoSomething ().

Со своей стороны, они также не могут слепо вызывать base.Dosomething (), потому что типичным случаем является то, чтокласс, который они генерируют, обычно происходит от SomeBase, и поскольку метод является абстрактным, он недопустим.(Лично мне не нравится это поведение в C #).

Но в любом случае, это хороший или принятый дизайн, который не вызывает base.xxx (), особенно когда «намерение» противоречит?

РЕДАКТИРОВАТЬ С точки зрения дизайна каркаса.Это нормально / приемлемо, что это делает?Если нет, то как бы он был разработан таким образом, чтобы либо предотвратить такой случай, либо лучше сообщить свое намерение (в обоих случаях).

Ответы [ 3 ]

2 голосов
/ 23 ноября 2010

Для вопроса DataSource я предпочитаю следующую схему

abstract class GridBase {
  public abstract object DataSource { get; }
}

class GenericGrid<T> : GridBase { 
  private T m_data;

  public override object DataSource { 
    get { return m_data; }
  }
  public T DataSourceTyped { 
    get { return m_data; }
    set { m_data = value; }
  }
}

Причины

  • Наличие элемента GridBase.DataSource с возможностью записи является небезопасным. Это позволяет мне расторгнуть договор GenericGrid<T>, установив значение non-T экземпляр
  • Это скорее вопрос мнения, но мне не нравится использование new, потому что оно часто смущает пользователей. Я предпочитаю суффикс ~ Type "для этого сценария
  • Требуется, чтобы данные хранились только один раз
  • Не требует небезопасного кастинга.

РЕДАКТИРОВАТЬ OP исправлено, что GridBase и GenericGrid оба используемых типа

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

GenericGrid<int> grid = new GenericGrid<int>();
GridBase baseGrid = grid;
baseGrid.DataSource = "bad";
Console.Write(grid.DataSource); // Error!!!

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

class Grid : GridBase { 
  private objecm m_data;
  public override object DataSource {
    get { return m_data; }
  }
  public object DataSourceTyped {
    get { return m_data; }
    set { m_data = value; }
  }

}
1 голос
/ 23 ноября 2010

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

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

public class Base {
  private readonly object m_Data; //immutable data, as per JaredPar suggestion that base class shouldn't be able to change it

  publlic Base(object data) {
    m_Data = data;
  }


  protected virtual object GetData() {return m_Data;}

  public Object DataSource {get {return GetData();}} 
}

public class Derived<T> : Base {
  private T m_Data;

  public Derived():base(null){}
  protected override object GetData() {return m_Data;}

  protected new T Data {return m_Data;}
}

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

1 голос
/ 23 ноября 2010

Я бы предпочел что-то вроде этого:

interface IGrid {
    object DataSource { get; }
}

interface IGrid<T> {
    T DataSource { get; }
}

public Grid : IGrid {
    public object DataSource { get; private set; }

    // details elided
}

public Grid<T> : IGrid<T> {
    public T DataSource { get; private set; }
    object IGrid.DataSource { get { return this.DataSource; } }

    // details elided
}

Обратите внимание, что я НЕ наследую от Grid.

...