2

我目前正在尝试使用装饰器。我创建了一个 Tank 类和两个装饰器:DoubleGunTank(射击更强大)和 FasterTank(开得更快)。他们来了:

public class Tank {
    public int shoot() {
        return 100;
    }

    public int drive() {
        return 10;
    }
}
public class FasterTank extends Tank {

    protected Tank fTank;

    public FasterTank(Tank tank) {
        fTank = tank;
    }

    public int drive() {
        return fTank.drive() * 2;
    }
}

public class DoubleGunTank extends Tank {

    protected Tank fTank;

    public DoubleGunTank(Tank tank) {
        fTank = tank;
    }
    public int shoot() {
        return fTank.shoot() * 2;
    }   
}

我想做的是用双枪和超高速装饰一辆坦克。所以我这样做:

Tank czolg = new Tank();
czolg = new FasterTank(czolg);
czolg = new DoubleGunTank(czolg);
System.out.println("Shoot: "+czolg.shoot());
System.out.println("Drive: "+czolg.drive());

但结果是:

Shoot: 200
Drive: 10

似乎只有一个装饰器激活了 DoubleGunTank 类的两种方法。所以我的问题是:我怎样才能让坦克的射击更有力,同时开得更快?

4

3 回答 3

5

您所有的装饰器都需要覆盖所有装饰对象的方法:

class FasterTank extends Tank {

    protected Tank fTank;

    public FasterTank(Tank tank) {
        fTank = tank;
    }

    public int drive() {
        return fTank.drive() * 2;
    }

    //crucial!  
    public int shoot() {
        return fTank.shoot();
    }
}

class DoubleGunTank extends Tank {

    protected Tank fTank;

    public DoubleGunTank(Tank tank) {
        fTank = tank;
    }
    public int shoot() {
        return fTank.shoot() * 2;
    }

    //crucial!  
    public int drive() {
        return fTank.drive();
    }

}

原因如下:当您拥有:

Tank czolg = new DoubleGunTank(new FasterTank(new Tank()));

而你调用czolg.drive()它实际上调用了一个DoubleGunTank类的方法——它是从Tank. 因此,不是使用修饰的目标方法,fTank而是调用DoubleGunTank.

请注意,您可以通过使用Tank接口来避免此类问题 - 这将迫使您始终装饰所有方法。此外,如果您的目标Tank类具有某些状态或在构造函数中执行某些操作,则每个装饰器(从它继承)将复制此状态并在构造函数中调用相同的代码。

更新(由 OP 本人建议):

或者,您可以使用abstract TankDecorator如下类:

abstract class TankDecorator extends Tank {
    protected final Tank fTank;

    protected TankDecorator(Tank fTank) {
        this.fTank = fTank;
    }

    @Override
    public int shoot() {
        return fTank.shoot();
    }

    @Override
    public int drive() {
        return fTank.drive();
    }
}

class FasterTank extends TankDecorator {

    public FasterTank(Tank tank) {
        super(tank);
    }

    public int drive() {
        return fTank.drive() * 2;
    }

}

class DoubleGunTank extends TankDecorator {

    public DoubleGunTank(Tank tank) {
        super(tank);
    }
    public int shoot() {
        return fTank.shoot() * 2;
    }

}

代理时遇到了这个问题——也通过从我的类继承来利用装饰器模式。基类构造函数被调用两次。请参阅:CGLIB 代理方法调用构造函数两次?Spring AOP 创建额外的 bean

于 2012-06-27T20:04:55.747 回答
2

不太确定装饰器模式,但czlog.drive()调用Tank.drive()因为DoubleGunTank没有覆盖它。

您需要覆盖超类中的每个方法并将其委托给包含fTank的方法,以便它像您希望的那样工作。

于 2012-06-27T20:03:38.973 回答
0

我发现当您将装饰器模式与具体类一起使用时,这是一个常见问题。这就是为什么我倾向于装饰接口,然而,如果你需要装饰具体的类,通常有一个基类来做这个:

public class DecoratedTank extends Tank {
   private Tank delegate;

   public DecoratedTank(Tank delegate) {
       this.delegate = delegate;
   }

   @Override
   public int shoot() {
       return delegate.shoot();
   }

   @Override
   public int drive() {
       return delegate.drive();
   }
}

然后做你的双枪坦克:

public class DoubleGunTank extends DecoratedTank {
    public DoubleGunTank(Tank delegate) {
        super(delegate);
    }

    @Override
    public int shoot() {
        return 2 * super.shoot();
    }
}
于 2012-06-27T22:47:44.160 回答