Java 模型对象实现行为(接口)的设计是否很糟糕?

Java 模型对象实现行为(接口)的设计是否很糟糕?,java,oop,model,Java,Oop,Model,对不起,如果我的问题真的很琐碎。但是我不认为我擅长“设计”,所以我想了解一下这个话题 假设我有一个空的接口水果,有不同的java实现,如Apple、Mango、Orange等 我有一个果篮,里面有各种水果的设置器,例如 basket.setApple(Apple a); basket.setOrange(Orange o); 等等 现在,当我需要为篮子设置特定的水果时,我需要调用正确的设置器。一种方法是使用一个实用工具服务,它将迭代所有te模型对象(结果),并调用篮子上相应的setter 像这

对不起,如果我的问题真的很琐碎。但是我不认为我擅长“设计”,所以我想了解一下这个话题

假设我有一个空的接口水果,有不同的java实现,如Apple、Mango、Orange等

我有一个
果篮
,里面有各种水果的设置器,例如

basket.setApple(Apple a);
basket.setOrange(Orange o);
等等

现在,当我需要为篮子设置特定的水果时,我需要调用正确的设置器。一种方法是使用一个实用工具服务,它将迭代所有te模型对象(结果),并调用篮子上相应的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);
        }

     }
  }

}
现在所有的水果(被认为是哑模型对象)都以这种方式实现这个接口

public class Apple implements Fruit {
 ....
 ....
 ....
 public void setBasket(FruitBasket b) {
   b.setApple(this);
 }
}
FruitBasket f =  new FruitBasket();
List<Fruit> fruits = getAllFruits();

for(Fruit fruit: fruits)
 {
     fruit.setBasket(f);
 }
 ........
 ........
现在我们不再需要水果实用程序类,只要调用水果实用程序的setAllFruits,我们就可以像这样简单地使用sniplet

public class Apple implements Fruit {
 ....
 ....
 ....
 public void setBasket(FruitBasket b) {
   b.setApple(this);
 }
}
FruitBasket f =  new FruitBasket();
List<Fruit> fruits = getAllFruits();

for(Fruit fruit: fruits)
 {
     fruit.setBasket(f);
 }
 ........
 ........
果篮f=新果篮();
列出水果=getAllFruits();
用于(水果:水果)
{
果肉(f);
}
........
........
我的团队领导说这是一个非常糟糕的设计,因为我已经“稀释”了模型对象。我们的模型被认为是一个“愚蠢”的对象,只是用来传输数据。因此,我们应该坚持基于效用的方法,或者除此之外的任何其他方法

做事情的正确/最佳方式是什么(至少在JAVA中)


提前感谢。

这取决于周围的工具链和/或开发风格,如果您有一个愚蠢的数据结构“模型”或一个实现业务逻辑的模型-在这两个极端之间有许多区别,那么这个问题通常不可回答。但是实用类方法有一个主要的缺点,那就是关注点分离,因为在果篮类中,没有人在不检查哪些类访问果篮的情况下直接知道这种果篮分离是如何实现的,因此在最坏的情况下,有人在其他地方实现了相同的算法,因为他不知道这个实用程序类。

我认为您的团队负责人提出了一个有效的观点。您正在将功能从
果篮
移动到
水果
s,但是
水果
s甚至不应该知道
果篮
。如果您有一个
水果袋
和一个
水果盒
,会发生什么?无论何时引入这样一个类,都必须更改每个
实现者

使用正确的setter函数的逻辑是内部的,应该保持在那里

现在谈谈“最佳方式”。在这里我没有想到最好的解决办法。必须在某处对水果类型进行区分。
if。。否则
可能不是最漂亮的解决方案,但看起来还可以。不过最好使用
enum
开关/case
。这样,当您忘记一种类型时,就会收到编译器警告


通常,整个
setApple
vs
setOrange
方法是问题的原因。这样就不需要苹果和橙子来实现水果。但没有一个简单且普遍可行的方法可以解决这个问题。它总是取决于细节。

我认为在这种情况下,水果应该是“哑巴”。今天你把它们放到篮子里,明天你会在商店里卖,一周后你会把它们拌成沙拉:)

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

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

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

例如:

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());
.....
Map,FruitBasketAdder>registry=newHashMap();
registry.put(Orange.class,新的OrangeBasketader());
registry.put(Apple.class,新的applebasketader());
.....
最后一点是使用这个注册表。比如说,您有一个通用方法,它接受一个“水果”列表作为参数,并尝试将其添加到篮子中,就像您在示例中用“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);

    }
}
public void将水果添加到篮子(列出水果、篮子){
用于(水果:水果){
BasketAdder adder=registry.get(fruit.getClass());
加法器。添加水果(水果、篮子);
}
}
这种设计比“如果”要好得多,因为它允许动态添加新种类的水果,而不会破坏接口。 只需创建一个BasketAdder,将其传递到注册表,完成后,主算法(遍历+添加到篮子)保持不变

标记


希望这有助于

显示
果篮的代码
将有助于让这个问题更清楚,至少对我来说是这样。