空抽象类是不好的做法,为什么?

我们的代码库中有几个空的抽象类。 我觉得很难看。 但除了这个非常愚蠢的原因(丑陋)之外,我应该重构它(例如空的界面)吗?

否则,代码是健壮的并经过充分测试。 因此,如果它只是出于“美学”的原因,我将通过并让空的抽象类保持不变。

你怎么看?

编辑:

1)通过“空抽象类”,我的意思是:

public abstract class EmptyAbstractClass {} 

2)“空虚”的原因:Hibernate。 我根本没有掌握这个持久性框架。 我只是理解一个接口不能映射到一个表,并且由于这个技术原因,一个类比接口更受欢迎。

听起来像这样是创建一个对象层次结构的结果,最终没有任何常见的function在它的最顶层。 我怀疑直接的子类本身是抽象的,或者至少有自己的子类。 另一个可能性是你的代码中散布着很多函数实例。

空的最高级别本身并不是一个大问题,但我会检查以确认实际上不存在共同的function。 假设它确实存在,我会考虑在父类中提取常用function。 我肯定会留意一下,并认真考虑重构它(例如,参考Refactoring to Patterns(Kerievsky))。

在这种情况下,接口更可取,因为它使您的代码更易于维护。 也就是说,您只能扩展单个类,但可以实现许多接口。

如果现在绝对没有直接影响,我就不会碰它。 如果维护事件出现需要您进行更改,那么我会重构它,因为我已经在代码中。

换句话说,如果没有破坏,请不要修理它。

要问的问题是:“我想用这些代码做什么,我不能做,或者很难做到,因为这些空的抽象类在那里?” 如果答案是“什么都没有”,你应该让他们独自一人。 如果答案是“某事”,那么删除它们可能是值得的 – 但如果可以,请先与创建它们的人交谈,以确保它们没有一些微妙的目的。 例如,也许您的代码使用reflection来查找特定ABC的所有实例并对它们执行特殊操作,在这种情况下,您的重构可能会以微妙的方式破坏代码。

它不一定比替代方案更难看,它可能重复代码。

在理想的世界中,您将能够仅使用接口进行建模。 例如:Vehicel – > Car – > Pontiac。

但是对于所有车辆可能存在相同的逻辑,因此界面是不合适的。 而且你没有专门针对汽车的逻辑。 但是你确实需要Car抽象,因为您的TrafficLightController想要区分汽车和自行车。

在这种情况下,你需要制作和抽象汽车。

或者你可以创建一个界面Vehicle,一个VehicleImpl实现Vehicle,一个接口Car extends Vehicle,一个接口Pontiac实现Car,一个PontiacImpl实现Pontiac扩展VehicleImpl。 我个人不喜欢并行的接口层次结构,以防止空抽象类多于空抽象类。

所以我想这是一个品味问题。

一个警告; 如果您使用许多代理类(如Spring和一些测试框架),并行接口层次结构可能会导致意外错误减少。

问自己这个问题:如果,由于你的重构,生产中断了一些东西而你的继续就业取决于你认为你决定花时间修理一些实际上没有破坏的东西,你说的是什么?

“这对我来说是丑陋和审美的冒犯”并不是我想把工作放在一边的答案。

在这个阶段,我说要安全地玩,和丑人一起生活。

空抽象类对我没有任何意义,应该使用抽象类来inheritance某些行为。 因此,我倾向于同意,这是一个非常丑陋的设计和抽象类的非常糟糕的使用,标记接口应该是首选。 所以你有两个选择:

  1. 等到你需要扩展另一个类以满足真正的需求并且被抽象类烦恼以用接口替换它。
  2. 不要等待并修复设计。

在这种特殊情况下,实际设计确实没有真正伤害到现在,所以你可以忍受它。 但是,我认为替换这些抽象类是一个非常容易的重构(使它们接口和替换extendsimplements ,你得到编译错误),我真的不能看到什么可能会被破坏所以我会去它。

就个人而言,人们当然可能不同意,我不喜欢过于防守。 有了规则, 如果它没有破坏,不要修复它 ,你永远不会重构任何东西(“我的测试通过,我为什么要重构?”)。 冻结代码肯定不是正确的解决方案, 积极的测试是正确的解决方案

根据面向对象编程理论,inheritance的主要目的是多态,代码重用和封装。 一个空的抽象类(当我说这意味着真的是空的,没有构造函数,没有方法,没有属性。没有!)没有达到这种编程技术所希望的三个目标中的任何一个。 它相当于if(true){...} 。 将其更改为界面并不会让它变得更好。

如果你想重构代码,我会建议你按照与你正在思考的方向相反的方向思考,我的意思是:尝试从共享抽象父类的所有类中抽象属性,方法和构造函数。

这是一项艰苦的工作,在短期内几乎没有任何回报,但它会大大提高代码的可维护性,因为核心逻辑更改只需要进行一次。 我怀疑使用那些空抽象类的原因是识别一组必须共享某些东西的类,否则Object和抽象类之间会有什么区别

关键是你可以只从一个抽象类扩展,同时你可以实现更多的接口。

显然,“空抽象类”设计决策是为了防止实现类从另一个类扩展。

如果是我,我会放手,否则它可能会破裂。 最好仍然是联系原始开发人员并要求推理(并将其作为评论添加到抽象类中,以便您和将来使用)。

如果您有以下模式,您会发现它是最灵活的:

 interface Fooable { void foo(); void bar(); } abstract class AbstractFoo implements Fooable { } class Foo extends AbstractFoo { public void foo() { } public void bar() { } } 

这样你总是可以通过接口传递,但如果你以后发现你有可以共同的代码,你可以将它放在抽象类中,而不必对所有类进行更改。

除非你有充分的理由不做那种模式(我怀疑你不是这种情况)我建议使用它。 这可以结束空的抽象类,但我认为它是好的(有点奇怪,但没关系)。

如果接口中确实没有方法或只有一个方法,那么我会跳过抽象类。

从编译器/function的角度来看,接口和抽象类之间没有真正的区别,其中所有方法都是抽象的。 界面将比抽象类更灵活,界面+抽象类将是最灵活的。

如果是我的代码我会改变它们作为接口…

显而易见的问题是,它为什么放在那里? 它是如何使用的?

我的猜测是,(a)这是一些错误开始的遗迹。 早期的草稿在抽象类中有一些数据或函数,但是当程序员工作时,这些都被移动到其他地方,直到除了空壳之外什么都没有。 但更有可能(b)在某些时候有代码说“if(x instanceof …”。我认为这是糟糕的设计,基本上使用类成员身份作为标志。如果你需要一个标志,创建一个标志,不要假装你通过创建一个类或一个接口然后将它作为一个标志来“更加面向对象”。这可能适合一些教科书定义更好的编码,但实际上只会使代码更加混乱。

+1给Monachus指出空接口并不比空抽象类好。 是的,是的,我知道Sun自己也使用了Cloneable,但我认为这是解决这个问题的一个蹩脚的解决方案。

虽然我没有得到空表被映射到的表中的内容,但如果该类用于某种目的,那么保留它,直到你有机会重构一些。

我肯定会做的是:写一个关于为什么这个类存在于文件中的注释。 干净和“漂亮”代码的一个重要原因是不让其他开发人员思考。 注释可以帮助解决这个问题,即使代码不是那么漂亮。