**
设计模式(七)之装饰者模式
**
- 案例说明
假设我们去一家卖电脑的店买电脑,店里面有戴尔电脑、联想电脑、惠普电脑,这三种电脑本身的价格都是只包括主机和屏幕,如果要买鼠标、键盘、耳机等配件需要另外加钱,如果我们要计算每种组合的价钱,用传统写法的话那就要定义很多类了,一旦要增加一种电脑的品牌或者是一种电脑配件,那类的数量就会变得极其庞大,这里我们只列出电脑+其中一种配件的组合情况来稍微感受一下传统写法可能会造成的“类爆炸”情况:
可以看出,单单是电脑+一种配件的组合情况就要建这么多的类,下面是模拟传统写法的客户端调用,客户端也就是电脑店。
public class Client {
public static void main(String[] args) {
Computer computer = null;
computer = new DellComputer();
System.out.println(computer.cost());
computer = new HPHeadSet();
System.out.println(computer.cost());
computer = new LenovoKeyBoard();
System.out.println(computer.cost());
}
}
测试
D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=58267:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.decorator.none.computer.Client
Dell 4000元
4000
HPHeadSet 3080元
3080
LenovoKeyBoard 5100元
5100
Process finished with exit code 0
我们对上面的设计做一点改造,首先电脑及电脑的具体品牌我们放一起,电脑的配件我们抽离出来放另一边,然后将电脑品牌组合到配件里面去,确保我们可以随意购买某种电脑品牌的某几种配件。
电脑抽象类
@Data
public abstract class Computer {
private String description;
public abstract BigDecimal cost();
}
电脑具体的子类
public class DellComputer extends Computer{
public DellComputer() {
setDescription("DellComputer");
}
@Override
public BigDecimal cost() {
// 戴尔电脑本身的价格是4000元
return new BigDecimal(4000.0);
}
}
public class HPComputer extends Computer{
public HPComputer() {
setDescription("HPComputer");
}
@Override
public BigDecimal cost() {
// 惠普电脑本身的价格是3000元
return new BigDecimal(3000.0);
}
}
public class LenovoComputer extends Computer{
public LenovoComputer() {
setDescription("LenovoComputer");
}
@Override
public BigDecimal cost() {
// 联想电脑本身的价格是4000元
return new BigDecimal(5000.0);
}
}
电脑配件总类,即装饰者类。单独实例化时因为没有具体的配件,所以返回的是电脑本身的价格。
public class Decorator extends Computer{
private Computer computer;
public Decorator(Computer computer){
this.computer = computer;
}
@Override
public String getDescription() {
return computer.getDescription() + " 配件" + super.getDescription();
}
@Override
public BigDecimal cost() {
// 返回的是没有加任何配件的电脑价格
return computer.cost();
}
}
电脑配件中的耳机类,继承了配件的总类,重写了cost方法,返回的是之前的累加价格加上耳机本身的80元。
public class HeadSet extends Decorator{
public HeadSet(Computer computer) {
super(computer);
setDescription("HeadSet");
}
@Override
public BigDecimal cost() {
// 返回的是上次累加的价格加上耳机本身80元的总价格
return super.cost().add(new BigDecimal(80.0));
}
}
电脑配件中的键盘类,继承了配件的总类,重写了cost方法,返回的是之前的累加价格加上键盘本身的100元。
public class KeyBoard extends Decorator{
public KeyBoard(Computer computer) {
super(computer);
setDescription("KeyBoard");
}
@Override
public BigDecimal cost() {
// 返回的是上次累加的价格加上键盘本身100元的总价格
return super.cost().add(new BigDecimal(100.0));
}
}
电脑配件中的鼠标类,继承了配件的总类,重写了cost方法,返回的是之前的累加价格加上鼠标本身的50元。
public class Mouse extends Decorator{
public Mouse(Computer computer) {
super(computer);
setDescription("Mouse");
}
@Override
public BigDecimal cost() {
// 返回的是上次累加的价格加上鼠标本身50元的总价格
return super.cost().add(new BigDecimal(50.0));
}
}
客户端模拟在电脑店卖东西
先买一台惠普电脑,不要配件:
public class Client {
public static void main(String[] args) {
Decorator dec = null;
Computer computer = null;
// 买一台惠普电脑
computer = new HPComputer();
dec = new Decorator(computer);
System.out.println("买的电脑和配件包括:电脑" + dec.getDescription() + ";共花费:" + dec.cost() + "元");
}
}
测试:
D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=60406:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.decorator.one.Client
买的电脑和配件包括:电脑HPComputer 配件null;共花费:3000元
Process finished with exit code 0
买一台惠普电脑的基础上再买一个鼠标:
public class Client {
public static void main(String[] args) {
Decorator dec = null;
Computer computer = null;
// 买一台惠普电脑
computer = new HPComputer();
dec = new Mouse(computer);
System.out.println("买的电脑和配件包括:电脑" + dec.getDescription() + ";共花费:" + dec.cost() + "元");
}
}
测试:
D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=60455:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.decorator.one.Client
买的电脑和配件包括:电脑HPComputer 配件Mouse ;共花费:3050元
Process finished with exit code 0
买一台惠普电脑和一个鼠标的基础上再买一个耳机:
public class Client {
public static void main(String[] args) {
Decorator dec = null;
Computer computer = null;
// 买一台惠普电脑
computer = new HPComputer();
// 买一个鼠标
dec = new Mouse(computer);
// 再买一个耳机
dec = new HeadSet(dec);
System.out.println("买的电脑和配件包括:电脑" + dec.getDescription() + ";共花费:" + dec.cost() + "元");
}
}
测试:
D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=60482:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.decorator.one.Client
买的电脑和配件包括:电脑HPComputer 配件Mouse 配件HeadSet ;共花费:3130元
Process finished with exit code 0
买一台惠普电脑、一个鼠标和一个耳机的基础上再买一个键盘:
public class Client {
public static void main(String[] args) {
Decorator dec = null;
Computer computer = null;
// 买一台惠普电脑
computer = new HPComputer();
// 买一个鼠标
dec = new Mouse(computer);
// 再买一个耳机
dec = new HeadSet(dec);
// 再买一个键盘
dec = new KeyBoard(dec);
System.out.println("买的电脑和配件包括:电脑" + dec.getDescription() + ";共花费:" + dec.cost() + "元");
}
}
测试:
D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=60525:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.decorator.one.Client
买的电脑和配件包括:电脑HPComputer 配件Mouse 配件HeadSet 配件KeyBoard ;共花费:3230元
Process finished with exit code 0
买一台惠普电脑、一个鼠标、一个耳机和一个键盘的基础上再买一个键盘:
public class Client {
public static void main(String[] args) {
Decorator dec = null;
Computer computer = null;
// 买一台惠普电脑
computer = new HPComputer();
// 买一个鼠标
dec = new Mouse(computer);
// 再买一个耳机
dec = new HeadSet(dec);
// 再买一个键盘
dec = new KeyBoard(dec);
// 再买一个键盘
dec = new KeyBoard(dec);
System.out.println("买的电脑和配件包括:电脑" + dec.getDescription() + ";共花费:" + dec.cost() + "元");
}
}
测试:
D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=60761:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.decorator.one.Client
买的电脑和配件包括:电脑HPComputer 配件Mouse 配件HeadSet 配件KeyBoard 配件KeyBoard ;共花费:3330元
Process finished with exit code 0
- 总结
1、 通过组合而非继承的方式,实现了动态扩展对象的功能的能力。
2、 有效避免了使用继承的方式扩展对象功能而带来的灵活性差,子类无限制扩张的问题。
3、 充分利用了继承和组合的长处和短处,在灵活性和扩展性之间找到完美的平衡点。
4、 装饰者和被装饰者之间虽然都是同一类型,但是它们彼此是完全独立并可以各自独立任意改变的。
5、 遵守大部分常用设计原则,高内聚、低耦合。