Decorator Pattern装饰模式
装饰模式:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
装饰,让我们想到了房子的装修、人的打扮、手机装饰…等等,但是,不管怎么装修,房子还是那个房子、墙还是那堵墙,人还是那个人,脸还是那张脸;手机还是那部手机,屏幕还是那个屏幕,只是给人的感觉变了而已。
如果拿手机来说,我们买了手机后,都会给手机贴膜、安个吊坠、装个边框什么的,下面我们看看我们最常用的手机是如何贴膜的:
public class Phone { //构造方法 public Phone(String name) { this.name = name; } //名称 private String name; //贴膜 public void film(){ System.out.print(" 贴膜 "); } //最后的外观 public void exterior(){ System.out.println(" 手机:"+ name + "的样子!"); } //getter & setter public String getName() { return name; } public void setName(String name) { this.name = name; } }
public class PhoneTest { public static void main(String[] args) { Phone p = new Phone("Iphone5");//一台Iphone5 p.film(); //贴膜 p.exterior(); //我们看到的样子 } }
这样设计是满足了要求,可是大家都知道,手机装饰有很多种,除了贴膜,还有吊坠、边框、后盖、贴纸….等等,难道新需求来的时候,去修改Phone这个类吗?
根据我们的设计原则:“只对扩展开放,对修改关闭”。就是根据变化去设计一个程序,以后有变化的时候,之扩展我们的程序就行了,不需要修改原来的程序。这就是设计模式的精髓所在。
根据以上的例子,我们要考虑,手机装饰随时可能变化,当你逛街时、浏览某个网站时,看到一款比较新奇的手机配件,也许你会更换装饰了!
我们可能想到了接口:把手机的装饰用一个接口来表示:让所有手机配件来继承这个接口:
//手机 public class Phone { //构造方法 public Phone(String name) { this.name = name; } //名称 private String name; //外观 private List<Accessories> accessories = new ArrayList<Accessories>(); // 外观展示 public void exterior(){ for(Accessories accessorie : accessories){ accessorie.exterior(); } System.out.println(" 手机:"+ name + "的样子!"); } //getter & setter public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Accessories> getAccessories() { return accessories; } public void setAccessories(List<Accessories> accessories) { this.accessories = accessories; } }
//配件接口 public interface Accessories { void exterior(); }
//贴膜 public class Film implements Accessories { public void exterior() { System.out.print(" 贴膜 "); } }
//金属边框
public class MetalFrame implements Accessories {
public void exterior() {
System.out.print(" 金属边框 ");
}
}
//手机测试 public class PhoneTest { public static void main(String[] args) { Phone p = new Phone("Iphone5");//一台Iphone5 //配件 Accessories a = new Film(); Accessories b = new MetalFrame(); List<Accessories> l = new ArrayList<Accessories>(); l.add(a); l.add(b); p.setAccessories(l); // 外观展示 p.exterior(); } }
这样似乎解决了问题,可是代码看起来非常复杂,特别是配件在装饰的时候,都要装到配件列表中。有没有更简洁的方式呢?
好吧,还是让我们用装饰模式优雅的解决这个问题吧:
先看类图:
代码如下:
//手机接口 public interface Phone { public void exterior(); }
//具体的对象 --实现了手机接口 public class Iphone5 implements Phone { public void exterior() { System.out.print("Iphone5外观展示!"); } }
//手机配件抽象类 ---同样实现手机接口
public abstract class Accessories implements Phone { //要装饰的对象 -- 手机 private Phone phone; public void setPhone(Phone phone) { this.phone = phone; } //调用传入的Phone的exterior方法 public void exterior() { if(phone != null){ phone.exterior(); } } }
//具体装饰类:贴膜 -- 继承自配件类 public class Film extends Accessories { // 重写父类中的外观展示方法, // 在父类方法调用前加入自己的处理逻辑 public void exterior() { System.out.print(" 贴膜 "); super.exterior(); } }
//具体装饰:金属边框 – 继承自配件类 public class MetalFrame extends Accessories { // 重写父类中的外观展示方法 // 在父类方法调用前加入自己的处理逻辑 public void exterior() { System.out.print(" 金属边框 "); super.exterior(); } }
//代码测试 public class PhoneTest { public static void main(String[] args) { // 一台Iphone5 Iphone5 iphone5 = new Iphone5(); //配件 Film film = new Film(); MetalFrame metalFrame = new MetalFrame(); // 外观展示 film.setPhone(iphone5); metalFrame.setPhone(film); metalFrame.exterior(); } }
是不是感觉代码更加简洁了,而且也更加灵活了。这是装饰模式在程序设计中发挥的重要作用。
如果还不明白,我们可以举一个非常简单的例子,我们都吃过火锅,比如呷哺火锅,火锅点餐系统就是你点一份锅底,然后选择几份配菜,配菜可以随便点、而且可以点多份,锅底就一个,到结账的时候要知道点了那些东西,然后得花多少钱。根据这个需求,我们设计的时候用装饰模式来解决:
建立一个食品接口,有一个获得食品名称的方法,一个计算价格的方法和另外一个通过传入其它食品计算价格的方法,然后具体对象是锅底,因为菜都是在锅底里面涮的,没有锅底肯定不行,而配菜是跟随锅底随意搭配的,配菜可以多点,而且可以点多份。建一个锅底类,给出具体名称和具体价格,和一个配菜抽象类,让锅底类和配菜都继承这个食品接口,然后让具体的配菜都继承配菜类,在配菜里给出配菜具体名称和具体价格。类图如下:
这样设计的好处是锅底的配菜能随意搭配,可多选可复选,而且以后如果增加另外一种锅底的时候,只需要去继承这个食品接口就可以了,配菜在扩展的时候只需要把新上市的蔬菜去继承配菜就行了,并不需要去更改以前写好的代码,这就遵循了设计原则的:“针对接口编程”和“对扩展开放,对修改关闭”的原则了。看看下面的代码,让我们开始点餐吧:
//食品接口 public interface Food { String getName(); int price(); int price(Food food); }
//配菜抽象类 public abstract class Materials implements Food { public abstract String getName(); }
//锅底类—具体对象 public class Spicy implements Food { private String name; //构造方法 public Spicy(String name){ this.name = name; } //食品名称 public String getName(){ return name; } //价格 public int price() { return 15; } public int price(Food food) { return food.price() + 15; } }
//粉丝 --继承自配菜 public class Fans extends Materials { private Food food; //构造方法 public Fans(Food food) { this.food = food; } public String getName() { return food.getName() + ",粉丝"; } public int price() { return food.price() + 3; } public int price(Food food) { return food.price() + 3; } }
//白菜类 – 继承自配菜 public class Cabbage extends Materials { private Food food; //构造方法 public Cabbage(Food food) { this.food = food; } public String getName() { return food.getName() + ",白菜"; } //白菜价格 public int price() { return food.price() + 2; } // public int price(Food food) { return food.price() + 2; } }
//牛肉类 – 继承自配菜 public class Beef extends Materials { private Food food; //构造方法 public Beef(Food food) { this.food = food; } public String getName() { return food.getName() + ",牛肉"; } public int price() { return food.price() + 10; } public int price(Food foods) { return foods.price() + 10; } }
//五花肉类 – 继承自配菜 public class Pork extends Materials { private Food food; //构造方法 public Pork(Food food) { this.food = food; } public String getName() { return food.getName() + ",五花肉"; } public int price() { return food.price() + 8; } public int price(Food food) { return food.price() + 8; } }
//豆皮类 – 继承自配菜类 public class Yuba extends Materials { private Food food; //构造方法 public Yuba(Food food) { this.food = food; } public String getName() { return food.getName() + ",豆皮"; } public int price() { return food.price() + 5; } public int price(Food foods) { return foods.price() + 5; } }
//测试类 public class FoodTest { public static void main(String[] args) { // 香辣锅底的点餐 Spicy spicy = new Spicy("香辣锅底"); Fans fans = new Fans(spicy); Yuba yuba = new Yuba(fans); Beef beef = new Beef(yuba); Cabbage cabbage = new Cabbage(beef); System.out.println( "您要点的菜是:" + cabbage.getName() + ",合计:" + cabbage.price()+ "元。"); // 清汤锅底的点餐 Spicy spicy2 = new Spicy("清汤锅底"); Pork pork = new Pork(spicy2); Fans fans2 = new Fans(pork); Fans fans3 = new Fans(fans2); Cabbage cabbage2 = new Cabbage(fans3); System.out.println( "您要点的菜是:" + cabbage2.getName() + ",合计:" + cabbage2.price()+ "元。"); } }
看看装饰模式的定义:动态的给一共对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
在看看我们的程序,锅底对象可以动态的添加配菜,并且能动态的计算出当前点餐的价格。比起生成一个单独配菜的子类然后计算价格更加灵活。