1、现有咖啡馆订单系统项目(示例项目)
咖啡种类:Espresso、ShortBlack、LongBlack、Decaf
调料:Milk、Soy、Chocolate
实现客户点单时,点个Decaf+milk+Soy可以立刻知道它的价格和描述,点个Decaf需要知道它的价格和描述
2、以传统的面向对象的原则设计这个项目。
1)定义一个超类
public abstract class Drink{
protected Stringdescription ="";
protected float price;
protected abstract StringgetDescription();
protected abstract float cost();
}
2)把所有的能单点的饮料都封装好描述和价格,继承Drink
public class Coffee extends Drink {
public Coffee(){
this.price =5.0f;
this.description ="coffee"+"-"+this.price;
}
@Override
protected String getDescription() {
return this.description;
}
@Override
protected float cost() {
return this.price;
}
}
3)如果要多点的则把单点的饮料作为引用放在此类中
public class CoffeeAndMilkextends Drink {
private Coffeecoffee;
private Milkmilk;
public CoffeeAndMilk(Coffee coffee,Milk milk){
this.coffee = coffee;
this.milk = milk;
}
@Override
protected StringgetDescription() {
return this.coffee.getDescription()+"&&"+this.milk.getDescription();
}
@Override
protected float cost() {
return this.milk.cost()+this.coffee.cost();
}
}
4)运行结果
Coffee coffee =new Coffee();
System.out.println("描述:"+coffee.getDescription());
System.out.println("价格:"+coffee.cost());
System.out.println("********************************************************");
Milk milk =new Milk();
System.out.println("描述:"+milk.getDescription());
System.out.println("价格:"+milk.cost());
System.out.println("********************************************************");
CoffeeAndMilk coffeeAndMilk =new CoffeeAndMilk(coffee,milk);
System.out.println("描述:"+coffeeAndMilk.getDescription());
System.out.println("价格:"+coffeeAndMilk.cost());
结果:
描述:coffee-5.0
价格:5.0
********************************************************
描述:milk-3.0
价格:3.0
********************************************************
描述:coffee-5.0&&milk-3.0
价格:8.0
问题:这样设计的话,增删饮料种类就很有问题,添加新的混合饮料的话非常麻烦,需要改动两个地方,新增单品饮料,新增复合饮料。搭配品种更不方便,假如我现在coffee不和牛奶混合,coffee和啤酒混合,就必须新建个复合饮料coffeeAndBeer类,扩展性不好,维护起来麻烦。
3、接下来我采用装饰者模式的方式改造代码
1)封装一个抽象接口,在装饰者模式中的角色是抽象组件角色。
public abstract class Drink{
public Stringdescription ="";
private float price =0f;
public void setDescription(String description) {
this.description = description;
}
public void setPrice(float price) {
this.price = price;
}
public StringgetDescription() {
return description+"-"+this.getPrice();
}
public float getPrice() {
return price;
}
public abstract float cost();
}
2)为这个抽象接口提供具体实现类,这里我还封装了一个中间层,这个抽象接口的具体实现应该是这个中间层的具体实现
Coffee中间层代码:
public class Coffeeextends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
具体实现Decaf代码:
public class Decafextends Coffee {
public Decaf(){
super.setDescription("Decaf");
super.setPrice(3.0f);
}
}
3)创建抽象装饰接口Decorator,包含抽象接口Drink的引用
public class Decorator extends Drink {
private Drinkobj;
public Decorator(Drink obj){
this.obj = obj;
}
@Override
public float cost() {
return super.getPrice()+obj.cost();
}
@Override
public StringgetDescription() {
return super.description+"-"+super.getPrice()+"&&"+obj.getDescription();
}
}
4)创建具体装饰接口的实现类Chocolate、Milk,负责具体的装饰
Chocolate代码:
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
super.setDescription("Chocolate");
super.setPrice(3.0f);
}
}
Milk代码:
public class Milkextends Decorator {
public Milk(Drink obj) {
super(obj);
super.setDescription("Milk");
super.setPrice(2.0f);
}
}
5)运行。
若你需要单品咖啡,你可以直接new
Drink order =null;
order =new Decaf();
System.out.println("order one price:"+order.cost());
System.out.println("order one desc:"+order.getDescription());
运行结果:
order one price:3.0
order one desc:Decaf-3.0
若你需要巧克力+牛奶+decaf,你可以现new一个单品(具体组件角色),然后利用装饰接口实现类进行装饰
Drink order =null;
order =new Decaf();
order =new Chocolate(order);
order =new Milk(order);
System.out.println("order two price:"+order.cost());
System.out.println("order two desc:"+order.getDescription());
运行结果:
order two price:8.0
order two desc:Milk-2.0&&Chocolate-3.0&&Decaf-3.0
以后需要怎样搭配就用装饰接口的实现类装饰具体饮料就行,这就解决了前面传统面向对象方式出现的问题,每新出一种混合饮料,都要新建一个混合饮料类的问题。
4、装饰模式概念
1)装饰者模式
动态地将功能附加到对象上,在对象功能扩展方面,它比继承更有弹性。装饰者模式又叫做包装模式。通过一种对客户端透明的方式来扩展对象的功能,是继承关系的一种替代方案。
2)装饰者模式的结构
3)装饰者模式的角色和职责
1、抽象组件角色:一个抽象接口,是被装饰类和被装饰类继承的父接口(本例中的抽象组件角色是Drink)
2、具体组件角色:为抽象组件的实现类(本例中的具体组件角色是Coffee)
3、抽象装饰角色:包含一个组件的引用,并定义了与抽象组件一致的接口(本例中的抽象装饰角色是Decorator)
4、具体装饰角色:为抽象装饰角色的实现类。负责具体的装饰。(本例中的具体装饰角色是Milk和Chocolate)
5、jdk中的装饰者模式
我们来看下java中的I/O类
接下来我们分析下,这些类充当的装饰者模式中的角色与职责
1)InputStream:这无疑是抽象组件角色,顶级接口
2)FileInputStream/StringBufferInputStream/ByteArrayInputStream:这些类是具体组件角色,是被装饰者角色
3)FilterInputStream:这个类是抽象装饰角色,是装饰者角色
4)BufferInputStream/DataInputStream/LineNumberInputStream:这些类是具体装饰角色,负责具体的装饰。
接着我们自己实现一个具体的装饰角色,自己编写一个自己的I/O装饰者,这个装饰者的功能是读取文件的字符全部大写
1)继承装饰者角色FilterInputStream
2)重写int read()方法和int read(byte[] b, int offset, int len)方法
public class UpperCaseInputStreamextends FilterInputStream {
public UpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read()throws IOException {
int c =super.read();
return c==-1?c:Character.toUpperCase((char)c);
}
@Override
public int read(byte[] b, int offset, int len)throws IOException {
int result=super.read(b,offset,len);
for(int i=0;i
b[i]=(byte)Character.toUpperCase((char)(b[i]));
}
return result;
}
}
3)运行
public static void main(String[] args) {
int c =0;
InputStream inputStream =null;
try {
inputStream =new UpperCaseInputStream(new BufferedInputStream(new FileInputStream("D:\\test.txt")));
while((c=inputStream.read())>=0) {
System.out.print((char)c);
}
}catch (IOException e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
test.txt文件中的内容
abcdefghjzklmen
运行结果:
ABCDEFGHJZKLMEN