装饰器模式概述
装饰器模式,也称为包装器模式,指在不改变原有对象的基础上,动态给一个对象添加一个额外的职责。
场景一
星巴克里面卖多种饮料,拿铁,咖啡,每一种饮料都有自己的价格。
为解决需求,设计一个饮料抽象类:
public abstract class Beverage {
private String name;
private int cost;
public void setName() {
this.name = "饮料";
}
String getName() {
return name;
}
Beverage(String name) {
this.name = name;
}
//价格
public int cost(){
return cost;
}
}
具体饮料:拿铁和咖啡继承饮料抽象
public class Coffee extends Beverage{
public Coffee() {
super("咖啡");
}
@Override
public int cost() {
return 10;
}
}
public class Latte extends Beverage{
public Latte() {
super("拿铁");
}
@Override
public int cost() {
return 12;
}
}
测试:
public static void main(String[] args) {
Beverage beverage = new Coffee();
Beverage beverage2 = new Latte();
System.out.println(beverage.getName()+" : "+beverage.cost());
System.out.println(beverage2.getName()+" : "+beverage2.cost());
}
场景二
现在需求变化了,现在星巴克不仅有拿铁,咖啡,可能在拿铁和咖啡中加入巧克力,糖,坚果,花生等调料,并且每份调料分别计算价格。
那我们可以创建出 巧克力拿铁、糖拿铁、坚果拿铁、花生拿铁、巧克力糖拿铁、巧克力花生拿铁等等类,会造成类爆炸的情况,996程序员也写不完,007也难,并且每加一种调料,工作量也是成倍增加。
所以我们改进写法:
把每一份调料作为成员变量放入饮料中。修改饮料类:
public abstract class Beverage {
private boolean chocolate = false;//巧克力
private boolean sugar = false;//糖
private boolean nut = false;//坚果
private boolean peanut = false;//花生
private String name;
private int cost;
public void setName() {
this.name = "饮料";
}
String getName() {
if(this.chocolate){
name = name + "+巧克力";
}
if(this.sugar){
name = name + "+糖";
}
if(this.nut){
name = name + "+坚果";
}
if(this.peanut){
name = name + "+花生";
}
return name;
}
Beverage(String name) {
this.name = name;
}
//价格
public int cost(){
if(this.chocolate){
cost = cost + 2;
}
if(this.sugar){
cost = cost + 3;
}
if(this.nut){
cost = cost + 4;
}
if(this.peanut){
cost = cost + 5;
}
return cost;
}
public void setChocolate(boolean chocolate) {
this.chocolate = chocolate;
}
public void setSugar(boolean sugar) {
this.sugar = sugar;
}
public void setNut(boolean nut) {
this.nut = nut;
}
public void setPeanut(boolean peanut) {
this.peanut = peanut;
}
}
以咖啡类为例,修改cost方法:
public class Coffee extends Beverage {
public Coffee() {
super("咖啡");
}
@Override
public int cost() {
return 10 + super.cost();
}
}
测试:
public static void main(String[] args) {
Beverage beverage = new Coffee();
beverage.setChocolate(true);
beverage.setNut(true);
System.out.println(beverage.getName()+" : "+beverage.cost());
}
需求我们完美实现了,饮料类中加多份调料,并计算出价格。
场景三
- 业务一:随着星巴克业务的变化和客户的需求,需要加入奶油这一调料。我们这时候势必需要修改饮料类的cost方法和getName方法,而我们作为客户端是不能修改代码的,这违反了开闭原则。
- 业务二:有的客户需要在咖啡中加入两份巧克力,两份巧克力总不能和一份的一样价钱吧!而我们写的成员变量作为布尔值,无法体现出同一调料的数量,势必需要改变源代码以适应新的需求,又违反开闭原则。
这时候,救世主“装饰器模式”诞生了!
重新修改饮料类,不再需要各种调料判断:
public abstract class Beverage {
private String name;
private int cost;
public void setName() {
this.name = "饮料";
}
String getName() {
return name;
}
Beverage(String name) {
this.name = name;
}
//价格
public abstract int cost();
}
并抽象出调料类:
//调料
public abstract class Seasoning extends Beverage{//1.继承Beverage
protected Beverage beverage;//2.需要有一个Beverage的成员变量
Seasoning(Beverage beverage) {
super("调料");
this.beverage = beverage;
}
}
写两个调料的实现类:巧克力和糖(花生类和坚果类不写了)
public class Chocolate extends Seasoning{
private int cost = 2;
Chocolate(Beverage beverage){
super(beverage);
}
@Override
public int cost() {
return beverage.cost() + this.cost;
}
@Override
String getName() {
return beverage.getName() + "+巧克力";
}
}
public class Sugar extends Seasoning{
private int cost = 3;
Sugar(Beverage beverage) {
super(beverage);
}
@Override
public int cost() {
return beverage.cost() + this.cost;
}
@Override
String getName() {
return beverage.getName() + "+糖";
}
}
测试:
public static void main(String[] args) {
Beverage beverage ;
beverage = new Coffee();
beverage = new Sugar(beverage);//这里一层装饰一层(包粽子一样)
beverage = new Chocolate(beverage);
beverage = new Sugar(beverage);
beverage = new Chocolate(beverage);
beverage = new Chocolate(beverage);
System.out.println(beverage.getName()+" : "+beverage.cost());
}
测试结果:
这里,我们实现了上述需求。以后无论增加什么调料或者饮料,我们都只需要继承Seasoning或者Beverage,调料如何加,加几份,我们也能实现,并且无需修改源码。上述就是装饰器模式的具体实现。
装饰器模式可以灵活扩展,但所有类都来自同一个祖宗Beverage。
uml类图:
装饰器模式的通用写法
//抽象组件
public abstract class Component {
public abstract void operation();
}
//装饰器抽象
public abstract class Decorator extends Component{
protected Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation(){
component.operation();
}
}
//具体组件
public class ConcreteComponent extends Component{
@Override
public void operation() {
System.out.println("ConcreteComponent operation");
}
}
//具体装饰器
public class ConcreteDecorator extends Decorator{
public ConcreteDecorator(Component component) {
super(component);
}
public void operation(){
System.out.println("first");
super.operation();
System.out.println("last");
}
}
测试:
public static void main(String[] args) {
Component component = new ConcreteDecorator(new ConcreteComponent());
component.operation();
}
jdk中的装饰器模式
jdk中的io相关的流所用的就是最明显的装饰器模式。InputStream就是我们上述的饮料,而FilterInputStream就是我们上述的调料。