处理这个问题的正确设计是什么?

时间:2012-10-02 14:20:51

标签: java oop design-patterns refactoring

我正在开发一个遗留Java应用程序,它可以解决“水果”和“蔬菜”问题。 它们在内部被视为不同的东西,因为它们没有共同的所有方法/属性,但很多东西都完全类似于它们。

所以,我们有很多方法 doSomethingWithAFruit(Fruit f) doSomethingWithAVegetable(Veg v),它们使用正确的 doOtherStuffWithAFruit(Fruit f)< / em> / doOtherStuffWithAVeg(Veg v)。这些非常相似,只不过用水果做事的方法只调用用水果做事的方法,对蔬菜做同样的事情。

我想重构这一点以减少重复,但我不确定实现这一目标的最佳方法是什么。我已经阅读了一些关于某些设计模式的内容,但我不知道它是否让我更清楚。 (我可以识别我使用的代码中的一些模式,但我真的不知道何时应该使用模式来改进周围的事情。也许我应该阅读更多有关重构自身的内容......)

我在考虑这两个选项:

1。创建一个可以拥有Fruit或Vegetable实例的类,并将其传递给方法,尝试最小化重复。它会是这样的:

public void doSomething(Plant p) {
   // do the stuff that is  common, and then...
   if (p.hasFruit()) {
       doThingWithFruit(p.getFruit());
   } else {
       doThingWithVegetable(p.getVegetable());
   }
}

这会让事情变得更好,但我不知道......它仍然感觉不对。

2。我想到的另一个选择是在水果和蔬菜中添加一个与他们共同的东西,并使用它来传递它。我觉得这是更清洁的方法,虽然我需要使用instanceof并在需要特定于他们的东西时施放到水果/蔬菜。

那么,我还能在这做什么呢?这些方法的缺点是什么?

更新:请注意,问题有点简化了,我正在寻找用“植物”做事的方法,也就是主要“使用”它们代替做事的代码给他们。话虽如此,我所提到的那些类似方法不能在“Plants”类中,并且它们通常有另一个参数,如:

public void createSomethingUsingFruit(Something s, Fruit f);
public void createSomethingUsingVegetable(Something s, Vegetable v);

即,除了水果/蔬菜之外,这些方法还有其他问题,并且不适用于任何水果/蔬菜类。

UPDATE 2:这些方法中的大多数代码只从Fruit / Vegetable对象中读取状态,并根据相应的类型创建其他类的实例,存储在数据库中等等 - < em>从我对评论中的问题的回答中我认为这很重要

6 个答案:

答案 0 :(得分:2)

我认为第二种方法会更好。设计界面总是一种更好的设计方式。这样你就可以轻松切换实现..

如果使用接口,则不需要进行类型转换,因为您可以轻松利用polymorphism的概念。也就是说,您将有`基类引用指向子类对象.. < / p>

但是,如果您只希望在接口中保留fruitsvegetables的常用方法,以及实现类中的特定实现。那么在这种情况下,typecasting将是必需的。

所以,你可以在接口级别拥有一个通用方法。在实现级别有更具体的方法..

public interface Food {
    public void eat(Food food);
}

public class Fruit implements Food {

    // Can have interface reference as parameter.. But will take Fruit object    
    public void eat(Food food) {
        / ** Fruit specific task **/
    }
}

public class Vegetable implements Food {

    // Can have interface reference as parameter.. But will take Vegetable object
    public void eat(Food food) {
        /** Vegetable specific task **/
    }
}

public class Test {
    public static void main(String args[]) {
         Food fruit = new Fruit();
         fruit.eat(new Fruit());    // Invoke Fruit Version

         Food vegetable = new Vegetable();
         vegetable.eat(new Vegetable());   // Invoke vegetable version

    }
}

好的,我修改了一个代码,使eat()方法获取Food类型的参数..这不会产生太大的影响。你可以将Vegetable对象传递给Food参考..

答案 1 :(得分:1)

如果您具有分别针对水果和蔬菜的功能,并且使用这两种类型的客户必须区分(使用instanceof) - 这是一致性与耦合问题。

也许考虑一下这些功能是不是更好地放在水果和蔬菜附近而不是客户端。然后,客户端可能以某种方式被称为功能(通过通用接口)而不关心他正在处理什么实例。至少从客户的角度来保留多态性。

但这是理论上的,可能不实用或对您的用例进行过度设计。或者你最终可能只是将instanceof隐藏在设计中的其他位置。当你开始在水果和蔬菜旁边有更多的继承兄弟姐妹时,instanceof将是一件坏事。然后你会开始违反Open Closed Principle

答案 2 :(得分:1)

您可以使用的另一个选项,或者可能包含它作为解决方案的一部分,是询问消费者是否可以管理您传递的对象。此时,消费者有责任确保它知道如何处理发送它的对象。

例如,如果您的消费者被称为Eat,您可以执行以下操作:

Consumer e = new Eat();
Consumer w = new Water();
if( e.canProcess( myFruit ) )
   e.doSomethingWith( myFruit );
else if ( w.canProcess( myFruit ) )
   w.doSomethingWith( myFruit );

.... etc

但是你最终得到了很多/其他类,所以你自己创建一个工厂来确定你想要的消费者。您的工厂基本上执行if / else分支以确定哪个消费者可以处理您传递的对象,并将消费者返回给您。

所以它看起来像

public class Factory {
   public static Consumer getConsumer( Object o ){
    Consumer e = new Eat();
    Consumer w = new Water();
    if( e.canProcess( o ) )
       return e;
    else if ( w.canProcess( o ) )
       return w;
   }
}

然后您的代码变为:

Consumer c = Factory.getConsumer( myFruit );
c.doSomethingWith( myFruit );

当然,在消费者的canProcess方法中,它基本上是一个实例或一些其他函数,用于确定它是否可以处理您的类。

public class Eat implements Consumer{
   public boolean canProcess(Object o ){
     return o instanceof Fruit;
   }
}

因此,您最终将责任从类转移到Factory类,以确定可以处理哪些对象。当然,诀窍在于所有消费者都必须实现一个通用接口。

我意识到我的伪代码是非常基本的,但它只是指出了一般的想法。根据您的类的结构,这可能会或可能不会在您的情况下工作和/或变得过度,但如果设计得很好,可以显着提高您的代码的可读性,并真正保持每个类型的所有逻辑自包含在他们自己的类中没有instanceof,if / then遍布各处。

答案 3 :(得分:0)

我会创建和抽象基类(比方说 - 食物,但我不知道你的域名,其他东西可能更合适)并开始逐个迁移方法。

如果你看到'doSomethingWithVeg'和'doSomthingWithFruit'略有不同 - 在基类创建'doSomething'方法并使用抽象方法只做不同的部分(我猜主要的业务逻辑可以是联合起来,只有像写入DB /文件这样的小问题是不同的。)

当你准备好一个方法时 - 测试它。在你确定它没问题之后 - 转到另一个。完成后,Fruit和Veg类不应该有任何方法,而是抽象方法的实现(它们之间的细微差别)。

希望有所帮助......

答案 4 :(得分:0)

这是一个基本的OOD问题。因为水果和蔬菜属于植物类型。 我建议:

interface Plant {
    doSomething();
}

class Vegetable {
    doSomething(){
    ....
    }
}

和水果一样。

在我看来,doOtherStuff方法应该是相关类的私有方法。

答案 5 :(得分:0)

您还可以考虑让它们都实现多个接口,而不只是一个。这样你就可以根据情况对最有意义的界面进行编码,这有助于避免投射。 Comparable<T>所做的那种。它有助于方法(比如排序对象的方法),它们不关心对象是什么,唯一的要求是它们必须具有可比性。例如在你的情况下,两者都可以实现一些名为Edible的接口,然后将它们都作为Edible,并期望Edible