概念
装饰模式是一种结构型设计模式,它允许在不改变现有对象结构的前提下,动态地将新行为添加到对象中。装饰模式通过将对象包装在装饰器类的实例中,来扩展其功能。
抽象组件(Component):定义了被装饰对象和装饰对象之间的共同接口,可以是抽象类或接口。
具体组件(ConcreteComponent):实现了抽象组件的接口,并且是被装饰的目标对象,即原始对象。
抽象装饰器(Decorator):继承自抽象组件,具有一个与抽象组件一致的接口,同时持有一个抽象组件的引用。其内部通常会包含一个构造方法,用于接收被装饰的对象。
具体装饰器(ConcreteDecorator):继承自抽象装饰器,负责给具体组件添加额外的功能。它在调用原始对象的操作之前或之后,执行自己的附加操作,从而扩展了原始对象的功能。
装饰模式的核心思想是利用组合而非继承来实现功能的扩展。通过将对象包装在装饰器类中,可以在运行时动态地给对象添加新的行为,而无需修改原始对象的结构或代码。这种灵活性使得装饰模式在需要动态添加功能、避免类膨胀以及保持代码可扩展性的场景下非常有用。
总结而言,装饰模式允许通过将对象包装在装饰器类中来动态地添加新功能,而不需要修改原始对象的结构。它提供了一种灵活、可扩展的方式来实现功能的组合,同时遵循开闭原则,使得系统更加灵活和可维护。
示例
我们可以在绘制的图形上进行装饰,比如填充、描边等方法,下面就是结构图及代码示例
//抽象界面构件类:抽象构件类
abstract class Component {
public abstract void display();
}
//圆形类:具体构件类
class CircleShape extends Component {
@Override
public void display() {
System.out.println("绘制圆形!");
}
}
//矩形类:具体构件类
class RectangleShape extends Component {
@Override
public void display() {
System.out.println("绘制矩形!");
}
}
//三角形:具体构件类
class TriangleShape extends Component {
@Override
public void display() {
System.out.println("绘制三角形形!");
}
}
//构件装饰类:抽象装饰类
class ComponentDecorator extends Component {
private Component component; //维持对抽象构件类型对象的引用
public ComponentDecorator(Component component) { //注入抽象构件类型的对象
this.component = component;
}
@Override
public void display() {
component.display();
}
}
//填充颜色装饰类:具体装饰类
class FillColorDecorator extends ComponentDecorator {
public FillColorDecorator(Component component) {
super(component);
}
@Override
public void display() {
super.display();
this.setFillColor();
}
public void setFillColor() {
System.out.println("为构件填充颜色!");
}
}
//黑色描边饰类:具体装饰类
class BlackBorderDecorator extends ComponentDecorator
{
public BlackBorderDecorator(Component component) {
super(component);
}
@Override
public void display() {
super.display();
this.setBlackBorder();
}
public void setBlackBorder() {
System.out.println("为构件增加黑色描边!");
}
}
客户端
Component component,componentFC; //使用抽象构件定义
component = new CircleShape(); //定义具体构件
componentFC = new FillColorDecorator(component); //定义装饰后的构件
componentFC.display();
输出结果
绘制圆形!
为构件填充颜色!
二次装饰
Component component,componentFC,componentDB; //使用抽象构件定义
component = new CircleShape(); //定义具体圆形构件component
componentFC = new FillColorDecorator(component); //将圆形构件component装入FillColorDecorator进行装饰
componentDB = new BlackBorderDecorator(componentFC); //将装饰完的部件再次装入BlackBorderDecorator进行装饰
componentDB.display();
运行结果
绘制圆形!
为构件填充颜色!
为构件增加黑色描边!
二次装饰解释:
首先理解第一次装饰component和componentFC都属于Component类,component定义的是Component的子类CircleShape,而componentFC定义的是Component的孙子类FillColorDecorator,而且在FillColorDecorator类构造器可以传入一个Component变量并为其赋值,因此new FillColorDecorator(component)的时候可以将此实例构造,并将内部的Component变量赋值,因为FillColorDecorator是Component的孙子辈,所以依然可以赋值给componentFC,至此,第一次装饰我们理解了。
第二次装饰BlackBorderDecorator类内部同样也是有Component变量,同样也是构造器为其赋值,因此传入的componentFC赋值给BlackBorderDecorator内部的Component,并创建变量componentDB,因此我们调用componentDB的display时,是这样的:componentDB的display包含componentFC的display,和本身的setBlackBorder;componentFC的display又包含着component的display和setFillColor;因此首先打印了component的display,然后是setFillColor,最后是setBlackBorder。
图示:
透明装饰模式与半透明装饰模式
透明装饰模式和半透明装饰模式是装饰模式的两种变体,它们在对被装饰对象的接口暴露方面有所不同:
透明装饰模式(Transparent Decorator Pattern):在透明装饰模式中,装饰器类和被装饰对象实现相同的接口。这意味着客户端可以将装饰器视为被装饰对象的类型来使用,而无需关心装饰器和被装饰对象之间的差异。透明装饰模式要求装饰器具有和被装饰对象相同的接口,以便能够替代被装饰对象。这种方式使得装饰器和被装饰对象对客户端来说是透明的。
半透明装饰模式(Semi-Transparent Decorator Pattern):在半透明装饰模式中,装饰器类和被装饰对象之间存在差异,装饰器类通常会引入新的方法或属性。客户端在使用装饰器时,需要明确知道自己是在使用装饰器,而不是原始的被装饰对象。客户端只能通过特定的方式来使用装饰器,不能将其视为被装饰对象的类型来使用。
透明装饰模式
上述我们提到的例子则为透明装饰模式,component,componentFC,componentDB都定义为Component,想要实现这样的效果,装饰类与被装饰类都要覆写display方法。
半透明装饰模式
有时候我们的装饰类比较特殊,比如有一个装饰类有一个setText方法,而这个方法并没有出现在Component类中,因此在客户端使用时,就不能将此装饰类定义为Component,而是要单独定义
比如:
//填充颜色装饰类:具体装饰类
class FillColorDecorator extends ComponentDecorator {
public FillColorDecorator(Component component) {
super(component);
}
@Override
public void display() {
super.display();
this.setFillColor();
}
public void setFillColor() {
System.out.println("为构件填充颜色!");
}
//此方法没有被display调用,但是我们还想使用此方法
public void setText() {
System.out.println("为构件输入文本!");
}
}
客户端
Component component = new CircleShape(); //定义具体圆形构件component;使用抽象构件定义
FillColorDecorator componentFC = new FillColorDecorator(component); //将圆形构件component装入FillColorDecorator进行装饰;单独为修饰类定义
componentFC.display();//调用原有业务方法
componentFC.setText();//调用新增业务方法
注意:半透明装饰模式无法进行二次装饰,因此,如果某个装饰类有特殊的方法,尽量在装饰抽象类中声明扩展,如果只有一个具体构件类,那么抽象装饰类可以作为该具体构件类的直接子类。因此我们应该尽量使用透明装饰模式
总结
优点:
1、动态地扩展对象的功能:可以在不修改原始对象的情况下,动态地给对象添加新的行为和功能。
2、遵循开闭原则:通过对原始对象进行包装,实现了对扩展开放、对修改关闭的原则。可以轻松地新增装饰器类,而不需要修改已有的代码,提高了代码的可维护性和可扩展性。
3、符合单一职责原则:装饰模式将功能划分到不同的装饰器类中,每个装饰器类只关注自己的特定功能,使得类的设计更加清晰,提高了代码的可读性和可理解性。
4、避免使用子类的过多继承:通过组合多个装饰器对象来扩展功能,避免了使用过多的子类继承。相比于使用继承创建子类,装饰模式更加灵活,可以根据需要动态地组合装饰器。
5、保持代码整洁性:装饰模式使得每个类只关注自己的核心功能,而把其他功能交给相应的装饰器来实现。这种解耦可以使得代码更加清晰、易于理解和维护。
缺点:
1、可能引入过多的对象:随着功能的扩展,可能会引入大量的装饰器对象,导致对象数量过多,复杂度增加。
2、容易出错:如果装饰器的使用不当,可能会导致装饰器的顺序错误或者产生意想不到的结果。
适用场景:
1、需要动态地给对象添加功能:当需要在不修改原始对象的情况下,动态地给对象添加额外的行为或功能时,可以考虑使用装饰模式。
2、避免使用过多的子类继承:当使用继承会导致类层次结构爆炸性增长的情况下,可以考虑使用装饰模式来替代继承,以实现更灵活的功能扩展。
3、需要对对象的功能进行动态组合:当需要根据不同的需求动态地组合对象的功能时,可以使用装饰模式,通过组合不同的装饰器对象实现灵活的功能组合。
总而言之,装饰模式适用于需要动态地扩展对象功能、避免过度继承以及动态组合对象功能的场景。它可以提供一种灵活、可维护和可扩展的设计方案。