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

时间:2009-11-17 15:04:59

标签: java refactoring abstract-class

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

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

您怎么看?

编辑:

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

public abstract class EmptyAbstractClass {}

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

11 个答案:

答案 0 :(得分:18)

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

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

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

答案 1 :(得分:7)

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

答案 2 :(得分:6)

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

在理想的世界中,您只能使用接口进行建模。例如: Vehicel - >汽车 - >庞蒂亚克。

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

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

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

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

一个警告;如果你使用很多代理类,比如Spring和一些测试框架,那么并行接口层次结构可能会减少意外错误。

答案 3 :(得分:5)

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

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

答案 4 :(得分:2)

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

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

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

答案 5 :(得分:2)

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

  1. 等到你需要扩展另一个类以满足实际需要并被抽象类烦恼以用接口替换它。
  2. 不要等待并修复设计。
  3. 在这种特殊情况下,实际设计确实不会造成伤害,因为现在,您可以忍受它。但是,我认为替换这些抽象类是一个非常简单的重构(使它们成为接口并用extends替换implements,在那里你会遇到编译错误)我真的看不出有什么东西会被破坏所以我会去吧。

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

答案 6 :(得分:1)

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

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

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

答案 7 :(得分:1)

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

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

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

答案 8 :(得分:0)

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

interface Fooable
{
   void foo();
   void bar();
}

abstract class AbstractFoo
    implements Fooable
{
}

class Foo
    extends AbstractFoo
{
   public void foo()
   {
   }

   public void bar()
   {
   }
}

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

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

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

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

如果是我的代码,我会把它们改为接口...

答案 9 :(得分:0)

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

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

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

答案 10 :(得分:0)

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

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