一.装饰者模式:
定义
动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。
组合和继承的区别
继承。继承是给一个类添加行为的比较有效的途径。通过使用继承,可以使得子类在拥有自身方法的同时,还可以拥有父类的方法。但是使用继承是静态的,在编译的时候就已经决定了子类的行为,我们不便于控制增加行为的方式和时机。
组合。组合即将一个对象嵌入到另一个对象中,由另一个对象来决定是否引用该对象来扩展自己的行为。这是一种动态的方式,我们可以在应用程序中动态的控制。
与继承相比,组合关系的优势就在于不会破坏类的封装性,且具有较好的松耦合性,可以使系统更加容易维护。但是它的缺点就在于要创建比继承更多的对象。
装饰者模式的优缺点
优点
1、装饰者模式可以提供比继承更多的灵活性
2、可以通过一种动态的方式来扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为。
3、通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
4、具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
缺点
1、会产生很多的小对象,增加了系统的复杂性
2、这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
装饰者的使用场景
1、在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
2、需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
用一个网上的标准类图来定义一下装饰者模式。
二.装饰者模式的简单实现
1.首先我们定义饮料父类。
public abstract class Beverage {//父类作为一个抽象类呈现
String descriptionString = "未知饮料";
//类型描述父类实现
public String getDescription() {
return descriptionString;
}
//子类需要实现的抽象方法
public abstract double cost();
}
2.实现调料的父类,必须能够替代饮料类,所以继承自饮料类,从而获得同样的类型。
public abstract class CondimentDecorator extends Beverage {
// 调料材料子类必须实现的抽象方法
public abstract String getDescription();
}
3.关于2个基类已经定义完毕,我们设置下具体的被装饰着(具体的饮料类型)。具体饮料必须需要描述,让客人明白这是什么饮料,也必须定义价格,让客人可以付款。
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "House Blend Coffee";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return 5;
}
}
仔细推敲下我们写的类图,我们已经有了抽象组件Beverage,具体组件HouseBlend,也有了抽象装饰者CondimentDecorator,我们需要实现具体的装饰者——具体的材料。
/*饮料配料-摩卡*/
public class Mocha extends CondimentDecorator {
Beverage beverage;
double myCost = 20;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDesCription() {
return beverage.getDescription() + ",Mocha";
}
@Override
public double cost() {
return myCost + beverage.cost();
}
}
/*饮料配料-烤培*/
public class Whip extends CondimentDecorator{
Beverage beverage;
double myCost = 20;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDesCription() + ",Whip";
}
@Override
public double cost() {
return myCost + beverage.cost();
}
}
关于构造方法,我们需要引入被修饰对象的实例变量,所以就作为构造参数输入了。
关于description和cost方法,我们都是采用获取被修饰对象的属性,然后才去累加的方法作为新的输入,这样,新的属性能够一直叠加下去(比如计算价格)。也就是说我们计算Mocha的价钱,我们首先获取被装饰对象的价钱,然后加上自身价值,算出总的金额。
我们用一段测试代码测试下我们的订单。
public class TestBeverage {
public static void main(String args[]) {
//不加调料的HouseBlend
Beverage beverage = new HouseBlend();
System.out.println(beverage.getDescription()+" $"+beverage.cost());
//加入调料的HouseBlend
Beverage beverage2 = new HouseBlend();
beverage2=new Soy(beverage2);
beverage2=new Mocha(beverage2);
System.out.println(beverage2.getDescription()+" $"+beverage2.cost());
}
}
结果如下:
三. 典型的装饰者I/O
据说,java.io包内的类很多都是装饰者,我们常见的文件读写会用到的哟~下面举个典型的例子。
BufferInputStream,LineNumberInputStream都是扩展自抽象的装饰类FileInputStream。
java.io符合装饰者模式所有特点。顺着看一看,比如Writer和Reader流和输入输出流十分类似。
参考于:
https://blog.csdn.net/ma598214297/article/details/80699377