今天带给大家的是装饰者模式,提起这个设计模式,心里一阵激动,这是学习JAVA以来接触的第一个设计模式,也许也是各位接触的第一个设计模式。记得当初老师在讲IO的时候就提到过它:“是你还有你,一切拜托你。”没错,这就是装饰者模式最简洁的定义了。下面引出标准的定义(出自百度百科):装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
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());
}
}
这种写法,相信看过前两篇文章的人一眼就看出了问题,我们虽然写出了实现,但是由于类之间过于耦合,导致不利于维护,当我们添加一个新的颜色的上衣时,需要更改Clothes代码,这就违反了我们的设计原则:类应该对扩展开发,对修改关闭,也就是我们常说的开闭原则。在这里,带着大家一起认识装饰者模式,我们以上衣为主体,以图案来“装饰”上衣。比方说,如果商家要蓝上衣花草图案,那么,我们要做的是:
①拿一个蓝色上衣对象
②以花图案对象装饰它
③以草图案对象装饰它
④调用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)计算出价钱,然后再加上上衣的价钱。
现在,来总结一下我们目前所指的的一切:
=。=装饰者和被装饰者对象有相同的基类。
=。=我们可以用一个或多个装饰者包装一个对象。
=。=既然装饰者和被装饰对象有相同的基类,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
=。=装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的,
=。=对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象
接下来我们先来看看装饰者模式的说明:装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
下面我们来看下类图:
到这里,大家可能会有些混淆:这里使用的是继承不是组合?。其实,这么做的重点在于,装饰者和被装饰者必须是一样的类型,也就是有共同的基类,这是相当关键的地方。在这里我们利用继承达到“类型匹配”,而不是利用继承获得“行为”。那么这个行为又来自哪里?当我们将装饰者与组件组合时,就是再加入新的行为,所得到的新行为,并不是继承自超累,而是由组合对象得来的。
分支
这里,为了防止各位看不懂图的结构,我们先切出一个分支来描述一下这张图,当然如果各位已经看懂,也可以直接跳过这段分支,直接看下面对一开始那个问题的分析。
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;
}
}
接下来我们开始写图案代码,也就是具体装饰者类,为了能够跟上思路,标了注释
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());
}
}
看到这里,相信大家一定有些熟悉,记得我们在学习IO的时候曾学过包装流,
其中FileInputStream是被装饰的“组件”。
BufferedInputStream是一个具体的“装饰者”,它加入两种行为:利用缓冲输入来改进性能利用一个readLine()方法(用来一次读取一行文本输入数据)来增强接口。
LineNumberInputStream也是一个具体的“装饰者”。它加上了计算行数的能力
这里也引出了装饰者模式的一个缺点,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API程序员的困扰。但是如果我们已经了解了装饰者的工作原理,以后当使用别人的大量装饰者的API时,就可以很容易辨别出他们的装饰者类是如何组织的,以方便用包装方式去的想要的行为。
装饰者模式就到此结束了,感谢大家的收看。
下期预告,单例模式。