【问题标题】:How do you make a utility class handle differences in subclasses?您如何使实用程序类处理子类中的差异?
【发布时间】:2017-01-25 23:58:24
【问题描述】:

这可能是一个不好的例子,但请使用它。我有一个超类Cake 和两个子类:ExampleCakeSaleCake。我还有一个Baker,他可以对蛋糕进行逆向工程以烘烤它的副本,并且知道之后如何处理它。

(全部伪代码)

public class Cake{
   radius; 
   height;
   ingredients;
   color;
   name;
}

public class ExampleCake extends Cake{
   shelfLocation;
}

public class SaleCake extends Cake{
   owner;
}

public class Baker{
   Cake bakeCake(Cake);
   Cake bakeSaleCake(Cake, Owner);
   Cake bakeExampleCake(Cake, Location);

   void handleCake(Cake);
}

面包师需要知道如何处理通用 Cakes、ExampleCakes 和 SaleCakes。所以他的 bakeCake 函数看起来像这样:

Cake bakeCake(cake){
   newCake = cake.copy();
   mixIngredients(newCake);
   putCakeInOven(newCake);
   putIcing(newCake);
   return cake;
}

Cake bakeSaleCake(cake, owner){
   newCake = bakeCake(cake);
   newCake.setOwner(owner);
   return newCake;
}

Cake bakeExampleCake(cake, location){
   newCake = bakeCake(cake);
   newCake.setLocation(location);
   return newCake;
}

void handleCake(cake){
   if(cake instanceof ExampleCake)
      putOnShelf((ExampleCake)cake);

   else if(cake instanceof SaleCake)
      giveToCustomer((SaleCake)cake);

   else
      putOnTable(cake);
}

我的问题是 Baker 被硬编码为只能处理某些类型的蛋糕。如果出现新类型的蛋糕,他将无法处理它们。是否有一种干净、通用的方法来处理它,或者我上面的伪代码是否有点“足够好”(例如,它不会伤害你的眼睛或心脏)?

谢谢。

【问题讨论】:

  • 好像是可克隆模式,你只需要让Cake类实现复制自己的方法。
  • 就蛋糕创作而言是有道理的。我忘了添加“handleCake”部分,我将其附加到伪代码的末尾。
  • handleCake 部分可能可以使用访问者模式重构。
  • 像这样在类型上切换是一种代码气味恕我直言。如果您需要知道这样的确切类型,则永远不要“忘记”它。
  • 使用访问者模式,我想我仍然会有句柄(SaleCake)和句柄(ExampleCake),所以它仍然是一个类似的问题,无法处理新类型的蛋糕。

标签: java inheritance subclass


【解决方案1】:

要复制蛋糕,您可以使用复制构造函数。这样,它如何复制自己取决于特定的蛋糕。要处理任意蛋糕,您可以使用访问者模式。通过访问者,您可以“询问”蛋糕的类型,而不是使用 instanceof 检查类型。

代码可能如下所示(除了复制构造函数之外,您还需要其他 Cake 构造函数):

//蛋糕类

public class Cake{
   int radius; 
   int height;

   public Cake(Cake cake){
       this.radius = cake.radius;
       this.height = cake.height;
   }

   <R> R accept(CakeVisitor<? extends R> visitor){
       return visitor.visit(this);
   }

}

//示例蛋糕类

public class ExampleCake extends Cake{
   String shelfLocation;

   public ExampleCake(Cake cake, String shelfLocation){
       super(cake);
       this.shelfLocation = shelfLocation;
   }

   <R> R accept(CakeVisitor<? extends R> visitor){
       return visitor.visit(this);
   }

}

//SaleCake 类

public class SaleCake extends Cake{
   String owner;

   public SaleCake(Cake cake, String owner){
       super(cake);
       this.owner = owner;
   }

   <R> R accept(CakeVisitor<? extends R> visitor){
       return visitor.visit(this);
   }

}

//访客界面

public interface CakeVisitor<R> {

    R visit(Cake cake); 
    R visit(SaleCake cake);
    R visit(ExampleCake cake);

}

//贝克类

public class Baker {
    private final CakeVisitor<Void> cakeHandler = new CakeVisitor<Void>(){

        @Override
        public Void visit(Cake cake) {
            putOnTable(cake);
            return null;
        }

        @Override
        public Void visit(SaleCake cake) {
            giveToCustomer(cake);
            return null;
        }

        @Override
        public Void visit(ExampleCake cake) {
            putOnShelf(cake);
            return null;
        }

    };

    Cake bakeCake(Cake cake){
       return processedCake(new Cake(cake));
    }

    SaleCake bakeSaleCake(Cake cake, String owner){
       return processedCake(new SaleCake(cake, owner));
    }

    ExampleCake bakeExampleCake(Cake cake, String location){
       return processedCake(new ExampleCake(cake, location));
    }

    void handleCake(Cake cake){
        cake.accept(cakeHandler);
    }

    private <C extends Cake> C processedCake(C cake){
        mixIngredients(cake);
        putCakeInOven(cake);
        putIcing(cake);
        return cake;
    }
}

现在,如果您想使用 Baker 处理一种新型蛋糕,除非您提供专门接受新蛋糕类型对象的访问方法,否则它将默认放在桌子上。我认为你真的不能让它比这更通用,因为当你有一种新类型的 Cake 时,你想以不同于其他类型的蛋糕的方式处理它,并且处理它的新代码必须去某个地方。像 putOnTable() 这样的代码不应该放在 cake 类中,因为蛋糕不知道它是如何放在桌子上的 - 面包师知道 -> 因此是访问者模式。

【讨论】:

  • 这很整洁。但是它不能在私有 C processesCake(C cake) 方法中编译。我创建了 mixIngrediant() 并说“引用缺少的类型 C”。什么是“C”类或语法是什么?感谢您写这篇文章。
  • @NickZiebert mixIngredients 方法和其中的其他方法(我没有包括在内)必须接受Cake 类型的蛋糕。然后它编译,因为泛型类型参数 C 被定义为 cake 的后代(Cake 的后代可以在需要超类 Cake 的地方使用)。 processedCake 返回传递给它的蛋糕,保留其静态类型。我尝试在烘焙方法中保存几行。
【解决方案2】:

良好的设计是要确定哪些行为发生了变化,哪些行为保持不变,然后将这两组分开。这里改变的是面包师对每个蛋糕的行为。所以我们想创建一个新的类来封装他不断变化的行为。这是我们的做法。使用一个名为 doBehavior() 的方法创建一个名为 BakerBehavior 的接口。然后,创建一个名为 PutOnShelfBehavior 的类和另一个名为 GiveToCustomerBehavior 的类,它们都实现了 BakerBehavior。覆盖 doBehavior() 方法。

现在,您可以在每个蛋糕类中创建一个 PutOnShelfBehavior 对象或 GiveToCustomerBehavior 对象。您的蛋糕超类将有一个 act() 方法,如下所示:

//Constructor {
myBehaviorObject=new PutOnShelfBehavior();
}

public void act() {
    myBehaviorObject.doBehavior();
}

现在,在您的 Baker 类中,您的 handleCake 方法将如下所示:

private void handleCake(Cake cake) {
    cake.act();
}

我看不出有任何其他方法可以做到这一点。这样做的好处是 A)不必在该 handleCake 类中硬编码蛋糕类型,B)您不必重复任何代码(例如,也许您有 2 种不同的蛋糕类型放在架子上)。 C)每个蛋糕对面包师一无所知,它只知道它将被放在架子上或其他什么东西上。我希望有人能告诉我是否有更好的方法,我对这个问题很感兴趣。

【讨论】:

    猜你喜欢
    • 2012-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-30
    • 1970-01-01
    • 2011-10-13
    相关资源
    最近更新 更多