示例问题
当我创建Java :: Geci抽象类AbstractFieldsGenerator
和AbstractFilteredFieldsGenerator
我遇到了一个不太复杂的设计问题。 我想强调一下,对于某些人来说,这个问题和设计可能看起来很明显,但是在我最近与一位初级开发人员(我的儿子Mihály的交谈)中,我的文章比我的英语要好得多,意识到这个话题可能仍然有价值。
无论如何。 我有这两个类,字段和过滤字段生成器。 第二堂课扩展了第一堂课
abstract class AbstractFilteredFieldsGenerator extends AbstractFieldsGenerator {...
添加额外的功能,同时应为具体实现提供相同的签名。 这是什么意思?
这些生成器有助于使用反射为特定类生成代码。 因此,他们处理的输入信息是Class
对象。 字段生成器类具有一个抽象方法process()
,该方法将为每个字段调用。 它是从一个实现的方法调用的,该方法遍历字段并分别对每个字段进行调用。 当具体类extends AbstractFieldsGenerator
并由此实现此抽象方法时,它将被调用。 当更改相同的具体类以使其extends AbstractFilteredFieldsGenerator
,将仅对过滤后的方法调用具体方法。 我想要一个设计,以便在具体课程中唯一需要的更改是更改名称。
问题定义 类
以更抽象的方式描述相同的问题:有两个抽象类A
和F
以便F extends A
和F
提供一些额外的功能。 两者都声明了具体类应实现的抽象方法m()
。 当具体的类C
声明从C extends A
到C extends F
到C extends F
时,方法m()
的调用应更改,但类C
不应有其他更改。 从类A
定义的方法p()
调用方法m()
。 如何设计F
?
这是什么问题?
可以通过两种明显不同的方式来扩展A
:
-
F
覆盖m()
使它混凝土在实施额外的功能m()
并调用新的抽象方法,说mx()
-
F
使用提供额外功能的版本覆盖方法p()
(在上面的示例中进行过滤),并调用仍然抽象的方法m()
第一种方法不能满足由具体类C
实施的签名应保持相同的要求。 第二种方法将A
的已经实现的功能扔到垃圾桶上,并以不同的方式重新实现它。 在实践中这是可能的,但是肯定会进行一些复制/粘贴编程。 这是有问题的,让我不解释原因。
问题的根源
在工程中,当我们面对这样的问题时,通常意味着问题或结构没有得到很好的描述,解决方案位于完全不同的区域中。 换句话说,有些假设驱动我们的思维方式是错误的。 在这种情况下,问题在于我们假设抽象类提供了一个扩展“ API”来对其进行扩展。 请注意,API不仅可以调用。 对于抽象类,扩展该抽象类时要实现的API。 正如库可以为不同的使用方式提供不同的API(Java 9 HTTP客户端可以send()
以及sendAsync()
)抽象(实际上也是非抽象的)类也可以提供不同的扩展方式用于不同的目的。
如果不修改A
就无法编码F
达到我们的设计目标。 我们需要一个A
版本,该版本提供不同的API来创建具体的实现,而另一个版本(不一定要是正交的)可以创建一个仍然抽象的扩展。
在这种情况下,API之间的区别在于,具体实现的目标是在调用链的末尾,而抽象扩展要挂接到链的最后一个元素。 A
的实现必须提供要挂接到调用链的最后一个元素上的API。 这已经是解决方案。
解
我们在类F
实现了方法ma()
,我们希望p()
调用ma()
而不是直接调用m()
。 修改A
我们可以做到。 我们在A
定义ma()
,然后从p()
调用ma()
p()
。 在A
实现的ma()
版本应毫不费力地调用m()
,以为A
具体实现提供原始的“ API”。 F
中的ma()
实现包含额外的功能(在示例中为过滤),然后调用m()
。 这样,任何具体的类都可以扩展A
或F
并可以使用完全相同的签名实现m()
。 我们还避免了复制/粘贴编码,只是调用m()
是在ma()
的两个版本中相同的代码。
如果我们希望类F
具有更多抽象类可扩展性,则F::ma
实现不应直接调用m()
,而应调用m()
的新mf()
m()
。 这样,新的抽象类可以覆盖mf()
从而再次提供新功能并调用抽象m()
。
带走
- 对抽象类进行编程非常复杂,有时很难清楚地了解谁在调用谁以及哪种实现。 如果您意识到这可能是一件复杂的事情,则可以克服这一挑战。 记录,可视化,讨论可以帮助您的任何方式。
- 当您不能解决问题时(在示例中,如何对
F
进行编码),您应该挑战环境(我们隐式地认为问题A
的类A
是不变的:“如何实现F
?”)。 - 避免复制/粘贴编程。 (面食包含大量CH,使您的代码变胖,动脉被阻塞,最后,应用程序的心脏将停止跳动。)
- 尽管在本文中没有详细介绍,但是请注意,抽象层次越深,要清楚地了解谁来呼叫谁就越困难(另请参见第1点)。
- 在https://github.com/verhas/abstractchain中找到示例演示应用程序
- 在https://github.com/verhas/javageci中找到具有这种模式的原始的,稍微复杂的应用程序
翻译自: https://www.javacodegeeks.com/2019/06/extending-abstract-classes-with-abstract-classes-in-java.html