你的模型对象实现一个行为(接口)是不好的设计吗?

3
抱歉,如果我的问题非常琐碎。但我不认为我擅长“设计”,所以想要一些关于这个话题的见解。
假设我有一个空接口Fruit,具有不同的Java实现,如Apple、Mango、Orange等。
我有一个FruitBasket,它有所有种类水果的setter,即
basket.setApple(Apple a);
basket.setOrange(Orange o);

等等。

现在,当我需要为篮子设置特定的水果时,我需要调用正确的setter。一种做法是拥有一个实用程序服务,它将迭代所有模型对象(水果)并在篮子上调用相应的setter。

类似这样。

public class FruitUtilty
{

 public static void setAllFruits(FruitBasket basket, List<Fruits> fruits)
  {
    for(Fruit fruit : fruits)
     {
        if(fruit.getClass() == Apple.class)
        {
            basket.setApple((Apple)fruit);
        }
        else if(fruit.getClass() == Mango.class)
        {
            basket.setMango((Mango)fruit);
        }
        else if(fruit.getClass() == Orange.class)
        {
            basket.setOrange((Orange)fruit);
        }

     }
  }

}

但我看到的问题是一个丑陋的if/else条件语句。每次我们添加一个模型对象,这个实用程序都必须被修改。

因此,我提出了一种设计,在这种设计中,接口不会是空的,而是有一个“setBasket”行为供所有水果实现。

public interface Fruit
{
   public void setBasket(FruitBasket b);
}

现在所有水果(它们应该是简单的模型对象)都以这种方式实现此接口。例如,苹果会像这样:

public class Apple implements Fruit {
 ....
 ....
 ....
 public void setBasket(FruitBasket b) {
   b.setApple(this);
 }
}

现在我们不再需要 FruitUtility 类,无论何时调用 FruitUtility 的 setAllFruits,我们都可以简单地使用以下代码片段。
FruitBasket f =  new FruitBasket();
List<Fruit> fruits = getAllFruits();

for(Fruit fruit: fruits)
 {
     fruit.setBasket(f);
 }
 ........
 ........

我的团队领导说这是一个非常糟糕的设计,因为我已经“稀释”了模型对象。我们的模型只应该是一个“哑”对象,只能携带数据。所以我们应该坚持使用基于实用程序的方法或其他任何方法而不是这种方法。

在JAVA中做事情的正确/最佳方式是什么?

提前感谢。


展示FruitBasket的代码会让这个问题更清晰,至少对我来说是这样。 - Don Roby
3个回答

3

我认为在这种情况下,水果应该是“愚蠢的”。今天你将它们添加到篮子里,明天你会在商店里出售它们,一周后你会把它们混合到沙拉中:)

如果在Fruits接口中添加一个“setBasket”方法,这两个概念将在接口级别上耦合在一起,这是不好的。 另一方面,如果不可扩展,就像您所述的那样。

我认为最好的做法(至少这是我如何解决此问题的方法)是引入另一个抽象概念,比如“操作”,它表示可以对水果进行的操作。

如果我们谈论篮子,我认为您可以创建一个通用接口,并为每种水果实现它。

示例:

public interface FruitBasketAdder {

     void addFruit(Fruit fruit, Basket basket);
}


public class AppleBasketAdder implements FruitBasketAdder {

     public void addFruit(Fruit fruit, Basket basket) {

          Apple apple = (Apple) fruit;
          basket.addApple(apple);
          ....
     }
}

public class OrangeBasketAdder implements FruitBasketAdder {
     public void addFruit(Fruit fruit, Basket) {

          Orange orange = (Orange) fruit;
          basket.addOrange(orange);
          ....
     }

}

现在您可以创建一个注册表,用于指定可以处理所有种类水果的策略。就像这样:
Map<Class<Fruit>>, FruitBasketAdder> registry = new HashMap<>();
registry.put(Orange.class, new OrangeBasketAdder());
registry.put(Apple.class, new AppleBasketAdder());
.....

最后一点是使用这个注册表。假设你有一个通用方法,它接受一个“水果”列表作为参数,并尝试像你在“if”示例中所述的那样将其添加到篮子中。因此,您应该从注册表中找到一个已分配的“BasketAdder”(假设您可以访问注册表的引用),并通过调用相关的BasketAdder将水果添加到篮子中。这样做:

public void addFruitsToTheBasket(List<Fruit> fruits, Basket basket) {

    for(Fruit fruit : fruits) {

        BasketAdder adder = registry.get(fruit.getClass());
        adder.addFruit(fruit, basket);

    }
}

这种设计比"if"好得多,因为它允许动态添加新的水果种类而不会破坏界面。只需创建一个BasketAdder,将其传递给注册表即可完成操作,主算法(遍历+添加到篮子中)保持不变。

马克

希望对你有所帮助。


1
我认为你的团队领导提出了一个很有道理的观点。你正在将功能从移动到中,但甚至不应该知道的存在。如果你还有一个和一个会发生什么?每当你引入这样一个类时,你都必须改变每个Fruit实现者。正确使用setter函数的逻辑是在FruitBasket内部,并且应该保留在那里。
现在谈谈“最好的方法”。我脑海中没有最好的解决方案。水果类型的区分必须在某个地方完成。if...else可能不是最美观的解决方案,但看起来还可以。然而,使用枚举和switch/case可能更好。这样,当你忘记一种类型时,你会得到编译器警告。
通常整个setApple vs setOrange方法是问题的根源。这样就没有必要让苹果和橙子实现Fruit。但是并没有一个易于推荐的简单方法来解决这个问题。它总是取决于具体情况。

0

这取决于周围的工具链和/或开发风格,如果你有一个“模型”数据结构或者实现业务逻辑的数据结构 - 在这两个极端之间有许多不同点,因此这个问题通常无法回答。但是实用类方法有一个主要的缺点,那就是关注点分离,因为看着水果篮子类,没有人可以直接得知这个水果分离是如何实现的,除非查看哪些类访问了水果篮子,以至于在最坏的情况下,某人会在其他地方实现相同的算法,因为他不知道这个实用类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接