类设计 - 带有通用子类的基类

5

我有以下类:

  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()好还是被接受的?
编辑 从框架设计的角度来看。它这样做是好的/可接受的吗?如果不是,那么如何设计才能防止这种情况或更好地传达他们的意图(在两种情况下)?

2
你的第二个问题应该是一个单独的问题。 - jason
3个回答

2
我更喜欢这样的东西:
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

对于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成员可写是类型不安全的。这使我可以通过将值设置为non-T实例来打破GenericGrid<T>的契约。
  • 这更多是一种观点,但我不喜欢使用new,因为它经常会让用户感到困惑。我更喜欢在这种情况下使用后缀~Type。
  • 这只需要数据被存储一次。
  • 不需要进行任何不安全的转换。

编辑 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; }
  }

}

我应该提到GridBase和GenericGrid都是可用的类,也就是说你可以实例化它们。我会修改我的问题。 - Shiv Kumar
我会更进一步建议GridBase也应该是通用的。这样可以避免使用new关键字和需要第二个~Typed属性的情况。 - James King
@James B 这将防止存储抽象概念 GridBase。例如,想象一下我想要存储一堆 GridBase 值并稍后打印它们。由于 GridBase 是泛型的,因此无法为每个可能的 GridBase<T> 实例执行此操作。 - JaredPar

1

泛型继承的第二种形式(转换基类属性)更为正确,因为它不违反Liskov替换原则。可以想象,将泛型类的实例强制转换为基类,并通过基类访问Data会指向不同的属性。您需要保持两者同步,以使派生类能够替代基类。

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

关于第二个问题,我不确定我理解了这个问题。看起来你遇到的问题与框架在运行时生成代理时没有调用抽象方法有关,这在抽象类中始终是合法的,因为执行该代码的唯一途径是通过派生类,而必须重写抽象方法。


关于第二种情况,您似乎理解得很正确。但是,当代理被创建时,它会从Child继承,并且Child有一个DoSomething()的实现。因此,在这种情况下,我希望生成的类在其自己的DoSomething()实现中某个时刻调用base.DoSomething()。如果不是这种情况,那么您如何通过设计来传达呢? - Shiv Kumar
我认为你不能在不调用基类的情况下设计框架。根据具体情况(例如Entity Framework),你可能需要修改生成代理类的模板。 - Igor Zevaka
问题更多地涉及框架应该如何设计,或者框架如何能够通过更好的设计来更好地传达信息或防止这种情况的发生(如果它们的实现被认为“可以接受”)。 - Shiv Kumar
@Shiv,我认为你应该始终假设派生类将选择不调用您的基本实现。如果有必须执行的代码以保持类不变量,则不应该放在虚拟方法中。 “base”功能对于继承者很方便,因为它提供了一种简单的方法来添加“之前”和“之后”的逻辑,但他们始终可以选择完全替换代码为其他实现。 - Dan Bryant
我正在将Igor的回答标记为答案。@Dan,是的,那就是我希望有更好的设计方式来指定意图的部分。 - Shiv Kumar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接