设计模式学习(五)装饰者模式、桥接模式、外观模式
装饰者模式
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
结构
(1)抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
(2)具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
(3)抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
(4)具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
案例
// 抽象组件:咖啡接口
interface Coffee {double cost();String description();
}// 具体组件:基础咖啡
class SimpleCoffee implements Coffee {@Overridepublic double cost() {return 5.0;}@Overridepublic String description() {return "简单咖啡";}
}// 抽象装饰者:咖啡装饰器
abstract class CoffeeDecorator implements Coffee {protected Coffee coffee;public CoffeeDecorator(Coffee coffee) {this.coffee = coffee;}@Overridepublic double cost() {return coffee.cost();}@Overridepublic String description() {return coffee.description();}
}// 具体装饰者:牛奶
class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee coffee) {super(coffee);}@Overridepublic double cost() {return super.cost() + 2.0;}@Overridepublic String description() {return super.description() + ", 加牛奶";}
}// 具体装饰者:糖
class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee coffee) {super(coffee);}@Overridepublic double cost() {return super.cost() + 1.0;}@Overridepublic String description() {return super.description() + ", 加糖";}
}// 具体装饰者:巧克力
class ChocolateDecorator extends CoffeeDecorator {public ChocolateDecorator(Coffee coffee) {super(coffee);}@Overridepublic double cost() {return super.cost() + 3.0;}@Overridepublic String description() {return super.description() + ", 加巧克力";}
}// 测试类
public class CoffeeShop {public static void main(String[] args) {// 基础咖啡Coffee coffee = new SimpleCoffee();System.out.println(coffee.description() + " 价格: ¥" + coffee.cost());// 加牛奶的咖啡Coffee milkCoffee = new MilkDecorator(coffee);System.out.println(milkCoffee.description() + " 价格: ¥" + milkCoffee.cost());// 加牛奶和糖的咖啡Coffee milkSugarCoffee = new SugarDecorator(new MilkDecorator(coffee));System.out.println(milkSugarCoffee.description() + " 价格: ¥" + milkSugarCoffee.cost());// 加所有配料的咖啡Coffee fullCoffee = new ChocolateDecorator(new SugarDecorator(new MilkDecorator(coffee)));System.out.println(fullCoffee.description() + " 价格: ¥" + fullCoffee.cost());}
}
使用场景
1、可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
2、装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
(1)当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类)
(2)在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
(3)当对象的功能要求可以动态地添加,也可以再动态地撤销时。
jdk源码
(1)抽象组件(Component):InputStream
抽象类,定义了所有输入流的核心接口(如read()
)。
(2)具体组件(Concrete Component):FileInputStream
、ByteArrayInputStream
等,是直接操作数据源的基础流。
(3)抽象装饰者(Decorator):FilterInputStream
,实现了InputStream
接口,并持有一个InputStream
类型的引用(被装饰对象),所有方法默认委托给被装饰对象。
(4)具体装饰者(Concrete Decorator):BufferedInputStream
(添加缓冲功能)、DataInputStream
(添加数据类型转换功能)、CheckedInputStream
(添加校验和功能)等,它们在委托基础上扩展了新功能。
// 1. 抽象组件(Component):定义核心接口
public abstract class InputStream implements Closeable {public abstract int read() throws IOException;// 其他方法...
}// 2. 具体组件(Concrete Component):基础实现类
public class FileInputStream extends InputStream {@Overridepublic int read() throws IOException {// 从文件读取字节的具体实现}// 其他方法...
}public class ByteArrayInputStream extends InputStream {@Overridepublic int read() {// 从字节数组读取字节的具体实现}// 其他方法...
}// 3. 抽象装饰者(Decorator):实现接口并持有组件引用
public class FilterInputStream extends InputStream {protected volatile InputStream in; // 持有被装饰的InputStream对象protected FilterInputStream(InputStream in) {this.in = in;}@Overridepublic int read() throws IOException {return in.read(); // 委托给被装饰对象}// 其他方法...
}// 4. 具体装饰者(Concrete Decorator):添加额外功能
public class BufferedInputStream extends FilterInputStream {private byte[] buf; // 缓冲数组,添加缓冲功能private int count;// ...public BufferedInputStream(InputStream in) {super(in);}@Overridepublic int read() throws IOException {// 先从缓冲区读取,缓冲区为空时再调用底层流读取// 实现了缓冲功能,减少IO次数}// 其他方法...
}public class DataInputStream extends FilterInputStream {public DataInputStream(InputStream in) {super(in);}// 添加读取基本数据类型的功能public int readInt() throws IOException {// 基于底层流实现int类型的读取}// 其他数据类型读取方法...
}// 使用示例:组合多个装饰器
public class IOExample {public static void main(String[] args) throws IOException {// 基础组件:文件输入流InputStream fileIn = new FileInputStream("data.txt");// 装饰1:添加缓冲功能InputStream bufferedIn = new BufferedInputStream(fileIn);// 装饰2:添加数据类型转换功能DataInputStream dataIn = new DataInputStream(bufferedIn);// 使用装饰后的流int num = dataIn.readInt();// ...}
}
代理和装饰者的区别
(1)相同点:都要实现与目标类相同的业务接口、在两个类中都要声明目标对象、都可以在不修改目标类的前提下增强目标方法
(2)不同点:目的不同 装饰者是为了增强目标对象 静态代理是为了保护和隐藏目标对象、获取目标对象构建的地方不同 装饰者是由外界传递进来,可以通过构造方法传递 静态代理是在代理类内部创建,以此来隐藏目标对象
桥接模式
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
结构
桥接(Bridge)模式包含以下主要角色:
(1)抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。
(2)扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
(3)实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。
(4)具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。
案例
模拟一个绘图应用,此应用要能绘制不同形状(像圆形、矩形),并且这些形状可以有不同颜色(比如红色、蓝色)。这里,“形状” 属于抽象部分,“颜色” 则是实现部分,借助桥接模式能让它们各自独立扩展。
public class BridgePatternDemo {// 颜色接口(实现部分)public interface Color {void applyColor();}// 红色(具体实现)public static class Red implements Color {@Overridepublic void applyColor() {System.out.print("红色");}}// 蓝色(具体实现)public static class Blue implements Color {@Overridepublic void applyColor() {System.out.print("蓝色");}}// 绿色(扩展的具体实现)public static class Green implements Color {@Overridepublic void applyColor() {System.out.print("绿色");}}// 形状抽象类(抽象部分)public abstract static class Shape {// 桥接引用:关联颜色接口protected Color color;// 通过构造器注入颜色public Shape(Color color) {this.color = color;}// 抽象方法:绘制形状public abstract void draw();}// 圆形(具体抽象)public static class Circle extends Shape {public Circle(Color color) {super(color);}@Overridepublic void draw() {System.out.print("绘制");color.applyColor(); // 委托给颜色实现System.out.println("圆形");}}// 矩形(具体抽象)public static class Rectangle extends Shape {public Rectangle(Color color) {super(color);}@Overridepublic void draw() {System.out.print("绘制");color.applyColor(); // 委托给颜色实现System.out.println("矩形");}}// 客户端public static void main(String[] args) {// 红色圆形Shape redCircle = new Circle(new Red());redCircle.draw(); // 蓝色矩形Shape blueRectangle = new Rectangle(new Blue());blueRectangle.draw(); // 绿色圆形(扩展测试)Shape greenCircle = new Circle(new Green());greenCircle.draw(); // 绿色矩形(扩展测试)Shape greenRectangle = new Rectangle(new Green());greenRectangle.draw(); }
}
优点与使用场景
好处:
(1)桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
(2)实现细节对客户透明
使用场景:
(1)当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
(2)当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
(3)当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
外观模式
又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。
结构
(1)外观(Facade)角色:为多个子系统对外提供一个共同的接口。
(2)子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
案例
模拟一个家庭影院系统,该系统包含多个组件:DVD 播放器、投影仪、音响、灯光等。如果客户端直接操作这些组件,需要了解每个组件的启动顺序和细节(比如先开投影仪、再开 DVD、调灯光等)。
// 子系统组件:DVD播放器
class DvdPlayer {public void on() {System.out.println("DVD播放器已开启");}public void play(String movie) {System.out.println("正在播放电影:" + movie);}public void off() {System.out.println("DVD播放器已关闭");}
}// 子系统组件:投影仪
class Projector {public void on() {System.out.println("投影仪已开启");}public void focus() {System.out.println("投影仪已对焦");}public void off() {System.out.println("投影仪已关闭");}
}// 子系统组件:音响
class SoundSystem {public void on() {System.out.println("音响已开启");}public void setVolume(int volume) {System.out.println("音响音量设置为:" + volume);}public void off() {System.out.println("音响已关闭");}
}// 子系统组件:灯光
class Lights {public void dim(int level) {System.out.println("灯光调暗至:" + level + "%");}public void on() {System.out.println("灯光已打开");}
}// 外观类:封装子系统的复杂操作
class HomeTheaterFacade {// 持有子系统组件的引用private DvdPlayer dvd;private Projector projector;private SoundSystem soundSystem;private Lights lights;// 构造器初始化子系统public HomeTheaterFacade(DvdPlayer dvd, Projector projector, SoundSystem soundSystem, Lights lights) {this.dvd = dvd;this.projector = projector;this.soundSystem = soundSystem;this.lights = lights;}// 封装“观看电影”的流程public void watchMovie(String movie) {System.out.println("\n=== 准备观看电影 ===");lights.dim(10); // 调暗灯光projector.on(); // 开投影仪projector.focus(); // 投影仪对焦soundSystem.on(); // 开音响soundSystem.setVolume(8); // 设音量dvd.on(); // 开DVDdvd.play(movie); // 播放电影}// 封装“结束观影”的流程public void endMovie() {System.out.println("\n=== 结束观影 ===");lights.on(); // 打开灯光dvd.off(); // 关DVDsoundSystem.off(); // 关音响projector.off(); // 关投影仪}
}// 客户端测试
public class FacadePatternDemo {public static void main(String[] args) {// 创建子系统组件DvdPlayer dvd = new DvdPlayer();Projector projector = new Projector();SoundSystem sound = new SoundSystem();Lights lights = new Lights();// 创建外观类HomeTheaterFacade homeTheater = new HomeTheaterFacade(dvd, projector, sound, lights);// 客户端只需调用简单接口,无需关心内部细节homeTheater.watchMovie("《星际穿越》");homeTheater.endMovie();}
}
好处
(1)降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
(2)对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
缺点
不符合开闭原则,修改很麻烦
使用场景
(1)对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
(2)当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
(3)当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
源码
使用tomcat作为web容器时,接收浏览器发送过来的请求,tomcat会将请求信息封装成ServletRequest对象,request对象是一个HttpServletRequest对象的子实现类对象即名为RequestFacade的类的对象。
定义 RequestFacade 类,分别实现 ServletRequest ,同时定义私有成员变量 Request ,并且方法的实现调用 Request 的实现。然后,将 RequestFacade上转为 ServletRequest 传给 servlet 的 service 方法,这样即使在 servlet 中被下转为 RequestFacade ,也不能访问私有成员变量对象中的方法。既用了 Request ,又能防止其中方法被不合理的访问。