有时当我扩展自己的一个类时,我想(为了子类的目的)在超类中的方法中“注入”一行或两行代码。
在这些情况下,我有时会添加一个空的受保护方法的调用,以便子类覆盖。
public void superClassMethod() {
// some fairly long snippet of code
doSubclassSpecificStuff();
// some other fairly long snippet of code
}
// dummy method used for overriding purposes only!
protected void doSubclassSpecificStuff() {
}
在同一课堂上多次这样做时,我必须说它看起来很尴尬/丑陋所以我的问题:
我能想出的唯一选择是使用类似命令模式的东西,并使用setMiddleOfMethodHandler(SomeRunnableHandler)
,并调用handler.doSubclassSpecificStuff()
而不是虚拟方法。我看到它有一些缺点,例如无法触及受保护的数据。
答案 0 :(得分:9)
您刚刚发现了Template method设计模式。请注意,通常构成各个步骤的方法是抽象的(而不是空的和受保护的),以便子类必须覆盖它们。
答案 1 :(得分:4)
有Template method pattern。这个想法是很多工作是常见的,除了一些位,由子类实现的方法处理。
答案 2 :(得分:2)
是的,这是一种合法的做事方式;我自己用过它。
我能看到的唯一问题不是具体的技术,而是你正在使用具体的子类(读取:非抽象)类的事实。子类化具体类有许多微妙的问题,所以我建议完全避免它。参见例如http://en.wikipedia.org/wiki/Liskov_substitution_principle解释你必须做些什么才能正确地对一个类进行子类化,以及所涉及的问题。此外,在“Effective Java”块中建议使用合成(第16项)。
另一种方法(避免子类化)将使用Dependency Injection。您的方法将接受实现接口ISpecificStuff
的类型的参数,该接口指定方法doSubclassSpecificStuff()
:
public void superClassMethod(ISpecificStuff specificStuff) {
....
specificStuff.doSubclassSpecificStuff();
....
}
这样,任何调用者都可以决定该方法应该做什么。这避免了子类化的需要。当然,如果你需要多种方法,你可以通过构造函数注入。
答案 3 :(得分:1)
对我来说看起来很可疑。我认为你最终不得不这样做的原因是一个设计缺陷。你需要“拆分”的方法可能做得太多了。解决方案是逐步分解,并将“doSubclassSpecificStuff”步骤赋予特定含义。
例如:
void Live()
{
BeBorn();
DoCrazyStuff(); // this can be made protected virtual
Die();
}
答案 4 :(得分:1)
是的,完全没问题。这是模板方法模式的一个示例,您可以使用继承来定义维护已知“骨架”的方法,但可以使用自定义逻辑。
public abstract class Ancestor
{
protected virtual void CanOverrideThisStep(){...}
protected abstract void MustDefineThisStep();
protected sealed void MustDoExactlyThis(){...}
private void HideThisStepFromEveryone(){...}
public sealed void TemplateMethod()
{
...
CanOverrideThisStep();
...
MustDoExactlyThis();
...
MustDefineThisStep();
...
HideThisStepFromEveryone();
}
}
上面祖先的继承者必须为MustDefineThisStep()定义一个主体,并且可以在它们的选项中覆盖CanOverrideThisStep(),但不能触及MustDoExactlyThis(),HideThisStepFromEveryone或TemplateMethod驱动函数本身。但是,除了HideThisStepFromEveryone之外,所有子方法都可用于子类,因此子项可以在MustDefineThisStep()的实现中使用MustDoExactlyThis()。
这很常见;这样的结构是OO语言可以使用这些访问修饰符的原因。该模式对于工作流,文件处理以及通常相同但实现细节略有不同的其他任务非常有用。
答案 5 :(得分:1)
我经常使用这种技术来处理特殊情况。我会写这样的东西:
public void foo()
{
theData=getTheData();
preprocessDataHook(theData);
putTheData(theData);
}
protected void preprocessDataHook(SomeObject theData)
{
// Nop. Available for subclasses to override.
}
不需要预处理数据的子类就可以不覆盖此函数。需要预处理的子类可以覆盖该函数。
如果我们期望所有或大多数子类都需要预处理,那么这应该是一个抽象函数来强制程序员实现它,或者做出有意识的决定什么都不做。但如果它只是一个需要在这里做某事的偶然子类,我认为这是一种非常有效的方法。