类设计 - 具有Generic Descendant的基类

时间:2010-11-22 20:53:42

标签: c# class-design

我有以下课程

  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 个答案:

答案 0 :(得分:2)

对于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的使用,因为它经常让用户感到困惑。我更喜欢后缀〜类型“为这个场景
  • 这只需要存储一次数据
  • 不需要任何不安全的铸造。

编辑 OP已更正GridBaseGenericGrid均为可用类型

在这种情况下,我会说你需要重新考虑一下你的设计。将它们作为可用类型打开,可以很容易地暴露类型错误。

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 :(得分:1)

我更喜欢这样的事情:

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

答案 2 :(得分:1)

通用继承的第二种形式(强制转换基类'属性)更正确,因为它没有违反Liskov Substitution Principle。可以想象,泛型类的实例被强制转换为基类,并且通过基类访问Data指向不同的属性。为了使派生类成为基类的 substitutable ,您需要保持同步。

或者,您可以实现某种策略模式,其中基类从派生类请求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;}
}

关于第二个问题,我注意到我确实理解了这个问题。听起来像你遇到的问题是框架在运行时生成代理时不调用抽象方法,这在抽象类中总是合法的,因为执行代码的唯一方法是通过派生类必须覆盖抽象方法。