关于Java:当我装饰对象时,变量重置为默认值

Variable resets to default value when I decorate object

我正在学习装饰设计师的设计模式。在这个例子中,我创建了一个应用程序,它根据咖啡的类型(浓缩咖啡、脱咖啡因、家庭用混合咖啡)、大小(高、大、通风)和调味品装饰器(大豆、生奶油、蒸牛奶)来计算咖啡的成本——一些代码因简洁而被排除在外。

从底部的输出可以看到,如果我设置了大小(grande)并且不包装对象,getSize()将返回grande。

如果我设置了大小(grande),那么装饰对象,getSize()将返回tall(饮料类中设置的默认值)。

如果我设置(grande),装饰对象,再次设置(grande),那么getSize()返回grande。

注:虽然尺寸打印不正确,但成本计算正确。

问题:有没有一种方法可以这样编码:当我setSize()时,即使在对象被修饰之后,它仍然保持这个值?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package coffee;

public abstract class Beverage {
    String description ="Unknown Beverage";
    public enum Size { TALL, GRANDE, VENTI };
    Size size = Size.TALL;

    public String getDescription() {
        return description;
    }

    public void setSize(Size size) {
        this.size = size;
    }

    public Size getSize() {
        return size;
    }

    public abstract double cost();
}
1
2
3
4
5
package coffee;

public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}
1
2
3
4
5
6
7
8
9
10
11
12
package coffee;

public class HouseBlend extends Beverage {

    public HouseBlend() {
        description ="House Blend Coffee";
    }

    public double cost(){
        return .89;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package coffee;

public class Soy extends CondimentDecorator {
    Beverage beverage;

    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return beverage.getDescription() +", Soy";
    }

    public double cost() {
        double cost = beverage.cost();
        if( beverage.getSize() == Size.TALL) {
            cost += .10;
        } else if( beverage.getSize() == Size.GRANDE) {
            cost += .15;
        }else if( beverage.getSize() == Size.VENTI) {
            cost += .20;
        }
        return cost;
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package coffee;
import coffee.Beverage.Size;
public class StarbuzzCoffeeController {

    public static void main(String[] args) {
        Beverage beverage = new HouseBlend();
        System.out.println( beverage.getSize() +"" + beverage.getDescription() +" $" + String.format("%.2f", beverage.cost() ));
        beverage.setSize(Size.GRANDE);
        System.out.println( beverage.getSize() +"" + beverage.getDescription() +" $" + String.format("%.2f", beverage.cost() ));
        System.out.println("-----------------------");

        Beverage beverage2 = new HouseBlend();
        beverage2.setSize(Size.GRANDE);
        System.out.println( beverage2.getSize() +"" + beverage2.getDescription() +" $" + String.format("%.2f", beverage2.cost() ));

如果我不使用另一个setSize()执行此操作,它将"高"作为下一行的大小打印出来:

1
2
3
4
5
6
7
8
        beverage2 = new Soy(beverage2);
        System.out.println( beverage2.getSize() +"" + beverage2.getDescription() +" $" + String.format("%.2f", beverage2.cost() ));
        System.out.println("-----------------------");

        Beverage beverage3 = new HouseBlend();
        beverage3.setSize(Size.GRANDE);
        System.out.println( beverage3.getSize() +"" + beverage3.getDescription() +" $" + String.format("%.2f", beverage3.cost() ));
        beverage3 = new Soy(beverage3);

如果我在装饰完对象后再次设置了大小(),则大小将正确打印为Grande:

1
2
3
4
        beverage3.setSize(Size.GRANDE);
        System.out.println( beverage3.getSize() +"" + beverage3.getDescription() +" $" + String.format("%.2f", beverage3.cost() ));
    }
}

输出:

塔尔豪斯混合咖啡0.89美元

格兰德豪斯混合咖啡0.89美元

-----------------

格兰德豪斯混合咖啡0.89美元

高屋混合咖啡,大豆1.04美元

-----------------

格兰德豪斯混合咖啡0.89美元

格兰德豪斯混合咖啡,大豆1.04美元


Soy不重写getSize()方法,因此使用了基类的默认实现。基类返回自己的size成员,该成员初始化为tall。

为了解决这个问题,您必须在类Soy中相应地重写getSize()方法:

1
2
3
4
@Override
public Size getSize() {
    return this.beverage.getSize();
}

这将正确返回装饰对象的大小。


您对decorator模式的实现在概念上是不正确的。您遇到的问题来自于不正确的实现。

我在下面引用维基百科文章中的5个项目:https://en.wikipedia.org/wiki/decorator_pattern

  • 将原始"component"类子类划分为"decorator"类;
    • 在您的例子中,"组件"是BeverageCondimentDecorator是修饰器。
  • 在decorator类中,添加组件指针作为字段;
    • 将组件指针添加为字段。您在Soy中添加了字段Beverage beverage,而不是添加到CondimentDecorator中。
  • "将组件传递给decorator构造函数以初始化组件指针;"
    • 这也应该在CondimentDecorator中完成。
  • 在decorator类中,将所有"component"方法重定向到"component"指针
    • 你没有这样做,这是你问题的根源。在CondimentDecorator上,默认情况下,getSize()应调用beverage.getSize()。其他所有字段也应如此。
  • 我认为您应该回到decorator模式试图实现什么的基础上,并用正确的思想重新构建您的实现。您也许可以用一种更简单的方式解决您的问题,但是您可能对装饰器模式实际上是什么有一个错误的想法。

    请记住以下主要观点:

    • 饮料是你的组成部分,
    • 调味品制作者是你的装饰师,
    • 大豆是一种混凝土装饰材料。


    下面修复了getSize()问题(我仍在努力了解如何重构此代码):

    已将抽象方法getSize添加到ConditionTDecorator类:

    1
    2
    3
    4
    5
    6
    package coffee;

    public abstract class CondimentDecorator extends Beverage {
        public abstract String getDescription();
        public abstract Size getSize();  
    }

    在ConcreteDecorator类中覆盖GetSize()。例如。:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package coffee;

    public class Soy extends CondimentDecorator {
        Beverage beverage;

        public Soy(Beverage beverage) {
            this.beverage = beverage;
        }

        public String getDescription() {
            return beverage.getDescription() +", Soy";
        }

        public Size getSize() {
            return this.beverage.getSize();
        }

        public double cost() {
            double cost = beverage.cost();
            if( beverage.getSize() == Size.TALL) {
                cost += .10;
            } else if( beverage.getSize() == Size.GRANDE) {
                cost += .15;
            }else if( beverage.getSize() == Size.VENTI) {
                cost += .20;
            }
            return cost;
        }  
    }

    并移动setsize()命令,使其始终出现在对象被修饰之前(因为调味品成本与大小有关,所以如果您在咖啡被修饰之后更改大小,它将计算错误的价格):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static void main(String[] args) {
        //...
        Beverage beverage4 = new Decaf();
        beverage4.setSize(Size.VENTI); //SET THE SIZE BEFORE DECORATING (COST OF CONDIMENTS DEPENDS ON SIZE)
        beverage4 = new SteamedMilk(beverage4);
        beverage4 = new Mocha(beverage4);
        beverage4 = new Whip(beverage4);
        //beverage4.setSize(Size.VENTI); //<--No, No.
        System.out.println( beverage4.getSize() +"" + beverage4.getDescription() +" $" + String.format("%.2f", beverage4.cost() ));
    }


    如果你想得到"原汁原味"饮料的价值,我想你必须委托getSize()去包装/装饰。

    1
    2
      Beverage cappuccino = new Cappuccino(Size.VENTI);
      Beverage soyMilkCappuccino = new Soy(cappuccino);

    Soy

    1
    2
    3
    4
    5
    6
      class Soy extends Beverage {
           private final Beverage decorated;
           Soy(Beverage other) { this.decorated = other; }
           // delegation
           Size getSize() { return decorated.getSize(); }
      }