开发者

Class Design - Base class with Generic Descendant

I have the following classes

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

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

Both GridBase and Generic Grid classes can be instantiated and one can descend from either as well.

Is this considered the correct/accepted way to implement such a hierarchy? Or should you go the extra mile and implement it like the following

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

The same applies to non generic classes when a property is re-introduced in a descendant, I'm just using a generic example here.

Another case and question

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

  class Child : SomeBase
  {
    protected override void DoSomething()
   开发者_运维百科 {
      /* Some implementation here */
    }
  }

The situation here is that framework "X" declares SomeBase allowing you to define your own descendants. The classes they create (at run time) then descend from your class (Child in the this case). However, they don't call your DoSomething() method, from their implementation of DoSomething().

On their part, they can't blindly call base.Dosomething() either because the typical case is that the class they generate normally descends from SomeBase and since the method is abstract that's not valid. (Personally, I don't like this behavior in C#).

But anyway, is that good or accepted design, that is not calling base.xxx(), especially when the the "intent" seems to contradict?

EDIT From a framework design perspective. Is it ok/acceptable that it does this? If not how would it be designed so as to either prevent such a case or better impart their intent (in both cases).


I would prefer something like this:

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
}

Note that I am NOT inheriting from Grid.


For the DataSource question I prefer the following pattern

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

Reasons

  • Having the GridBase.DataSource member be writable is type unsafe. It allows me to break the contract of GenericGrid<T> by setting the value to a non-T instance
  • This is more of a matter of opinion but I dislike the use of new because it often confuses users. I prefer the suffix ~Type" for this scenario
  • This only requires the data be stored once
  • Doesn't require any unsafe casting.

EDIT OP corrected that GridBase and GenericGrid are both usable types

In that case I would say you need to reconsider your design a bit. Having them both as usable types opens you up to very easy to expose type errors.

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

The design will be a lot more reliable if separate the storage from the access of the values in a manner like my original sample. You could extend it further with the following code to have a usable non-generic container

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

}


The second form of the generic inheritance (casting the base class' attribute) is more correct as it does not violate Liskov Substitution Principle. It is conceivable that an instance of the generic class is cast into base class and accessing Data through the base class points to a different property. You will need to keep both in sync in order for the derived class to be substitutable for the base class.

Alternatively, you can implement some sort of a strategy pattern where the base class asks for the Data property from the derived class, in order to avoid awkward downcasting. This is what I had in mind:

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

With regards to the second question, I am note sure I understand the question. Sound like the problem you are having is to with the framework not calling the abstract method when it generates a proxy at runtime, which is always legal in abstract classes, as the only way for that code to execute is through a derived class which must override the abstract method.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜