【问题标题】:Generics, Inheritance and the new operator泛型、继承和新运算符
【发布时间】:2010-10-14 09:31:36
【问题描述】:

这是我发现自己不时使用的东西,我只是想获得一些关于这种做法的优点的反馈。

假设我有一个基类:

abstract class RealBase {
    protected RealBase(object arg) {
        Arg = arg;
    }

    public object Arg { get; private set; }

    public abstract void DoThatThingYouDo();
}

我经常创建第二个通用基类来处理从基类中的“object”类型到“T”类型的转换,如下所示:

abstract class GenericBase<T> : RealBase {
    protected GenericBase(T arg)
        : base( arg ) {
    }

    new public T Arg { get { return (T) base.Arg; } }
}

这使我可以在没有强制转换操作的情况下访问“Arg”作为它的显式类型:

class Concrete : GenericBase<string> {
    public Concrete( string arg )
        : base( arg ) {
    }

    public override void DoThatThingYouDo() {
        // NOTE: Arg is type string. No cast necessary.
        char[] chars = Arg.ToLowerInvariant().ToCharArray();  
        // Blah( blah, blah );
        // [...]
    }

}

同时还可以通过“RealBase”使用它:

class Usage {
    public void UseIt() {
        RealBase rb = new Concrete( "The String Arg" );
        DoTheThing(rb);
    }

    private void DoTheThing(RealBase thingDoer) {
        rb.DoThatThingYouDo();
    }
}

假设还有许多其他“具体”类型......不仅仅是一种。

这是我的问题/疑虑:

  1. 我是否因为使用而“摇摆不定”? 像这样的方法?
  2. 有吗 任何明显的缺点/注意事项 使用这种方法?
  3. 怎么样 那个“新公共T...”在 通用基础?好/坏主意?尴尬?

任何反馈或建议将不胜感激。

【问题讨论】:

    标签: c# generics inheritance oop


    【解决方案1】:

    只要您有足够的纪律性,仅将泛型基类用作帮助器并且从不贬低它,我对此没有任何明确的反对意见。如果你开始到处引用 RealBase、GenericBase 和 ConcreteClass,事情往往会很快变得真正紧密耦合。

    事实上,我建议将其提升一个档次并引入一个界面

    interface IReal {
      void DoThatThingYouDo();
    }
    

    并将基类完全排除在外(基本上从不引用它,除非在声明派生类时)。只是一个帮助我提高代码灵活性的提示。

    哦,如果你确实使用了接口,不要只在基类中声明它,而是在具体的基类中声明它:

    class MyConcrete: BaseClass<MyConcrete>, IReal {
      ...
    }
    

    提醒一下,基类并不重要,重要的是它做了什么!

    【讨论】:

    • Re:在具体实现上声明接口。我发现我自己断断续续地辩论这个问题。一方面是重复,另一方面是沟通。我认为您在这种情况下不再强调基类的重要性的观点具有重要意义。
    • DRY(不要重复自己)是有目的的,但在这种情况下,我真的看不到明确说明接口定义会导致您误入歧途的场景
    【解决方案2】:

    嗯,我们现在已经有了一个三层深的继承树,但你没有给出任何特别的理由这样做。

    我个人很少使用继承(或者更确切地说,除了实现接口和直接从对象派生之外,我很少设计自己的继承层次结构)。继承是一种强大的工具,但很难有效地使用。

    如果与其他方法相比,这为您提供了一些明显的优势,那很好 - 但我会考虑您为阅读代码的人增加的复杂性。他们真的要考虑三个层次结构,有两个同名的属性吗? (我会将 GenericBase.Arg 重命名为 GenericBase.GenericArg 或类似名称。)

    【讨论】:

    • 我同意。教授们~爱~继承,并创造了很好的、整洁的、最终不切实际的例子。我认为一些应届毕业生可能需要一些时间才能意识到他们编写的每个程序都不需要有 6 级继承。
    • Jon,您对使用基类作为帮助器以使您不必重复自己有何看法?我经常这样做,但无可否认,我会做噩梦,梦见有人稍后出现并开始向下转换并传递 MyBaseClass 引用。
    • George:如果基类不包含任何状态或抽象方法,我会编写一个带有静态方法的实用程序类。如果您确实有可以重用的抽象方法或状态,那就更合理了。我只是不经常发现自己处于这种情况。
    • (当然,另一种方法是 compose 代码的可重用位。这有时可行,有时不可行 - 这取决于细节。)
    • 嗯,我一直在使用基类,例如在我的实体中执行诸如覆盖 Equals 或定义 Id 属性之类的事情。如果没有人对 BaseEntity 失望,我真的看不出问题
    【解决方案3】:

    我个人强烈建议不要使用 new 运算符,因为它可能会导致混淆。其实我自己最近也遇到过这样的问题,但是我用的是基类abstract后缀为AsObject,即:

    public BaseClass{
        public abstract object ValueAsObject {get;set;}
    }
    

    还有泛型类:

    public BaseClass<T> : BaseClass {
        public T Value {get;set;}
        public override object ValueAsObject {
           get{return (T)this.Value;} 
           set{this.Value = value;} // or conversion, e.g. string -> int
    }
    

    Georg Mauer 提出的 DoThatThingYouDo 接口也不错。

    【讨论】:

      【解决方案4】:

      我认为您可以通过使用接口而不是双抽象基类来获得您想要的相同功能,考虑一下:

      public interface IAmReal
      {
          void DoThatThingYouDo();
          ...
      }
      
      abstract class GenericBase<T> : IAmReal
      {
          protected GenericBase<T>(T arg)
          {
              Arg = arg;
          }
          public T Arg { get; set; }
          public abstract void DoThatThingYouDo();
      }
      
      class MyConcrete : GenericBase<string>
      {
          public MyConcrete(string s) : base(s) {}
          public override void DoThatThingYouDo()
          {
              char[] chars = Arg.ToLowerInvariant().ToCharArray();
              ...
          }
      }
      
      class Usage
      {
          public void UseIt()
          {
              IAmReal rb = new MyConcrete( "The String Arg" );
              DoTheThing(rb);
          }
      
          private void DoTheThing(IAmReal thingDoer)
          {
              rb.DoThatThingYouDo();
          }
      }
      

      【讨论】:

        【解决方案5】:

        我认为你并没有失控。有关更复杂的内容,请参阅 Curiously Recurring Template

        【讨论】:

        • "struct Derived : Base" 只是导致我的堆栈溢出:)
        【解决方案6】:

        我是唯一一个认为应该尽可能避免继承的人吗?当继承导致奇怪的问题时,我已经看到了不同的实现,而不仅仅是在尝试向下转换时。接口是救星!我并不是说应始终避免继承,但仅通过查看指定的代码,我看不出这种继承树相对于常规接口的优势。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-07-08
          • 2023-04-02
          • 1970-01-01
          • 2023-03-25
          • 1970-01-01
          • 2011-03-06
          • 1970-01-01
          相关资源
          最近更新 更多