设计模式(三)—— 装饰者模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gududedabai/article/details/81989196

由于之前看的容易忘记,因此特记录下来,以便学习总结与更好理解,该系列博文也是第一次记录,所有有好多不完善之处请见谅与留言指出,如果有幸大家看到该博文,希望报以参考目的看浏览,如有错误之处,谢谢大家指出与留言。

一、咖啡馆订单系统项目

咖啡馆订单系统项目:

咖啡馆订单项目:

1)、咖啡种类:Espresso、ShortBlack(浓缩)、LongBlack、Decaf (无糖)(这些就是相当咖啡的基础,单品咖啡,基础元素,所有的咖啡都是在此基础上混合起来了的)

2)、调料:Milk、Soy、Chocolate  (往基础品种中加入牛奶,巧克力等等,就组成了类似巧克力味咖啡,摩卡,卡夫奇诺等等。我们一般在咖啡厅一般都是直接点卡夫奇诺,其价格就是有单品咖啡和各种调料相加在一起的价格。所以下面的案例系统就是咖啡完整系统)

3)、咖啡馆订单项目设计原则(或者是需求):扩展性好、改动方便、维护方便

(1)、先来看一个差的方案(简单的通过oo模式实现)

就是把所有的咖啡或者调料设计成一个超类;如图,就是把所有饮料设计成一个抽象的超类Drink,有一个变量描述description,这个描述其实就是咖啡的名字,调料的名字等等。还有个方法getDescription()用来调用这个类,来指定是具体是什么类型的咖啡。最后是一个抽象的方法cost(),再具体的单品咖啡咖啡中去实现,比如表示单品咖啡的价格,或者是单品咖啡加调料的价格。而这上面那些单品咖啡去实现这个抽象类,通过实现description来呈现具体的事哪一种单品种类的咖啡,就能得到具体的类型。像Decaf等类在new的时候就知道了。cost里面就直接return返回多少钱。代码实现就是用户new一个单品咖啡Decaf。然后在调用getDescription()方法就知道点了Decaf这个种类咖啡,在调用cost就知道是多少钱了。

那么要是加各种调料该怎么办呢,比较某个单品加上调料就是摩卡?其实是一样,如下图:

比如,在扩展drink类是,在调用description的时候就指明我加入那些调料,比如Espresson几个实现指明加糖,豆浆。cost()里面也是直接算好指明总共多少钱,当用户调用Espresson加Milk加Soy时候调用cost就直接返回了多少钱。上面实现了三种咖啡。

但问题就是,调料拥有很多,哪些组合可以组,哪些组合不可以组,就要自己维护了。以后要要添加一个单品类型,就要新添加一个类实现Drink类。如果还要添加调料就要跟单品不同的组合,就会产生很多的类。这样类就会爆炸,引入如果一个单品价格变了,相关的组合就要跟着改动。所以下面来使用一个好一点的方式设计。

(2)、一个好一点的设计方案

思路就是在超类中把所有的种类调料都内置到超类中。把上面说的几个调料,内置进去,但他们都是布尔类型的,在下面定义的方法用于判断,是否扩展类中是否有他们,或者是要加什么调料,同时是否要加钱。这个方案就会比上一个方案好一点,不会类爆炸,但是他也有一些问题。

(3)、问题

1)增删调料种类,如果要引入一个调料,就要改动这个超类。也就是新功能的引入,会影响原有功能,原有代码。这样就会在改动原有功能时,所有的引入都会产生bug,都有可能引入bug。还有不要豆浆也会修改超类。

2)添加多份问题,例如如果引入两个牛奶,布尔型就不能判断。

因此这个设计会比上面好一点,但是还是有很多问题,所以下采用装饰者模式类设计。因而这种模式也是最优的

二、装饰者模式原理

1、装饰者模式就像打包一个快递 ;比如我卖了一个陶瓷,一本书,其核心就是我们卖的东西。这些东西我们不可能直接去卖出去,因此我们会在外面包装好多保护东西,如泡沫塑料等等。

1)主体:陶瓷、衣服

2)包装:报纸填充、塑料泡沫、纸板、木板

因此装饰者模式也可以这么理解。同样他也跟上面案例一样,分为如下几个部分:

(1)、Component:主体(装饰的主体是什么)他就像上面饮料例子的超类Drink类(抽象的超类)。

(2)、ConcreteComponent:具体不同类型主题(比如具体要卖的陶瓷,衣服等等),就像上面的单品咖啡,用来被包装的物品。

(3)、Decorator:装饰者(比如泡沫塑料,纸板等等),就像是各种调料。

                                                            

上面就是类结构实现,如果主题比较复杂,还可以在主题和实体之间添加一个缓冲类,就是把主题公共的方法拿出来放在主题与实体之间。再添加一个抽象的类。然后在去实现具体的实体类。这个就根据具体情况来设计。这次的咖啡设计就比较简单,就不需要在引入中间层。一般装饰者就是在主体组件扩展到具体的实现类时,会引入一个中间层,把装饰者的公布部分引入进来,在引入具体的实现时,只需要实现自己特定的部分就行了。公共的就放在上面,中间层中。

4、装饰者模式定义: 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性。简单地说:由上面的例子来说,这里的对象其实指的就是单品咖啡,附加的功能就是各种调料,是单品咖啡喝起来味道更好。也就是说装饰者模式可以动态的将调料添加到单品咖啡上混合出一个好的咖啡。而且是通过附加的方式添加上去,不是继承的方式添加上去的。

因此我们来重新设计这个方案:

三、新的项目设计方案

1、用装饰者模式设计重新设计的方案

这里主体跟实体大致不变,主要在抽象主体与调料中间添加了一个中间层。中间层的作用就是在调用cost的时候,他是要进行费用的叠加的,还有一个重要的就是中间层引入了一个Drink对象,这个对象其实包含着被装饰的对象,这个被装饰的对象,在调用coat的时候我们会获取他的费用,然后通过递归的放方式去获取这一级所有费用、同时具体装饰的名字可以通过getDescription()获取。就是外面包装那么多东西,也可以获取出来。

2、装饰者模式下的举例订单:2份巧克力+一份牛奶的LongBlack

                                       

怎么做呢?首先new一个单品对象LongBlack,然后包装到Milk,再然后包装到Chocolate,在包装在Chocolate里面,具体的费用计算是通过,代用最外层cost方法去获取,他会自己去获取自己里面包装的费用,然后一级级递归,去调用所有费用,从而获取所有费用;通过这种方式结构,你会发现当调料很多的情况下,任意他随意组合,我们都可以通过这种递归的方式获取所有的费用。不像第一种方式一样,每新一个组合就要扩展一个类,导致类爆炸。而且你引入一个调料的时候,只需要在上面说的具体的调料上去扩展一个出来就行了,引入新调料的时候,不会对原有的功能造成任何影响。它引入自己引入自己,不会对原有代码造成影响。

四、装饰者模式示例演示

1、装饰者模式的设计方案

抽象超类

package com.java.jikexueyuan.coffeebar;
/*
 * 
 * 首先定一个Drink超类,抽象类 ;在抽象类的基础上扩展出两个分支,一个是咖啡的单品
 * ,这里咖啡可以设置个中间层,这里实现了中间层,Coffee中间层,这个中间层把单品的公有功能放进去了;一个是调料,
 * 装饰者分支Decorator,这个装饰者本身就是个中间层,在这Decorator里面实现共有功能,具体的调料去继承这个中间层去实现即可
 * 这样,代码接口就比较清晰了
 */
public abstract class Drink {
	public String description=""; //这个描述具体扩展出是什么单品或调料
	private float price=0f;; //这里是具体的单品或调料的价格
	
	
	public void setDescription(String description)
	{
		this.description=description;
	}
	
	public String getDescription()
	{
		return description+"-"+this.getPrice();
	}
	public float getPrice()
	{
		return price;
	}
	public void setPrice(float price)
	{
		this.price=price;
	}
	public abstract float cost();  //这个是用抽象是因为,在单品种直接返回价格即可,
	//但在调料中,调料是不能单独存在的,是跟着实体,具体实体一起的,价格不仅是调料还有单品的价格,调料cost方法是是要递归去获取所有调料的价格
	
}
package com.java.jikexueyuan.coffeebar.coffee;

import com.java.jikexueyuan.coffeebar.Drink;
//这是个具体主题分支上面的中间层
public  class Coffee extends Drink {

	@Override
	public float cost() { //在实现是直接返回价格即可,因为单品就单品一个,实现简单
		// TODO Auto-generated method stub
		return super.getPrice();
	}

	
}
package com.java.jikexueyuan.coffeebar.coffee;

public class Decaf extends Coffee {
	public Decaf()
	{
		super.setDescription("Decaf");
		super.setPrice(3.0f);
	}
}
package com.java.jikexueyuan.coffeebar.decorator;

import com.java.jikexueyuan.coffeebar.Drink;
//装饰者分支 中间层
public  class Decorator extends Drink {
	private Drink Obj;//注意这里面有个超类的对象,因为这是个装饰者,
	//所以他包装的是一个单品,或者是一个被包装过的单品.所以他用个超类的类型

	public Decorator(Drink Obj){//所以实现这个装饰者时必须带入这个Drink对象放进去
		this.Obj=Obj;
	};
	
	//这里的价格计算就有所跟单品不一样了。首先他要计算自己的价格,比如,巧克力,牛奶多少钱,还有要计算的就是前面
	//带入的主题的价格,如果是已包装过的,就变成了递归了,就迭代的计算所有价格,以级最终的单品价格
	@Override
	public float cost() {
		// TODO Auto-generated method stub
		
		return super.getPrice()+Obj.cost();
	}

	@Override
	public String getDescription()
	{
		return super.description+"-"+super.getPrice()+"&&"+Obj.getDescription();
	}
	
	}

调料装饰者

package com.java.jikexueyuan.coffeebar.decorator;

import com.java.jikexueyuan.coffeebar.Drink;

public class Chocolate extends Decorator {

	public Chocolate(Drink Obj) {		
		super(Obj);
		// TODO Auto-generated constructor stub
		super.setDescription("Chocolate");
		super.setPrice(3.0f);
	}

}

五、Java里内置装饰者介绍

1、Java的IO结构的装饰者

这个其实跟上面咖啡馆设计其实是一模一样的。

FileInputStream等三个是一些主体,其中FilterInputStream是个中间层,继承一些公共的东西,而下面就是一些装饰者。

2、编写自己的Java I/O装饰者  -利用扩展java这个FilterInputStreamsh中间层实现自己的装饰者对象

该例子功能是把文件输入流或者其他主体输入流里面输入过来的字串转换成大写的这么一个自定义的把小写全部实现转化为大写输入流

package com.java.jikexueyuan.myiodecorator;

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;
	}
}

 

package com.java.jikexueyuan.myiodecorator;

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();
		}

	}
}

六、装饰者模式原则

1、开放-关闭原则的设计

意思就是:装饰者添加新功能,比如添加一个调料,开放就是添加一个新功能,关闭就是添加一个新功能时,对原有的代码不轻易改变。也就是说在设计一个项目体系结构的时候,对添加新功能,新代码是开放的,对已经设计好的,或者测试好的代码是不允许修改的。

想要完整代码可以留下邮箱,我会及时发送。

猜你喜欢

转载自blog.csdn.net/gududedabai/article/details/81989196
今日推荐