可以使用扩展到另一个类的"空"类吗?

时间:2020-03-06 14:49:10  来源:igfitidea点击:

假设我有一个类" Foo",其中包含一堆逻辑,而另一个类是" Bar",其本质上是相同的。但是,由于FooBar是不同的(但相关)实体,因此我需要从代码中看出差异(即,我可以判断实例是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
}

你怎么认为?我们如何处理这些"标记类"?

作为我的代码可能希望以不同方式对待FooBar的示例,我的代码将需要能够执行诸如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的数据成员。这取决于这三个类的含义。

如果有朝一日FooBar可能在实现上发生分歧,那么问题将以看起来最好的方式使用继承来回答。

但是,如果我们绝对确定它们永远不会偏离,那么显然我们正在寻找应该由单个类表示的事物,例如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",请使用一个枚举。