@TOC
一、咖啡馆订单系统项目
1.咖啡馆订单系统项目:
(1) 咖啡种类:Espresso、ShortBlack(浓缩)、LongBlack、Decaf (无糖)(这些就是相当咖啡的基础,单品咖啡,基础元素,所有的咖啡都是在此基础上混合起来了的)
(2) 调料:Milk、Soy、Chocolate (往基础品种中加入牛奶,巧克力等等,就组成了类似巧克力味咖啡,摩卡,卡夫奇诺等等。我们一般在咖啡厅一般都是直接点卡夫奇诺,其价格就是有单品咖啡和各种调料相加在一起的价格。所以下面的案例系统就是咖啡完整系统)
(3) 咖啡馆订单项目设计原则(或者是需求):扩展性好、改动方便、维护方便
2.传统方式--简单的通过oo模式实现
就是把所有的咖啡或者调料设计成一个超类;如图,就是把所有饮料设计成一个抽象的超类Drink,有一个变量描述description,这个描述其实就是咖啡的名字,调料的名字等等。还有个方法getDescription()用来调用这个类,来指定是具体是什么类型的咖啡。最后是一个抽象的方法cost(),再具体的单品咖啡咖啡中去实现,比如表示单品咖啡的价格,或者是单品咖啡加调料的价格。而这上面那些单品咖啡去实现这个抽象类,通过实现description来呈现具体的事哪一种单品种类的咖啡,就能得到具体的类型。像Decaf等类在new的时候就知道了。cost里面就直接return返回多少钱。代码实现就是用户new一个单品咖啡Decaf。然后在调用getDescription()方法就知道点了Decaf这个种类咖啡,在调用cost就知道是多少钱了。
那么要是加各种调料该怎么办呢,比较某个单品加上调料就是摩卡?其实是一样,如下图:
比如,在扩展drink类是,在调用description的时候就指明我加入那些调料,比如Espresson几个实现指明加糖,豆浆。cost()里面也是直接算好指明总共多少钱,当用户调用Espresson加Milk加Soy时候调用cost就直接返回了多少钱。上面实现了三种咖啡。
但问题就是,调料拥有很多,哪些组合可以组,哪些组合不可以组,就要自己维护了。以后要要添加一个单品类型,就要新添加一个类实现Drink类。如果还要添加调料就要跟单品不同的组合,就会产生很多的类。这样类就会爆炸,引入如果一个单品价格变了,相关的组合就要跟着改动。所以下面来使用一个好一点的方式设计。
3.一个稍微好一点的设计方案(可不看)
思路就是在超类中把所有的种类调料都内置到超类中。把上面说的几个调料,内置进去,但他们都是布尔类型的,在下面定义的方法用于判断,是否扩展类中是否有他们,或者是要加什么调料,同时是否要加钱。这个方案就会比上一个方案好一点,不会类爆炸,但是他也有一些问题
(1)增删调料种类,如果要引入一个调料,就要改动这个超类。也就是新功能的引入,会影响原有功能,原有代码。这样就会在改动原有功能时,所有的引入都会产生bug,都有可能引入bug。还有不要豆浆也会修改超类。
(2)添加多份问题,例如如果引入两个牛奶,布尔型就不能判断。
二、装饰者模式原理
1、装饰者模式原理
装饰者模式就像打包一个快递 ;比如我卖了一个陶瓷,一本书,其核心就是我们卖的东西。这些东西我们不可能直接去卖出去,因此我们会在外面包装好多保护东西,如泡沫塑料等等。
主体:陶瓷、衣服
包装:报纸填充、塑料泡沫、纸板、木板
因此装饰者模式也可以这么理解。同样他也跟上面案例一样,分为如下几个部分:
(1)Component:主体(装饰的主体是什么)他就像上面饮料例子的超类Drink类(抽象的超类)。
(2)ConcreteComponent:具体不同类型主题(比如具体要卖的陶瓷,衣服等等),就像上面的单品咖啡,用来被包装的物品。
(3)Decorator:装饰者(比如泡沫塑料,纸板等等),就像是各种调料。
上面就是类结构实现,如果主题比较复杂,还可以在主题和实体之间添加一个缓冲类,就是把主题公共的方法拿出来放在主题与实体之间。再添加一个抽象的类。然后在去实现具体的实体类。这个就根据具体情况来设计。这次的咖啡设计就比较简单,就不需要在引入中间层。一般装饰者就是在主体组件扩展到具体的实现类时,会引入一个中间层,把装饰者的公布部分引入进来,在引入具体的实现时,只需要实现自己特定的部分就行了。公共的就放在上面,中间层中。
2、装饰者模式定义
动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性。简单地说:由上面的例子来说,这里的对象其实指的就是单品咖啡,附加的功能就是各种调料,是单品咖啡喝起来味道更好。也就是说装饰者模式可以动态的将调料添加到单品咖啡上混合出一个好的咖啡。而且是通过附加的方式添加上去,不是继承的方式添加上去的。
三、新的项目设计方案
1、用装饰者模式设计重新设计的方案
这里主体跟实体大致不变,主要在抽象主体与调料中间添加了一个中间层。中间层的作用就是在调用cost的时候,他是要进行费用的叠加的,还有一个重要的就是中间层引入了一个Drink对象,这个对象其实包含着被装饰的对象,这个被装饰的对象,在调用coat的时候我们会获取他的费用,然后通过递归的放方式去获取这一级所有费用、同时具体装饰的名字可以通过getDescription()获取。就是外面包装那么多东西,也可以获取出来。
2、装饰者模式下的举例订单
2份巧克力+一份牛奶的LongBlack
怎么做呢?首先new一个单品对象LongBlack,然后包装到Milk,再然后包装到Chocolate,在包装在Chocolate里面,具体的费用计算是通过,代用最外层cost方法去获取,他会自己去获取自己里面包装的费用,然后一级级递归,去调用所有费用,从而获取所有费用;通过这种方式结构,你会发现当调料很多的情况下,任意他随意组合,我们都可以通过这种递归的方式获取所有的费用。不像第一种方式一样,每新一个组合就要扩展一个类,导致类爆炸。而且你引入一个调料的时候,只需要在上面说的具体的调料上去扩展一个出来就行了,引入新调料的时候,不会对原有的功能造成任何影响。它引入自己引入自己,不会对原有代码造成影响。
四、装饰者模式示例演示
1、装饰者模式的总体设计方案
2、代码实现
1.Component类
public abstract class Drink {
public String description;
private float price=0.0f;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public abstract float cost();
}
2.ConcreteComponent类们
缓冲层coffee类
public class Coffee extends Drink{
@Override
public float cost() {
return super.getPrice();
}
}
Decaf类、LongBlack类、ShortBlack类、Espresso 类
public class Decaf extends Coffee{
public Decaf() {
super.setDescription("Decaf");
super.setPrice(3.0f);
}
}
public class LongBlack extends Coffee {
public LongBlack() {
setDescription(" longblack ");
setPrice(5.0f);
}
}
public class ShortBlack extends Coffee{
public ShortBlack() {
setDescription(" shortblack ");
setPrice(4.0f);
}
}
public class Espresso extends Coffee {
public Espresso() {
setDescription(" 意大利咖啡 ");
setPrice(6.0f);
}
}
3.Decorator类们
Milk类、Chocolate类、Soy类
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDescription(" 牛奶 ");
setPrice(2.0f);
}
}
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDescription(" 巧克力 ");
setPrice(3.0f);
}
}
public class Soy extends Decorator{
public Soy(Drink obj) {
super(obj);
setDescription(" 豆浆 ");
setPrice(1.5f);
}
}
4.Test类
public static void main(String[] args) {
// 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
// 1. 点一份 LongBlack
Drink order = new LongBlack();
System.out.println("费用1=" + order.cost());
System.out.println("描述=" + order.getDescription());
// 2. order 加入一份牛奶
order = new Milk(order);
System.out.println("order 加入一份牛奶 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 描述 = " + order.getDescription());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入一份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDescription());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入2份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDescription());
System.out.println("===========================");
Drink order2 = new Decaf();
System.out.println("order2 无因咖啡 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 描述 = " + order2.getDescription());
order2 = new Milk(order2);
System.out.println("order2 无因咖啡 加入一份牛奶 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDescription());
}
五、装饰者模式原则
开放-关闭原则的设计,意思就是:装饰者添加新功能,比如添加一个调料,开放就是添加一个新功能,关闭就是添加一个新功能时,对原有的代码不轻易改变。也就是说在设计一个项目体系结构的时候,对添加新功能,新代码是开放的,对已经设计好的,或者测试好的代码是不允许修改的。
六、Java里内置装饰者介绍(待看)
1、Java的IO结构的装饰者
这个其实跟上面咖啡馆设计其实是一模一样的。
FileInputStream等三个是一些主体,其中FilterInputStream是个中间层,继承一些公共的东西,而下面就是一些装饰者。
2、编写自己的Java I/O装饰者
利用扩展java这个FilterInputStreamsh中间层实现自己的装饰者对象
该例子功能是把文件输入流或者其他主体输入流里面输入过来的字串转换成大写的这么一个自定义的把小写全部实现转化为大写输入流
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
//首先实现中间层这个对象FilterInputStream
public class UpperCaseInputStream extends FilterInputStream{
//然后在构造函数中要把这个超类带进来,然后super进去
protected UpperCaseInputStream(InputStream in) {
super(in);
// TODO Auto-generated constructor stub
}
//下面两个就是类似cost方法,需要实现的,
public int read() throws IOException//单字符的读
{
int c=super.read();//这个super.read()就是调用上面super(in);的主题对象
return c==-1?c:Character.toUpperCase((char)(c));
}
public int read(byte[] b,int offset,int len) throws IOException//多字符的读
{
int result=super.read(b,offset,len);
for(int i=0;i<result;i++)
{
b[i]=(byte)Character.toUpperCase((char)(b[i]));
}
return result;
}
}
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class InputTest {
public static void main(String[] args) {
int c;
try {
InputStream in = new UpperCaseInputStream(new BufferedInputStream(
new FileInputStream("F:\\test.txt")));
while((c=in.read())>=0)
{
System.out.print((char)c);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}