可以使用扩展到另一个类的"空"类吗?
假设我有一个类" Foo",其中包含一堆逻辑,而另一个类是" Bar",其本质上是相同的。但是,由于Foo
和Bar
是不同的(但相关)实体,因此我需要从代码中看出差异(即,我可以判断实例是Foo
还是Bar
)
当我在不加思索的情况下一起重击时,我得到了以下结果:
public class Foo { /* constructors, fields, method, logic and what-not */ } public class Bar extends Foo { /* nothing here but constructors */ }
这个可以吗?使Bar成为复合类更好吗?例如:
public class Bar { private Foo foo; /* constructors and a bunch of wrapper methods that call into foo */ }
甚至在我们使用它的同时,还有一些低技术含量的产品:
public class Foo { /* constructors, fields, method, logic and what-not */ private boolean isABar; // Could be an enum }
你怎么认为?我们如何处理这些"标记类"?
作为我的代码可能希望以不同方式对待Foo
和Bar
的示例,我的代码将需要能够执行诸如List <Foo>和List <Bar>之类的工作。 " Foo"不能进入" List <Bar>",反之亦然。
解决方案
以我的观点,最好是将" Foo"和" Bar"的子类从具有所有功能的公共祖先类(也许是" AbstractFoo")中移除。 Foo和Bar之间应该在行为上有什么区别?将该差异编码为AbstractFoo中的抽象方法,而不是在代码中使用if语句。
示例:而不是这样:
if (foo instanceof Bar) { // Do Bar-specific things }
改为执行以下操作:
class Bar extends AbstractFoo { public void specialOp() { // Do Bar-specific things } } // ... foo.specialOp();
这种方法的好处是,如果我们需要第三类,就像" Foo"一样,但有一点点区别,则不必遍历所有代码并添加编辑所有" if"语句。 :-)
绝对使用布尔值属性。这是最简单的解决方案,除非我们预见到Bar类需要在以后更改其接口(例如,覆盖其方法)。
继承是最好的。
使用布尔属性,该类必须知道两种不同类型的对象的存在,并且这不容易扩展为两个以上。而且,这种方法不允许我们重载函数。
合成使我们可以为所有功能编写包装。
这完全取决于Foo和Bar类的含义。它们代表什么,目的是什么。请说清楚。
我能想象出每种解决方案和建议的解决方案都是正确的情况。
数据不够清晰,但是基于我认为我们需要的信息,我感到困惑的是,为什么我们不简单地选择第四个选项:
Class MainStuff; Class TypeA; Class TypeB;
不能使TypeA和B从MainStuff继承,也不能使MainStuff成为TypeA和TypeB的数据成员。这取决于这三个类的含义。
如果有朝一日Foo
和Bar
可能在实现上发生分歧,那么问题将以看起来最好的方式使用继承来回答。
但是,如果我们绝对确定它们永远不会偏离,那么显然我们正在寻找应该由单个类表示的事物,例如ThingThatIsEitherFooOrBar。
有了这个类,而不是给它一个像isFoo
这样的布尔属性,最好再看一遍为什么需要区分Foo和Bar。 Foos是什么使我们对它们的处理与对Bars的处理不同?找出答案,并创建一个属性,以指定不同的信息。 Foos更大了吗?然后为size创建一个属性(即使它是一个枚举,其值为" Foo-size"和" Bar-sized")。
如果没有关于Foo和Bar的具体示例,这可以说的差不多。
Foo和Bar从FooBarImplementation继承
我将创建一个FooBarImplementation类,该类将实现Foo和Bar的常见功能。 Foo和Bar将从中得出。但是在代码中,永远不要使用FooBarImplementation类型。我的Java时代已经过去了,但是我想必须有某种方法可以从用户代码中隐藏FooBarImplementation(根据项目组织,使其成为受保护的或者仅可见的软件包。这样,没有用户代码会混和Foo酒吧(反之亦然)?
class FooBarImplementation { public void doSomething() { /* etc. */ } /* etc. */ } class Foo inherits FooBarImplementation { /* etc. */ } class Bar inherits FooBarImplementation { /* etc. */ }
Foo和由FooBarImplementation组成的Bar
另一种可能性是使Foo和Bar将它们的每个方法转发给内部类(同样,FooBarImplementation)。这样,用户代码就不可能是Foo和Bar。
class FooBarImplementation { public void doSomething() { /* etc. */ } /* etc. */ } class Foo { private FooBarImplementation fooBarImplementation = new FooBarImplementation() ; public void doSomething() { this.fooBarImplementation.doSomething() ; } /* etc. */ } class Bar { private FooBarImplementation fooBarImplementation = new FooBarImplementation() ; public void doSomething() { this.fooBarImplementation.doSomething() ; } /* etc. */ }
不要使Foo从Bar继承(反之亦然)
Shoudl Foo从Barn继承而来,就语言而言,Foo将是一家酒吧。不这样做,我们将失去对象之间的差异,而这正是我们所不想要的。
不要使用布尔值和whataver类型字段
这是我们可能遇到的最糟糕的想法。 Bjarne Stroustrup警告不要针对C ++使用这种反模式,而C ++不仅涉及OOP。因此,我猜想这种模式对于Java来说甚至更"反" ... :-)
就像其他人所说的,这取决于,但是如果Foo和Bar之间具有共同的功能,并且功能上的差异可以表示为参数,那么我投票支持子类。
从根本上讲,这就是我们在实践软件课程结束时所做的事情。
我们应该实现一个数独游戏,最后,我们有了一个" AbstractPlayfield",它能够将任意规则集应用于任意游戏场。这个AbstractPlayfield是我们应该实现的各个变体的子类。这些子类为抽象的游戏场设置参数(主要是规则和棋盘的形状),所有内容都像一个饰物一样工作。我们甚至最终在这些子类中继承了更多的继承权,因为其中一些变体包含规则"数字在一行中必须唯一"和"数字在列中必须唯一"。
使用该代码,我们能够在大约3天内完成大约2个月的估计工作:)(并且,他们用"测试那些微小的属性设置类,因为我们可能在那里有bug!测试它们!他们!我们不在乎所有重要的逻辑都经过测试!"。)
另一方面,如果Bar类与Bar没有特殊的不同功能,那么我至少从我们提供给我的数据中看不到添加它的意义。如果我们想基于类型进行一些操作并根据类型进行分派,这可能是有道理的,但是我无法从Foo和Bar上读到它。在这种情况下,由于YAGNI,我不会创建Bar。
基本上,我们需要应用策略模式。
如果Foo和Bar在行为上没有区别,则类名" Foo"不够抽象。确定Foo和Bar之间的通用抽象,并相应地重命名该类。然后在类中提供一个成员字段,以将实例标识为" Foo"," Bar"等。如果希望将可能的值限制为" Foo"和" Bar",请使用一个枚举。