4.4 装饰模式
4.4.1 装饰模式的定义
1.动机:在不改变一个对象本身功能的基础上给对象增加额外的新行为
2.定义:动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活
4.4.2 装饰模式的结构与分析
//抽象构件类
public interface Component {
public void operation();
}
//具体构建类
public class ConcreteComponent implements Component{
@Override
public void operation() {
}
}
//装饰者类
public class Decorator implements Component {
private Component component;//要装饰哪个构件
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
//具体装饰者类
public class ConcreteDecorator extends Decorator{
public ConcreteDecorator(Component component) {
super(component);
}
public void preAddOperation() {
}
public void operation() {
preAddOperation();
super.operation();
postAddOperation();
}
public void postAddOperation() {
}
}
//客户端
public class Main {
public static void main(String[] args) {
//要装饰的构建
ConcreteComponent component = new ConcreteComponent();
//创建装饰类
Decorator decorator = new Decorator(component);
decorator.operation();
}
}
-
装饰模式是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。
-
透明装饰模式和半透明装饰模式
**透明装饰模式:**客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应该全部声明为抽象构件类型。无法在客户端单独调用新增方法addedBehavior()
**半透明装饰模式:**用具体装饰类型来定义装饰之后的对象,而具体构件使用抽象构件类型来定义。最大的缺点在于不能实现对同一个对象的多次装饰,而且客户端需要有区别地对待装饰之前的对象和装饰之后的对象(此时已经不再是Component类型了)
4.4.3 装饰模式的案例
某软件公司基于面向对象技术开发了一套图形界面构件库——VisualComponent,该构件库提供了大量基本构件,如窗体、文本框、列表框等,由于在使用该构件库时,用户经常要求定制一些特殊的显示效果,如带滚动条的窗体、带黑色边框的文本框、既带滚动条又带黑色边框的列表框等等,因此经常需要对该构件库进行扩展以增强其功能。现使用装饰模式来设计该图形界面构件库。
//抽象构件类
public interface Component {
public void display();
}
public class Window implements Component{
@Override
public void display() {
System.out.println("一个窗口");
}
}
//抽象装饰类
public class Decorator implements Component{
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void display() {
component.display();
}
}
//装饰类
public class BorderDecoration extends Decorator{
public BorderDecoration(Component component) {
super(component);
}
public void display() {
addBorder();
super.display();
}
public void addBorder() {
System.out.println("添加黑色边框");
}
}
public class ScrollBarDecorator extends Decorator {
public ScrollBarDecorator(Component component) {
super(component);
}
public void display() {
addScrollBar();
super.display();
}
public void addScrollBar() {
System.out.println("添加滚动条");
}
}
//客户端
public class Main {
public static void main(String[] args) {
Window window = new Window();
//添加滚动条
ScrollBarDecorator decorator1 = new ScrollBarDecorator(window);
//添加黑边框
BorderDecoration decorator2 = new BorderDecoration(decorator1);
decorator2.display();
}
}
4.4.4 装饰模式的优缺点
优点 | 缺点 |
---|---|
1.装饰模式可以动态的扩展一个对象的功能,且比继承简洁,不会导致类的个数急剧增多 | 1.装饰模式进行系统设计时,会产生很多小对象,占用资源 |
2.可以对一个对象多次装饰 | 2.比继承更容易犯错 |
3.构件类和装饰类可以独立变化,无需修改源代码符合开闭原则 |
4.4.5 装饰模式的适用场景
-
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
-
当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式
4.5 外观模式
4.5.1 外观模式的定义
**1.模式动机:**一个客户类需要和多个业务类交互,有时候这些需要交互的业务类会作为一个整体出现,如何简化这种主系统和子系统之间的交互。
**2.模式定义:**为子系统中的一组接口提供一个统一的入口,外观模式定义了一个高层接口,这个接口使得子系统更加容易使用,外部与子系统的通信通过一个统一的外观对象进行。
4.5.2 外观模式的结构与分析
- 外观对象是迪米特法则的一种具体实现。
- 子系统符合单一职责原则。
- 但是当新加入一个子系统时,需要修改子系统角色,这不符合开闭原则。
- 一个系统可能有多个外观类
- 不要通过外观类给子系统增加行为
- 为了在增加外观类的时候符合开闭原则,可以增加一个抽象外观类
public class Facade {
private SubSystem1 subSystem1 = new SubSystem1();
private SubSystem2 subSystem2 = new SubSystem2();
public void method() {
subSystem1.method1();
subSystem2.method2();
}
}
public class SubSystem1 {
public void method1() {}
}
public class SubSystem2 {
public void method2() {}
}
4.5.3 外观模式的案例
现在考察一个电源总开关的例子,以便进一步说明外观模式。为了使用方便,一个电源总开关可以控制四盏灯、和一台电视机的启动和关闭。通过该电源总开关可以同时控制上述所有电器设备,使用外观模式设计该系统。
public class Light {
private String location;
public Light(String location) {
this.location = location;
}
public void on() {
System.out.println("打开" + location + "的灯");
}
public void off() {
System.out.println("关闭" + location + "的灯");
}
}
public class TV {
public void on() {
System.out.println("打开电视");
}
public void off() {
System.out.println("关闭电视");
}
}
public class Facade {
private Light[] lightArray = new Light[4];
private TV tv;
public Facade() {
lightArray[0] = new Light("客厅");
lightArray[1] = new Light("厨房");
lightArray[2] = new Light("卫生间");
lightArray[3] = new Light("卧室");
tv = new TV();
}
public void on() {
lightArray[0].on();
lightArray[1].on();
lightArray[2].on();
lightArray[3].on();
tv.on();
}
public void off() {
lightArray[0].off();
lightArray[1].off();
lightArray[2].off();
lightArray[3].off();
tv.off();
}
}
public class Main {
public static void main(String[] args) {
Facade facade = new Facade();
facade.on();
facade.off();
}
}
4.5.4 外观模式的优缺点
优点 | 缺点 |
---|---|
1.对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易 | 1.不能很好地限制客户端直接使用子系统类 |
2.对客户端和子系统进行解耦 | 2.如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则 |
3.子系统的变化独立,不会影响到其他子系统和客户端 |
4.5.5 外观模式的适用场景
-
要为访问一系列复杂的子系统提供一个简单入口
-
客户端程序与多个子系统之间存在很大的依赖性
-
在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而是通过外观类建立联系,降低层之间的耦合度
4.6 代理模式
4.6.1 代理模式的定义
**1.模式动机:**通过引入一个新的对象来实现对真实对象的操作,或者将新的对象作为真实对象的一个替身,引入代理对象来间接访问一个对象
2.模式定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用
4.6.2 代理模式的结构与分析
public interface Subject {
public void request();
}
public class Real implements Subject{
@Override
public void request() {}
}
public class Proxy implements Subject{
private Real real = new Real();
public void preRequest() {}
@Override
public void request() {
preRequest();
real.request();
postRequest();
}
public void postRequest() {}
}
4.6.3 代理模式的案例
- 保护代理:
在一个论坛中已注册用户和游客的权限不同,已注册的用户拥有发帖、修改自己的注册信息、修改自己的帖子等功能;而游客只能看到别人发的帖子,没有其他权限。使用代理模式来设计该权限管理模块。
public interface Permission {
public void readBolg();
public void writeBolg();
public void updateBolg();
public void updateUserInfo();
}
public class RealPermission implements Permission{
@Override
public void readBolg() {
System.out.println("阅读博客");
}
@Override
public void writeBolg() {
System.out.println("写博客");
}
@Override
public void updateBolg() {
System.out.println("修改博客");
}
@Override
public void updateUserInfo() {
System.out.println("修改用户信息");
}
}
public class ProxyPermission implements Permission{
private int state;
private RealPermission permission;
public ProxyPermission(int state) {
this.state = state;
permission = new RealPermission();
}
@Override
public void readBolg() {
permission.readBolg();
}
@Override
public void writeBolg() {
if (state == 0) {
System.out.println("游客模式不能写博客");
} else {
permission.writeBolg();
}
}
@Override
public void updateBolg() {
if (state == 0) {
System.out.println("游客模式不能更改博客");
} else {
permission.updateBolg();
}
}
@Override
public void updateUserInfo() {
if (state == 0) {
System.out.println("游客模式不能更改信息");
} else {
permission.updateUserInfo();
}
}
}
4.6.4 代理模式的优缺点
优点 | 缺点 |
---|---|
1.能够协调调用者和被调用者,在一定程度上降低了系统的耦合度 | 1.增加了代理对象,有些类型的代理模式可能会造成请求的处理速度变慢 |
2.客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则 | 2.实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂 |
4.6.5 代理模式的适用场景
代理模式 | 描述 |
---|---|
远程代理 | 当客户端对象需要访问远程主机中的对象时 |
虚拟代理 | 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时 |
缓冲代理 | 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时 |
保护代理 | 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时 |
智能引用代理 | 当需要为一个对象的访问(引用)提供一些额外的操作 |
防火墙代理 | 保护目标不让恶意用户接近。 |
同步化代理 | 使几个用户能够同时使用一个对象而没有冲突。 |
Copy-on-Write代理 | 把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作。 |