【问题标题】:Code reuse through generics vs polymorphism通过泛型与多态的代码重用
【发布时间】:2010-02-23 21:16:13
【问题描述】:

重用实现的更好方法是什么:继承还是泛型?

模型如下:脚本有步骤,步骤有元素。树结构是双重链接的,即步骤知道他们的脚本和元素现在他们的步骤。

现在,有两种类型的脚本:模板和运行,其中运行首先创建为模板的副本。这会产生 2 个类似的层次结构 ScriptTemplate->ScriptTemplateStep->ScriptTemplateElement 和 ScriptRun->ScriptRunStep->ScriptRunElement。大多数功能是通用的,但各种类可能有一些附加属性。

为了重用功能,我可以开发由 ScriptRun 和 ScriptTemplate 派生的抽象 Script 类,例如:

abstract class Script { IList<Step> Steps; }
class ScriptRun : Script {}
class ScriptTemplate : Script {}

class Step { Script Script; IList<Element> Elements; }
class ScriptRunStep : Step {}
class ScriptTemplateStep : Step {}

或者我可以试试泛型:

abstract class Script<TScript, TStep, TElement> 
where TScript:Script<TScript, TStep, TElement>
where TStep:Step<TScript, TStep, TElement> 
where TElement:Element<TScript, TStep, TElement>
{ IList<TStep> Steps; }

abstract class Step<TScript, TStep, TElement> 
where TScript:Script<TScript, TStep, TElement>
where TStep:Step<TScript, TStep, TElement> 
where TElement:Element<TScript, TStep, TElement>
{ TScript Script; IList<TElement> Elements; }

class ScriptRun : Script<ScriptRun, ScriptRunStep, ScriptRunElement> {}
class ScriptRunStep : Step<ScriptRun, ScriptRunStep, ScriptRunElement> {}
class ScriptRunElement : Element<ScriptRun, ScriptRunStep, ScriptRunElement> {}

class ScriptTemplate : Script<ScriptTemplate, ScriptTemplateStep, ScriptTemplateElement> {}
class ScriptTemplateStep : Step<ScriptTemplate, ScriptTemplateStep, ScriptTemplateElement> {}
class ScriptTemplateElement : Element<ScriptTemplate, ScriptTemplateStep, ScriptTemplateElement> {}

泛型方法的缺点:

  1. 起初看起来有点复杂。尤其是糟糕的地方。
  2. 一开始似乎不熟悉。
  3. DataContractSerializing 时带来一点乐趣。
  4. 组件更大。

优点:

  1. 类型安全:您将无法将 ScriptTemplateElement 添加到 ScriptRunStep。
  2. 不需要从集合项转换为具体类型。此外 - 更好的智能感知支持。 ScriptTemplate.Steps 立即属于 ScriptTemplateStep,而不是抽象 Step。
  3. 遵守 Liskov 原则:在继承场景中,ScriptRun 上有 IList 集合,但确实不应该在其中添加 ScriptTemplateStep,尽管它显然是一个 Step。
  4. 您不必进行覆盖。例如。假设您想在脚本上有一个 NewStep 方法。在前一种情况下,您说

abstract class Script { abstract Step NewStep(); }

abstract class ScriptRun { 
    override Step NewStep(){ 
        var step = new ScriptRunStep(); 
        this.Steps.Add(step); 
        return step; 
    } 
}

abstract class ScriptTemplate { 
    override Step NewStep(){ 
        var step = new ScriptTemplateStep(); 
        this.Steps.Add(step); 
        return step; 
    } 
}

在你写的泛型场景中:

abstract class Script<TScript, TStep, TElement> 
where TScript:Script<TScript, TStep, TElement>
where TStep:Step<TScript, TStep, TElement>, new()
where TElement:Element<TScript, TStep, TElement>
{ 
    TStep NewStep() {
        var step = new TStep();
        this.Steps.Add(step);
        return step;
    }
}

ScriptRun 和 ScriptTemplate 自动拥有该方法,或者更好的方法:返回类型分别为 ScriptRunStep 和 ScriptTemplateStep 而不是简单的 Step。

【问题讨论】:

  • 也许你的例子就是这样,但我想我可能不会区分“模板”和“运行”。为什么不创建一个 API 来“克隆”脚本对象。更进一步,给 Script 类一个“BasedOn”属性,也是 Script 类型。 Steps 集合中的条目可以添加、删除或替换“BasedOn”脚本(如果有)中的步骤。
  • @Daniel - 原因之一是它们可以具有不同的属性。第二个原因是它们在数据库中存储为不同的实体:)。从模板创建运行实际上是克隆模板丢弃模板特定的属性。
  • @wwosik,根据您的描述,您可以将Template 分离成一个包含Run 的完全独立的对象。
  • @Jeff - 怎么样?通常有一个模板和许多运行。编辑:哦,我明白了。但是,它不会给架构带来更多的复杂性吗?
  • 我不确定它是否会使它变得更复杂 - 我设想单独的 Template 和 Run 对象,每个对象都包含一个脚本。脚本包含步骤等。这将提供一个简单的包含层次结构 - 虽然我不知道它是否会满足您的要求!

标签: c# generics polymorphism


【解决方案1】:

我发现泛型通过泛型属性促进组合,而无需为您想要利用的每个组合编写不同的类,也不必创建冗长的继承树。在可能的情况下,我会尽量支持组合而不是继承,尤其是在单继承平台中。

我会说你的情况需要两者兼而有之。可能类似于以下内容:

类 Child { TParent 父级; } 类父 { IList 孩子; } 类 ParentAndChild : Parent { TParent Parent; } 类元素:Child { ... } 类步骤:ParentAndChild

【讨论】:

  • 这个继承树不会很长,它是抽象的->具体的,就是这样:)
  • @after 编辑:我必须说我真的很喜欢你的父母/孩子的想法。 :)
  • 我做过类似的事情,但层次结构更深。在到达叶子孩子之前有几个级别的 ParentAndChild 类。
【解决方案2】:

两者都没有。重用实现的最佳方式是聚合。

之后,模板是否适用取决于问题。我的经验法则是,任何产生最少代码的东西都会更好。至少,这是我的经验。

[编辑] 对于如此复杂的问题,这可能是一个简短的答案。

但是,特别是对于这种情况(这似乎主要是关于重用父子层次结构的逻辑,你可以写

 IChildOf<TPARENT> 
 { TPARENT Parent {get; set;} }


 sealed class Children<TPARENT,TCHILD> : where TCHILD: IChildOf<TPARENT>
 {
      // .... details omitted 
      public void Add(TCHILD child)
      { 
          // add child to internal collection, then:

          child.Parent = parent;
      }

      readonly TPARENT parent;
 }

TPARENT 将有一个 Children 集合作为成员,并将一个指向自身的指针传递给它。该集合确保每个TCHILD 都有对其父级的引用。

【讨论】:

  • 好吧,但是在这个特定示例中,您将在此处汇总什么以及如何汇总?
  • @wwosik:我编辑了我的分析器(实际上是今天早上)以回应您的评论。我不确定 SO 是否会通知您,因此发表了此评论。
【解决方案3】:

看看你的优缺点列表 - 我怀疑你已经知道答案了。缺点通常是“我对此不熟悉”、“效率不高”的形式,而优点是“它对我的代码强制执行更严格的结构”的形式。我说,总是选择使代码更健壮的那个,那就是泛型。

【讨论】:

  • 嗯,“我不熟悉这个”对于开发团队来说是一个相当重要的论点。见这里(无耻广告):stackoverflow.com/questions/2320371/…。注意泛型比继承更有效,而不是相反。 ;)
  • 我不认为泛型实际上比继承更复杂......团队习惯的可能会增加 感知 复杂性,但这就是更多的理由尝试更严格的方法,以便团队习惯更强大的技术。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-21
  • 1970-01-01
  • 2023-02-22
  • 2018-06-07
  • 1970-01-01
  • 1970-01-01
  • 2012-04-12
相关资源
最近更新 更多