设计模式之装饰者模式

/ 设计模式 / 没有评论 / 451浏览

在之前的设计模式文章中楼主已经介绍过了,要尽量针对接口编程,而不要针对实现编程。因为这样我们的程序比较方便扩展,又遵循了设计模式的基本原则。既然要针对接口编程,那么势必会创建大量的子类来实现。但有些时候并不是所有的业务都可以通过创建子类就可以实现的,反而通过创建大量子类,而增加了程序的不可扩展性。所以今天楼主分享一下设计模式中另一种模式叫装饰者模式。装饰者模式运用了对象组合的方式,可以做到在运行时动态的装饰类,这也是装饰者模式的由来。那么在介绍装饰者模式之前,我们和其他的设计模式一样,我们先看一个简单的例子。我们将以游戏中角色为例。我们知道在游戏中角色可以使用很多不同的武器,在使用不同的武器时,用户角色的攻击力就会不同,那么下面的例子我们将创建3个不同的武器分别为刀、剑、枪,并为这3个武器分别初始化不同的攻击力。下面为具体的代码。

/**
* 游戏角色
*/
public abstract class GameUser {

/**
* 角色佩戴的武器
*/
protected String weapon;

/**
* 输出游戏角色使用的武器及攻击力
* @return
*/
public void show() {
System.out.println(String.format("游戏角色正在使用的武器为:%s 游戏角色当前的攻击力为:%s", weapon, aggressivity()));
}

/**
* 返回用户的攻击力
*/
public abstract int aggressivity();

}
/**
* 游戏武器刀
*/
public class GameWeaponKnife extends GameUser {

public GameWeaponKnife() {
weapon = "刀";
}

/**
* 刀的攻击力为100
*/
@Override
public int aggressivity() {
return 100;
}
}
/**
* 游戏武器剑
*/
public class GameWeaponSword extends GameUser {

public GameWeaponSword() {
weapon = "剑";
}

/**
* 剑的攻击力为200
*/
@Override
public int aggressivity() {
return 200;
}
}
/**
* 游戏武器枪
*/
public class GameWeaponGun extends GameUser {

public GameWeaponGun() {
weapon = "枪";
}

/**
* 枪的攻击力为300
* @return
*/
@Override
public int aggressivity() {
return 300;
}
}
/**
* 测试类
*/
public class Test {

public static void main(String[] args) {
GameUser gameUser = new GameWeaponKnife();
gameUser.show();

gameUser = new GameWeaponSword();
gameUser.show();

gameUser = new GameWeaponGun();
gameUser.show();
}

}
游戏角色正在使用的武器为:刀 游戏角色当前的攻击力为:100
游戏角色正在使用的武器为:剑 游戏角色当前的攻击力为:200
游戏角色正在使用的武器为:枪 游戏角色当前的攻击力为:300

至此我们实现了上述基本的需求了。但我们知道在游戏的角色中,除了携带武器外,还可以穿戴很多种饰品,也就是装备。穿戴不同的装备可以增加不同的攻击力。如果我们将上述的需求在修改的复杂一点,也就是游戏角色除了要携带武器外,还可以穿戴很多种装备。我们知道游戏中的装备很多种为了方便我们测试,我们暂时只创建护肩、胸甲、饰品等。并且游戏角色穿戴不同的装备需要为用户增加不同的攻击力。如果需求变更成这样时,那我们应该怎么修改上述的代码呢?具体的代码如下:

/**
* 游戏角色
*/
public abstract class GameUser {

/**
* 攻击力
*/
private int aggressivity = 0;

/**
* 护肩
*/
private String shoulderPad;

/**
* 胸甲
*/
private String breastplate;

/**
* 饰品
*/
private String ornament;

/**
* 角色佩戴的武器
*/
protected String weapon;

/**
* 角色穿戴的装备
*/
protected StringBuffer equipment = new StringBuffer();

/**
* 输出游戏角色使用的武器及攻击力
*/
public void show() {
System.out.println(String.format("游戏角色正在使用的武器为:【%s】游戏角色正在穿戴的装备为:【%s】游戏角色当前的攻击力为:【%s】", weapon, equipment.toString().trim(), aggressivity()));
}

/**
* 返回用户的攻击力
*/
public int aggressivity() {
// 先计算所有装备的攻击力然后在由子类计算武器的攻击力然后计算游戏角色的总攻击力
return aggressivity;
}

public String getShoulderPad() {
return shoulderPad;
}

public void setShoulderPad(String shoulderPad) {
this.shoulderPad = shoulderPad;
this.aggressivity += 10; // 护肩的攻击力为10
equipment.append(shoulderPad).append(" ");
}

public String getBreastplate() {
return breastplate;
}

public void setBreastplate(String breastplate) {
this.breastplate = breastplate;
this.aggressivity += 20; // 胸甲的攻击力为20
equipment.append(breastplate).append(" ");
}

public String getOrnament() {
return ornament;
}

public void setOrnament(String ornament) {
this.ornament = ornament;
this.aggressivity += 30; // 饰品的攻击力为30
equipment.append(ornament).append(" ");
}

public int getAggressivity() {
return aggressivity;
}
}
/**
* 游戏武器刀
*/
public class GameWeaponKnife extends GameUser {

public GameWeaponKnife() {
weapon = "刀";
}

/**
* 刀的攻击力为100
*/
@Override
public int aggressivity() {
return getAggressivity() + 100;
}
}
/**
* 游戏武器剑
*/
public class GameWeaponSword extends GameUser {

public GameWeaponSword() {
weapon = "剑";
}

/**
* 剑的攻击力为200
*/
@Override
public int aggressivity() {
return getAggressivity() + 200;
}
}
/**
* 游戏武器枪
*/
public class GameWeaponGun extends GameUser {

public GameWeaponGun() {
weapon = "枪";
}

/**
* 枪的攻击力为300
* @return
*/
@Override
public int aggressivity() {
return getAggressivity() + 300;
}
}
/**
* 测试类
*/
public class Test {

public static void main(String[] args) {
GameUser gameUser = new GameWeaponKnife();
gameUser.setShoulderPad("护肩");
gameUser.show();

gameUser = new GameWeaponSword();
gameUser.setBreastplate("胸甲");
gameUser.show();

gameUser = new GameWeaponGun();
gameUser.setOrnament("饰品");
gameUser.show();
}

}
游戏角色正在使用的武器为:【刀】游戏角色正在穿戴的装备为:【护肩】游戏角色当前的攻击力为:【110】
游戏角色正在使用的武器为:【剑】游戏角色正在穿戴的装备为:【胸甲】游戏角色当前的攻击力为:【220】
游戏角色正在使用的武器为:【枪】游戏角色正在穿戴的装备为:【饰品】游戏角色当前的攻击力为:【330】

我们现在已经实现了新的需求了。但上面的代码虽然实现了需求,但是违背了设计模式的基本原则,也就是将程序中涉及到可能变化的部分提取出来。除此之外还违背了另一种设计模式的基本原则,也就是开放关闭原则。开放关闭原则:类应该对扩展开放,而对修改关闭。说的简单点就是,如果我们已经开发好了一个类,如果有其他的需求,这个类现在并不能满足我们的需求时,那么可以通过任何扩展的方式来改变我们这个类的行为,也就是前面说的对类的扩展是开放的。而不能通过直接在原有类中修改,这也就是前面所说的关闭,也就是说对一个已经开发好的类对它的修改是关闭的,不允许任何的修改,只能通过扩展来实现。如果我们现在要添加一个新的装备,宠物类型的话,那么显然上述的代码违背了上述这2个设计模式的基本原则了,因为我们不得不修改原先已经修改好的代码,这样很容易会导致曾经已经编写好的代码产生新的问题。那么我们怎么能解决上述的问题呢?有没有一种设计模式类似观察者模式那样呢?在我们创建新的观察者时,是不需要重新修改主题的代码的。答案一定是有的,也就是本文将要介绍的设计模式,也就是装饰者模式。下面我们看一下装饰者模式的定义。

装饰者模式:动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

上面就是装饰者模式的定义,说的比较笼统。下面我们以游戏的需求为例,在来做简单的说明。如果我们创建一个游戏角色的话,如果不设置装备的话,那么当前游戏角色就是没有穿戴任何装备,也就是当前游戏角色的攻击力只有武器。如果我们这时想要穿戴护肩这个装备的话,那么就创建这个护肩对象来装饰这个游戏角色,如果我们这时还想要穿戴胸甲的话,那么就继续创建胸甲这个对象来,继续装饰已经被护肩对象装饰的游戏角色。以此类推,并且,每个对象都负责计算自己对象的攻击力即可。这样程序在运行时,会先执行最外面的装饰对象,计算攻击力,然后在继续计算被它装饰对象的攻击力,这样就达到了我们想要的效果了,也就是动态的为对象添加相应的职责了。那我们怎么保证,不同的对象可以彼此被装饰呢?所以,在使用装饰者模式有几点需要特殊的注意。下面是几点特殊的地方。

下面我们将代码修改为装饰者模式,下面为具体的代码:

/**
* 游戏角色
*/
public abstract class GameUser {

/**
* 输出游戏角色的武器、装备和攻击力
*/
public void show() {
System.out.println(String.format("游戏角色正在使用的武器为:【%s】游戏角色正在穿戴的装备为:【%s】游戏角色当前的攻击力为:【%s】", weapon(),
equipment().toString().trim(), aggressivity()));
}

/**
* 角色佩戴的武器
*/
public String weapon() {
return "";
}

/**
* 角色穿戴的装备
*/
public StringBuffer equipment() {
StringBuffer equipment = new StringBuffer();
return equipment;
}

/**
* 返回用户的攻击力
*/
public abstract int aggressivity();

}
/**
* 游戏武器刀
*/
public class GameWeaponKnife extends GameUser {

@Override
public String weapon() {
return "刀";
}

/**
* 刀的攻击力为100
*/
@Override
public int aggressivity() {
return 100;
}
}
/**
* 游戏武器剑
*/
public class GameWeaponSword extends GameUser {

@Override
public String weapon() {
return "剑";
}

/**
* 剑的攻击力为200
*/
@Override
public int aggressivity() {
return 200;
}
}
/**
* 游戏武器枪
*/
public class GameWeaponGun extends GameUser {

@Override
public String weapon() {
return "枪";
}

/**
* 枪的攻击力为300
* @return
*/
@Override
public int aggressivity() {
return 300;
}
}
/**
* 装备类
*/
public abstract class GameEquipment extends GameUser {

@Override
public abstract int aggressivity();
}
/**
* 护甲
*/
public class GameEquipmentShoulderPad extends GameEquipment {

private GameUser gameUser; // 保存被装饰对象目的是获取被装饰对象的攻击力

public GameEquipmentShoulderPad(GameUser gameUser) {
this.gameUser = gameUser;
}

@Override
public String weapon() {
return gameUser.weapon();
}

@Override
public StringBuffer equipment() {
return gameUser.equipment().append("护甲").append(" ");
}

@Override
public int aggressivity() {
return 10 + gameUser.aggressivity(); // 除了计算自己的攻击力还要计算被装饰对象的攻击力
}
}
/**
* 胸甲
*/
public class GameEquipmentBreastplate extends GameEquipment {

private GameUser gameUser; // 保存被装饰对象目的是获取被装饰对象的攻击力

public GameEquipmentBreastplate(GameUser gameUser) {
this.gameUser = gameUser;
}

@Override
public StringBuffer equipment() {
return gameUser.equipment().append("胸甲").append(" ");
}

@Override
public String weapon() {
return gameUser.weapon();
}

@Override
public int aggressivity() {
return 20 + gameUser.aggressivity(); // 除了计算自己的攻击力还要计算被装饰对象的攻击力
}

}
/**
* 饰品
*/
public class GameEquipmentOrnament extends GameEquipment {

private GameUser gameUser; // 保存被装饰对象目的是获取被装饰对象的攻击力

public GameEquipmentOrnament(GameUser gameUser) {
this.gameUser = gameUser;
}

@Override
public String weapon() {
return gameUser.weapon();
}

@Override
public StringBuffer equipment() {
return gameUser.equipment().append("饰品").append(" ");
}

@Override
public int aggressivity() {
return 30 + gameUser.aggressivity(); // 除了计算自己的攻击力还要计算被装饰对象的攻击力
}
}
/**
* 测试类
*/
public class Test {

public static void main(String[] args) {
GameUser gameUser = new GameWeaponKnife();
gameUser = new GameEquipmentShoulderPad(gameUser);
gameUser.show();

gameUser = new GameWeaponSword();
gameUser = new GameEquipmentBreastplate(new GameEquipmentShoulderPad(gameUser));
gameUser.show();

gameUser = new GameWeaponGun();
gameUser = new GameEquipmentOrnament(new GameEquipmentBreastplate(new GameEquipmentShoulderPad(gameUser)));
gameUser.show();
}

}
游戏角色正在使用的武器为:【刀】游戏角色正在穿戴的装备为:【护甲】游戏角色当前的攻击力为:【110】
游戏角色正在使用的武器为:【剑】游戏角色正在穿戴的装备为:【护甲 胸甲】游戏角色当前的攻击力为:【230】
游戏角色正在使用的武器为:【枪】游戏角色正在穿戴的装备为:【护甲 胸甲 饰品】游戏角色当前的攻击力为:【360

这样我们就将代码修改为装饰者模式的代码了,如果我们要新添加宠物装备时,我们只要新创建一个宠物类,并让该类继承装备类GameEquipment即可,并且我们并不需要修改曾经已经编写好的代码,我们只是用了类的扩展而已。下面为具体的代码。

/**
* 宠物
*/
public class GameEquipmentPet extends GameEquipment {

private GameUser gameUser; // 保存被装饰对象目的是获取被装饰对象的攻击力

public GameEquipmentPet(GameUser gameUser) {
this.gameUser = gameUser;
}

@Override
public StringBuffer equipment() {
return gameUser.equipment().append("宠物").append(" ");
}

@Override
public String weapon() {
return gameUser.weapon();
}

@Override
public int aggressivity() {
return 50 + gameUser.aggressivity(); // 除了计算自己的攻击力还要计算被装饰对象的攻击力
}

}
/**
* 测试类
*/
public class Test {

public static void main(String[] args) {
GameUser gameUser = new GameWeaponKnife();
gameUser = new GameEquipmentPet(new GameEquipmentShoulderPad(gameUser));
gameUser.show();

gameUser = new GameWeaponSword();
gameUser = new GameEquipmentPet(new GameEquipmentBreastplate(new GameEquipmentShoulderPad(gameUser)));
gameUser.show();

gameUser = new GameWeaponGun();
gameUser = new GameEquipmentPet(new GameEquipmentOrnament(new GameEquipmentBreastplate(new GameEquipmentShoulderPad(gameUser))));
gameUser.show();

}

}
游戏角色正在使用的武器为:【刀】游戏角色正在穿戴的装备为:【护甲 宠物】游戏角色当前的攻击力为:【160】
游戏角色正在使用的武器为:【剑】游戏角色正在穿戴的装备为:【护甲 胸甲 宠物】游戏角色当前的攻击力为:【280】
游戏角色正在使用的武器为:【枪】游戏角色正在穿戴的装备为:【护甲 胸甲 饰品 宠物】游戏角色当前的攻击力为:【410】

这就是装饰者模式的好处,非常方便的扩展。但它也有相应的缺点就是会造成设计时有很多个类,因为任何一种装饰都需要定义一种类,如果我们需要更多的装备时时,那我们就要创建更多装备的装饰类,如果还有其它业务处理的话,我们又会创建符合这种需求的装饰类。这就会造成一个简单的工具类,会依赖很多个类,造成使用这个工具类的困惑。就像Java IO一样,大家都知道IO里的类其在是大多了,多到很多类压根就没有用过 ,就是因为java io 也是采用了这种装饰者模式,所以它需要为每一种可能有的需求都创建相应的装饰类,来达到装饰的目的。这也是装饰者模式的弊端。但同时也是装饰者模式的好处,也就是方便扩展,如果我们要想扩展Java IO中某些类的功能,那我们只需要创建一个新的装饰者就可以了,这样在程序中,我们就可以用我们自己创建的装饰类,来装饰Java IO中的对象了。

在Java IO中FilterInputStream类是一个抽象装饰者,我们我们要实现自己的装饰对象,那我们需要继承FilterInputStream类。具体代码如下:

直接读取txt.txt内容:

/**
* 测试类
*/
public class Test {

public static void main(String[] args) throws Exception {
InputStream inputStream = new BufferedInputStream(new FileInputStream(Thread.currentThread().getContextClassLoader().getResource("").getPath() + "txt.txt"));
int c;
while ((c = inputStream.read()) >= 0) {
System.out.print((char) c);
}
}

}
jilinwula

现在我们新创建了一个新的装饰类UpperCaseInputStream,也就是用这个装饰类装饰的对象,获取出的内容都会将自动转成大写字母。下面为测试用例。

/**
* 将内容转成大写
*/
public class UpperCaseInputStream extends FilterInputStream {

protected UpperCaseInputStream(InputStream in) {
super(in);
}

@Override
public int read() throws IOException {
int result = super.read();
return (result == -1 ? result : Character.toUpperCase(result));
}
}
/**
* 测试类
*/
public class Test {

public static void main(String[] args) throws Exception {
InputStream inputStream = new UpperCaseInputStream(new BufferedInputStream(new FileInputStream(Thread.currentThread().getContextClassLoader().getResource("").getPath() + "txt.txt")));
int c;
while ((c = inputStream.read()) >= 0) {
System.out.print((char) c);
}
}

}
JILINWULA

这样我们就可以直接用这个UpperCaseInputStream装饰类去装饰任何FilterInputStream的类了。这也就是装饰模式的好处。