JAVA设计模式详解(三)----------装饰者模式
今天LZ带给大家的是装饰者模式,提起这个设计模式,LZ心里一阵激动,这是LZ学习JAVA以来接触的第一个设计模式,也许也是各位接触的第一个设计模式。记得当初老师在讲IO的时候就提到过它:“是你还有你,一切拜托你。”没错,这就是装饰者模式最简洁的定义了。下面LZ引出标准的定义(出自百度百科):装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
ex:工厂制作上衣,白上衣是30,蓝上衣40,红上衣50,另外还有往衣服上绣的图案,花图案需10元,草图案需5元。现在工厂需要计算出成本价,要求设计出这个实现代码。
我们初看这题有什么思路,难道要设计出5个类吗?这显然不行,如果工厂老板突然让加一个新的图案,岂不是“类爆炸”!这个时候,我们可能想到了用另一种实现方式,把CLothes做为基类,然后设置whiteClothes,
blueClothes ,redClothes 为三个布尔类型变量,在基类里设置cost方法,然后图案设置为子类继承它,各自重写cost方法。这样就把原本的五个类缩小成了三个。
//白上衣是30,蓝上衣40,红上衣50,另外还有往衣服上绣的图案,花图案需10元,草图案需5元
public class Clothes {
private int cost;
private boolean whiteClothes = false;
private boolean blueClothes = false;
private boolean redClothes = false;
public int cost(){
if(whiteClothes){
cost += 30;
} if(blueClothes){
cost += 40;
} if(redClothes){
cost += 50;
}
return cost;
} public int getCost() {
return cost;
} public void setCost(int cost) {
this.cost = cost;
} public boolean isWhiteClothes() {
return whiteClothes;
} public void setWhiteClothes(boolean whiteClothes) {
this.whiteClothes = whiteClothes;
} public boolean isBlueClothes() {
return blueClothes;
} public void setBlueClothes(boolean blueClothes) {
this.blueClothes = blueClothes;
} public boolean isRedClothes() {
return redClothes;
} public void setRedClothes(boolean redClothes) {
this.redClothes = redClothes;
}
} class FlowerPattern extends Clothes{
public int cost(){
return 10+super.cost();
}
} class GrassPattern extends Clothes{
public int cost(){
return 5+super.cost();
}
}
测试方法
public class TestDemo1 {
public static void main(String[] args) {
FlowerPattern flowerPattern = new FlowerPattern();
flowerPattern.setBlueClothes(true);
System.out.println("蓝上衣花图案一共花费:"+flowerPattern.cost());
GrassPattern grassPattern = new GrassPattern();
grassPattern.setRedClothes(true);
System.out.println("红上衣草图案一共花费:"+grassPattern.cost());
}
}
这种写法,相信看过LZ前两篇文章的人一眼就看出了问题,我们虽然写出了实现,但是由于类之间过于耦合,导致不利于维护,当我们添加一个新的颜色的上衣时,需要更改Clothes代码,这就违反了我们的设计原则:类应该对扩展开发,对修改关闭,也就是我们常说的开闭原则。在这里,LZ带着大家一起认识装饰者模式,我们以上衣为主体,以图案来“装饰”上衣。比方说,如果商家要蓝上衣花草图案,那么,我们要做的是:
①拿一个蓝色上衣对象
②以花图案对象装饰它
③以草图案对象装饰它
④调用cost()方法,并依赖委托将调料的价钱加上去
下面我们画个图更加详细了解如何以装饰者构造衣服订单
①以BlueClothes对象开始,别忘了它继承自clothes,且有一个用来计算上衣价钱的cost()方法
②建立一个花图案对象,并用它将BlueClothes对象包(wrap)起来。FlowerPattern对象是一个装饰者,它的类型"反映"了它所装饰的对象(本例中,就是Clothes)。所谓的"反映",指的是两者类型一致。所以FlowerPattern也有一个cost()方法。通过多态,也可以把FlowerPattern所包裹的任何clothes当成是clothes(因为FlowerPattern是clothes的子类)
③接下来还要建立草图案GrassPattern装饰者,并用它将FlowerPattern对象包起来。别忘了,BlueClothes继承自Clothes,且有一个cost()方法,用来计算衣服价钱
④这样,计算总价钱通过调用最外圈装饰者(GrassPattern)的cost()就可以办得到。GrassPatternd cost()方法会先委托它装饰的对象(也就是FlowerPattern)计算出价钱,然后再加上上衣的价钱。
现在,LZ来总结一下我们目前所指的的一切:
=。=装饰者和被装饰者对象有相同的基类。
=。=我们可以用一个或多个装饰者包装一个对象。
=。=既然装饰者和被装饰对象有相同的基类,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
=。=装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的,
=。=对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象
接下来我们先来看看装饰者模式的说明:装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
下面我们来看下类图:
到这里,大家可能会有些混淆:这里使用的是继承不是组合?。其实,这么做的重点在于,装饰者和被装饰者必须是一样的类型,也就是有共同的基类,这是相当关键的地方。在这里我们利用继承达到“类型匹配”,而不是利用继承获得“行为”。那么这个行为又来自哪里?当我们将装饰者与组件组合时,就是再加入新的行为,所得到的新行为,并不是继承自超累,而是由组合对象得来的。
分支
这里,为了防止各位看不懂图的结构,我们先切出一个分支来描述一下这张图,当然如果各位已经看懂,也可以直接跳过这段分支,直接看下面LZ对一开始那个问题的分析。
1,Component接口可以是接口也可以是抽象类,甚至是一个普通的父类(这个强烈不推荐,普通的类作为继承体系的超级父类不易于维护)。
2,装饰器的抽象父类Decorator并不是必须的。
那么我们将上述标准的装饰器模式,用我们熟悉的JAVA代码给诠释一下。首先是待装饰的接口Component。
public interface Component { void method(); }
接下来便是我们的一个具体的接口实现类,也就是俗称的原始对象,或者说待装饰对象。
public class ConcreteComponent implements Component{ public void method() {
System.out.println("原来的方法");
} }
下面便是我们的抽象装饰器父类,它主要是为装饰器定义了我们需要装饰的目标是什么,并对Component进行了基础的装饰。
public abstract class Decorator implements Component{ protected Component component; public Decorator(Component component) {
super();
this.component = component;
} public void method() {
component.method();
} }
再来便是我们具体的装饰器A和装饰器B。
public class ConcreteDecoratorA extends Decorator{ public ConcreteDecoratorA(Component component) {
super(component);
} public void methodA(){
System.out.println("被装饰器A扩展的功能");
} public void method(){
System.out.println("针对该方法加一层A包装");
super.method();
System.out.println("A包装结束");
}
}
public class ConcreteDecoratorB extends Decorator{ public ConcreteDecoratorB(Component component) {
super(component);
} public void methodB(){
System.out.println("被装饰器B扩展的功能");
} public void method(){
System.out.println("针对该方法加一层B包装");
super.method();
System.out.println("B包装结束");
}
}
下面给出我们的测试类。我们针对多种情况进行包装。
public class Main { public static void main(String[] args) {
Component component =new ConcreteComponent();//原来的对象
System.out.println("------------------------------");
component.method();//原来的方法
ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);//装饰成A
System.out.println("------------------------------");
concreteDecoratorA.method();//原来的方法
concreteDecoratorA.methodA();//装饰成A以后新增的方法
ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB(component);//装饰成B
System.out.println("------------------------------");
concreteDecoratorB.method();//原来的方法
concreteDecoratorB.methodB();//装饰成B以后新增的方法
concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);//装饰成A以后再装饰成B
System.out.println("------------------------------");
concreteDecoratorB.method();//原来的方法
concreteDecoratorB.methodB();//装饰成B以后新增的方法
}
}
从这个例子中,各位应该对装饰者模式的结构有了一定的了解,那么接下来我们解决之前的那个问题。
分支结束
接下来我们开始看最开始的问题代码:
先从clothes下手,这里我们加一个description 用来描述的更详细一下:
public abstract class Clothes {
String description = "unknown Clothes"; public String getDescription(){
return description;
} public abstract double cost();
}
接下来是装饰者类,我们成为图案Pattern类,也继承自Clothes
abstract class Pattern extends Clothes{
public abstract String getDescription();
}
有了两个基类,我们为实现一些衣服,这里我们为了描述清楚,每个都写构造方法:
class WhiteClothes extends Clothes{
public WhiteClothes(){
description = "WhiteClothes";
}
@Override
public double cost() { return 30;
} } class BlueClothes extends Clothes{
public BlueClothes(){
description = "BlueClothes";
}
@Override
public double cost() { return 40;
} } class RedClothes extends Clothes{
public RedClothes(){
description = "RedClothes";
}
@Override
public double cost() { return 50;
} }
接下来我们开始写图案代码,也就是具体装饰者类,为了能够跟上思路,LZ标了注释
class FlowerPattern extends Pattern{//花图案是一个装饰者,让它扩展自Pattern,而Pattern又扩展自Clothes
//我们为了能让FlowerPattern能够引用clothes,用一个实例变量记录衣服,也就是被装饰者,然后在构造器中将其记录在实例变量里
private Clothes clothes;
public FlowerPattern(Clothes clothes){
this.clothes = clothes;
}
@Override
public String getDescription() { return clothes.getDescription() + ",FlowerPattern";//这里我们把图案也描述出来
} @Override
public double cost() { return 10+clothes.cost();//我们把调用委托给被装饰对象,以计算价钱,然后再加上FlowerPattern的价钱,得到最终结果
} }
同理,我们写出草图案的具体实现:
class GrassPattern extends Pattern{//草图案是一个装饰者,让它扩展自Pattern,而Pattern又扩展自Clothes
//我们为了能让FlowerPattern能够引用clothes,用一个实例变量记录衣服,也就是被装饰者,然后在构造器中将其记录在实例变量里
private Clothes clothes;
public GrassPattern(Clothes clothes){
this.clothes = clothes;
}
@Override
public String getDescription() { return clothes.getDescription() + ",GrassPattern";//这里我们把图案也描述出来
} @Override
public double cost() { return 5+clothes.cost();//我们把调用委托给被装饰对象,以计算价钱,然后再加上GrassPattern的价钱,得到最终结果
} }
最后,测试一下:
public class TestDemo2 {
public static void main(String[] args) {
Clothes clothes = new BlueClothes();
clothes = new FlowerPattern(clothes);
clothes = new GrassPattern(clothes);
System.out.println(clothes.getDescription()+"$"+clothes.cost());
}
}
看到这里,LZ相信大家一定有些熟悉,记得我们在学习IO的时候曾学过包装流,
其中FileInputStream是被装饰的“组件”。
BufferedInputStream是一个具体的“装饰者”,它加入两种行为:利用缓冲输入来改进性能利用一个readLine()方法(用来一次读取一行文本输入数据)来增强接口。
LineNumberInputStream也是一个具体的“装饰者”。它加上了计算行数的能力
这里也引出了装饰者模式的一个缺点,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API程序员的困扰。但是如果我们已经了解了装饰者的工作原理,以后当使用别人的大量装饰者的API时,就可以很容易辨别出他们的装饰者类是如何组织的,以方便用包装方式去的想要的行为。
装饰者模式就到此结束了,感谢大家的收看。
下期预告,单例模式。
JAVA设计模式详解(三)----------装饰者模式的更多相关文章
- android java 设计模式详解 Demo
android java 设计模式详解 最近看了一篇设计模式的文章,深得体会,在此基础我将每种设计模式的案例都写成Demo的形式,方便读者研究学习, 首先先将文章分享给大家: 设计模式(Design ...
- JAVA设计模式详解(二)----------观察者模式
有一个模式可以帮助你的对象知悉现况,不会错过该对象感兴趣的事,对象甚至在运行时可以决定是否要继续被通知,如果一个对象状态的改变需要通知很多对这个对象关注的一系列对象,就可以使用观察者模式 .观察者模式 ...
- JAVA设计模式详解(四)----------单例模式
上一章我们学习了装饰者模式,这章LZ带给大家的是单例模式. 首先单例模式是用来干嘛的?它是用来实例化一个独一无二的对象!那这有什么用处?有一些对象我们只需要一个,比如缓存,线程池等.而事实上,这类对象 ...
- java设计模式4.适配器模式、装饰器模式
适配器模式 把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够工作. 1. 类的适配器模式 目标角色:期望的接口,对于类的适配器模式,此角色不可以是具体类 ...
- Java设计模式(四) 装饰 代理模式
(七)装饰 Decorator 装饰是一个对象,以动态地增加一些新功能. 象与被装饰的对象须要实现同一个接口.装饰对象持有被装饰对象的实例. interface DecoratorSourceable ...
- JAVA设计模式详解(六)----------状态模式
各位朋友,本次LZ分享的是状态模式,在这之前,恳请LZ解释一下,由于最近公司事情多,比较忙,所以导致更新速度稍微慢了些(哦,往后LZ会越来越忙=.=). 状态模式,又称状态对象模式(Pattern o ...
- JAVA设计模式详解(一)----------策略模式
策略模式,顾名思义就是设计一个策略算法,然后与对象拆分开来将其单独封装到一系列策略类中,并且它们之间可以相互替换.首先LZ举一个例子为大家引出这一个模式. 例子:某公司的中秋节奖励制度为每个员工发放2 ...
- Python 中的设计模式详解之:策略模式
虽然设计模式与语言无关,但这并不意味着每一个模式都能在每一门语言中使用.<设计模式:可复用面向对象软件的基础>一书中有 23 个模式,其中有 16 个在动态语言中“不见了,或者简化了”. ...
- JAVA设计模式详解(五)----------适配器模式
各位朋友好,本章节我们继续讲第五个设计模式. 在生活中,我们都知道手机内存卡是无法直接接电脑的,因为内存卡的卡槽比较小,而电脑只有USB插孔,此时我们需要用到读卡器.这个读卡器就相当于是适配器.这是生 ...
随机推荐
- AJPFX讲解外汇保证金交易的货币符号和外汇的报价方式
AJPFX:外汇保证金交易的货币符号 认识货币名字是必须的入门基础,通过货币符号,首先要知道买卖哪个货币,下面是一些货币的符号.买卖外汇就是这些任意其中某两种货币的比值,也就是汇率.根据汇率比的升高或 ...
- SVM的基础原理
因为看cs231的时候用了一下multi-class的svm,所以又把svm给复习了一下,教材是周志华的西瓜书,这里是大概的笔记. 1.线性可分 对于一个数据集: 如果存在一个超平面X能够将D中的正负 ...
- CDH中如何升级Spark
公司平时使用的CDH版本的hadoop生态,spark任务是基于yarn来管理的,而不是基于原生的spark master slave集群管理. 因此任务的大致启动模式是: 如果是Cluster模式: ...
- nginx和tomcat访问图片和静态页面的配置方法
生产环境下,有时候需要访问图片,正常需要应用ftp.nginx等配套使用,但是有时候为了简化,可以用以下的两种简单的访问,说实话,就是为了偷懒,但是效果是能有的,这就行了,所以今天做这个简化版的方便大 ...
- iOS数据持久化--用户属性
一.简介 NSUserDefaults类是一个单例类,每个程序只有一个 NSUserDefaults对象,可以用来存储用户的属性,比如自动登录时候的账号密码等小型的数据. 二.使用 1.NSUserD ...
- captcha ~ 生成验证码图片
验证码在我们的世界中可以保护我们的信息安全的一个保障之一 这就是生成验证码的代码 # 导报 from captcha.image import ImageCaptcha #验证码的包 from ...
- 广度优先遍历-BFS、深度优先遍历-DFS
广度优先遍历-BFS 广度优先遍历类似与二叉树的层序遍历算法,它的基本思想是:首先访问起始顶点v,接着由v出发,依次访问v的各个未访问的顶点w1 w2 w3....wn,然后再依次访问w1 w2 w3 ...
- 升级Ghost
环境:CentOS 6.3 [root@AY1406151605405725a8Z ghost]# ls -l total 108 -rw-rw-rw- 1 root root 1132 Sep ...
- 【nodejs】文件上传demo实现
文件结构: index.js var server = require('./server.js'); var router = require('./router.js'); var request ...
- 企业域的冗余设计①:DHCP冗余设计(上)
在许多企业网络中,为了方便客户端IP地址的管理,通常采用的是自动获取的方式向DHCP服务器获得IP地址.为了保证DHCP服务器能够正常稳定地向客户端提供IP地址的租赁,DHCP服务器的冗余设计就显得格 ...