【发布时间】: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> {}
泛型方法的缺点:
- 起初看起来有点复杂。尤其是糟糕的地方。
- 一开始似乎不熟悉。
- DataContractSerializing 时带来一点乐趣。
- 组件更大。
优点:
- 类型安全:您将无法将 ScriptTemplateElement 添加到 ScriptRunStep。
- 不需要从集合项转换为具体类型。此外 - 更好的智能感知支持。 ScriptTemplate.Steps 立即属于 ScriptTemplateStep,而不是抽象 Step。
- 遵守 Liskov 原则:在继承场景中,ScriptRun 上有 IList 集合,但确实不应该在其中添加 ScriptTemplateStep,尽管它显然是一个 Step。
- 您不必进行覆盖。例如。假设您想在脚本上有一个 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