0%

java-设计模式(四)

九、装饰模式(decorator)

为对象动态添加功能。

装饰模式是一种用来代替继承的技术,无须通过继承增加子类就能拓展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时可以避免类型体系的快速膨胀

img

  • Component:抽象构件,真实对象和装饰对象都有的接口,这样,客户端对象可以以与真实对象相同的方式同装饰对象交互
  • ConreteComponent:具体构件对象(真实对象)
  • Decorator:装饰角色。持有一个抽象构件的引用,装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象。这样,就能在真实对象调用前后增加新的功能
  • ConreteDecorator:具体装饰对象。负责给构件对象增加新的责任

要点: 装饰者与被装饰者拥有共同的超类,继承的目的是继承类型,而不是行为

Implementation

component抽象类:

Component是一个接口或是抽象类,就是定义我们最核心的对象,也就是最原始的对象。

1
2
3
4
5
public abstract class Component {

public abstract void operation();

}

conretetComponent:

具体构件,通过继承实现Component抽象类中的抽象方法。是最核心、最原始、最基本的接口或抽象类的实现,我们要装饰的就是它。

1
2
3
4
5
6
7
public class ConretetComponent extends Component
{
@Override
public void operation(){
System.out.println("具体对象的操作");
}
}

Decorator装饰类:

  一般是一个抽象类,在其属性里必然有一个private变量指向Component抽象构件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class Decorator extends Component {

private Component component = null;

//通过构造函数传递给被修饰者
public Decorator(Component component) {
this.component = component;
}

//委托给被修饰者执行
@Override
public void operation() {
if(component != null) {
this.component.operation();
}
}

}

ConcreteDecorator类:

  我们可以写多个具体实现类,把最核心的、最原始的、最基本的东西装饰成其它东西。

  这里就写两个类,稍改一下二者的实现顺序,看看结果。

  A类,它的operation()方法先执行了method1()方法,再执行了Decorator的operation()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ConcreteDecoratorA extends Decorator {

//定义被修饰者
public ConcreteDecoratorA(Component component) {
super(component);
}

//定义自己的修饰方法
private void method1() {
System.out.println("method1 修饰");
}

@Override
public void operation() {
this.method1();
super.operation();
}

}

B类,它的operation()方法先执行了Decorator的operation()方法,再执行了method2()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ConcreteDecoratorB extends Decorator {

//定义被修饰者
public ConcreteDecoratorB(Component component) {
super(component);
}

//定义自己的修饰方法
private void method2() {
System.out.println("method2 修饰");
}

@Override
public void operation() {
super.operation();
this.method2();
}

}

Client客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {

public static void main(String[] args) {
Component component = new ConcreteComponent();
//第一次修饰
component = new ConcreteDecoratorA(component);
//第二次修饰
component = new ConcreteDecoratorB(component);
//修饰后运行
component.operation();
}

}

IO流实现细节

  • Component抽象构件角色:
    • io流中的InputStream、OutputStream、Reader、Writer
  • ConcreteComponent 具体构件角色:
    • io流中的FileInputStream、FileOutputStream
  • Decorator装饰角色:
    • 持有一个抽象构件的引用:io流中的FilterInputStream、FilterOutputStream
  • ConcreteDecorator具体装饰角色:
    • 负责给构件对象增加新的责任。Io流中的BufferedOutputStream、BufferedInputStream等。

装饰模式的应用

 1. 何时使用

  • 在不想增加很多子类的情况下扩展类时

 2. 方法

  • 将具体功能职责划分,同时继承装饰者模式

 3. 优点

  • 装饰类和被装饰类可以独立发展,而不会相互耦合。它有效地把类的核心职责和装饰功能分开了
  • 装饰模式是继承关系的一个替代方案
  • 装饰模式可以动态地扩展一个实现类的功能

  4. 缺点

  • 多层装饰比较复杂。比如我们现在有很多层装饰,出了问题,一层一层检查,最后发现是最里层的装饰出问题了,想想工作量都害怕

  5. 使用场景

  • 需要扩展一个类的功能时
  • 需要动态地给一个对象增加功能,并可以动态地撤销时
  • 需要为一批的兄弟类进行改装或加装功能时

装饰模式和桥接模式的区别

两个模式都是为了解决过多子类对象问题。但他们的诱因不一样。桥接模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。装饰模式是为了增加新的功能。

参考文章

简说设计模式——装饰模式 - JAdam - 博客园 (cnblogs.com)

十、外观模式(facade)

提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。

Implementation

观看电影需要操作很多电器,使用外观模式实现一键看电影功能。

子系统角色:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SubSystem {
public void turnOnTV() {
System.out.println("turnOnTV()");
}

public void setCD(String cd) {
System.out.println("setCD( " + cd + " )");
}

public void startWatching(){
System.out.println("startWatching()");
}
}

外观类:

1
2
3
4
5
6
7
8
9
public class Facade {
private SubSystem subSystem = new SubSystem();

public void watchMovie() {
subSystem.turnOnTV();
subSystem.setCD("a movie");
subSystem.startWatching();
}
}

客户端:

1
2
3
4
5
6
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.watchMovie();
}
}

设计原则

最少知识原则:只和你的密友谈话。也就是说客户对象所需要交互的对象应当尽可能少。

外观模式的应用

  1. 何时使用

  • 客户端不需要知道系统内部的复杂联系,整个系统只提供一个“接待员”即可
  • 定义系统的入口

 2. 方法

  • 客户端不与系统耦合,外观类与系统耦合

 3. 优点

  • 减少了系统的相互依赖
  • 提高了灵活性。不管系统内部如何变化,只要不影响到外观对象,任你自由活动
  • 提高了安全性。想让你访问子系统的哪些业务就开通哪些逻辑,不在外观上开通的方法,你就访问不到

 4. 缺点

  • 不符合开不原则,修改很麻烦

 5. 使用场景

  • 为一个复杂的模块或子系统提供一个外界访问的接口
  • 子系统相对独立,外界对子系统的访问只要黑箱操作即可
  • 预防低水平人员带来的风险扩散

十一、享元模式(FlyWeight)

运用共享技术有效地支持大量细粒度的对象。

池技术:String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。

img

  • FlyWeightFactory:享元工厂类。创建并管理享元对象,享元池一般设计成键值对。
  • FlyWeight:抽象享元类。通常是一个接口或抽象类,声明公共方法,这些方法可以向外界提供对象的内部状态,设置外部状态。
  • ConcreteFlyWeight:具体享元类。为内部状态提供成员变量进行存储
  • UnsharedConcreteFlyWeight:非共享享元类。不能被共享的子类可以设计为非共享享元类

Implementation

Flyweight抽象类

  所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public abstract class Flyweight {

//内部状态
public String intrinsic;
//外部状态
protected final String extrinsic;

//要求享元角色必须接受外部状态
public Flyweight(String extrinsic) {
this.extrinsic = extrinsic;
}

//定义业务操作
public abstract void operate(int extrinsic);

public String getIntrinsic() {
return intrinsic;
}

public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}

}

ConcreteFlyweight类

  继承Flyweight超类或实现Flyweight接口,并为其内部状态增加存储空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConcreteFlyweight extends Flyweight {

//接受外部状态
public ConcreteFlyweight(String extrinsic) {
super(extrinsic);
}

//根据外部状态进行逻辑处理
@Override
public void operate(int extrinsic) {
System.out.println("具体Flyweight:" + extrinsic);
}

}

UnsharedConcreteFlyweight类

  指那些不需要共享的Flyweight子类。

1
2
3
4
5
6
7
8
9
10
11
12
public class UnsharedConcreteFlyweight extends Flyweight {

public UnsharedConcreteFlyweight(String extrinsic) {
super(extrinsic);
}

@Override
public void operate(int extrinsic) {
System.out.println("不共享的具体Flyweight:" + extrinsic);
}

}

FlyweightFactory类

  一个享元工厂,用来创建并管理Flyweight对象,主要是用来确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或创建一个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class FlyweightFactory {

//定义一个池容器
private static HashMap<String, Flyweight> pool = new HashMap<>();

//享元工厂
public static Flyweight getFlyweight(String extrinsic) {
Flyweight flyweight = null;

if(pool.containsKey(extrinsic)) { //池中有该对象
flyweight = pool.get(extrinsic);
System.out.print("已有 " + extrinsic + " 直接从池中取---->");
} else {
//根据外部状态创建享元对象
flyweight = new ConcreteFlyweight(extrinsic);
//放入池中
pool.put(extrinsic, flyweight);
System.out.print("创建 " + extrinsic + " 并从池中取出---->");
}

return flyweight;
}
}

Client客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Client {

public static void main(String[] args) {
int extrinsic = 22;

Flyweight flyweightX = FlyweightFactory.getFlyweight("X");
flyweightX.operate(++ extrinsic);

Flyweight flyweightY = FlyweightFactory.getFlyweight("Y");
flyweightY.operate(++ extrinsic);

Flyweight flyweightZ = FlyweightFactory.getFlyweight("Z");
flyweightZ.operate(++ extrinsic);

Flyweight flyweightReX = FlyweightFactory.getFlyweight("X");
flyweightReX.operate(++ extrinsic);

Flyweight unsharedFlyweight = new UnsharedConcreteFlyweight("X");
unsharedFlyweight.operate(++ extrinsic);
}

}

内部状态和外部状态

  上面享元模式的定义为我们提出了两个要求:细粒度和共享对象。我们知道分配太多的对象到应用程序中将有损程序的性能,同时还容易造成内存溢出,要避免这种情况,用到的就是共享技术,这里就需要提到内部状态和外部状态了。

  因为要求细粒度对象,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。

  内部状态指对象共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变;外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。

  我们举一个最简单的例子,棋牌类游戏大家都有玩过吧,比如说说围棋和跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色略多一点,但也是不太变化的,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,我们落子嘛,落子颜色是定的,但位置是变化的,所以方位坐标就是棋子的外部状态。

  那么为什么这里要用享元模式呢?可以想象一下,上面提到的棋类游戏的例子,比如围棋,理论上有361个空位可以放棋子,常规情况下每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。

享元模式的应用

  1. 何时使用

  • 系统中有大量对象时
  • 这些对象消耗大量内存时
  • 这些对象的状态大部分可以外部化时

  2. 方法

  • 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储

  3. 优点

  • 大大减少了对象的创建,降低了程序内存的占用,提高效率

  4. 缺点

  • 提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变
  • 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态 使运行时间变长。用时间换取了空间。

 5. 使用场景

  • 系统中存在大量相似对象
  • 需要缓冲池的场景

 6. 应用实例

  • String常量池
  • 数据库连接池

 7. 注意事项

  • 注意划分内部状态和外部状态,否则可能会引起线程安全问题
  • 这些类必须有一个工厂类加以控制

结构型模式汇总

代理模式 为真实对象提供一个代理,从而控制对真实对象的访问
适配模式 使原本由于接口不兼容不能一起工作的类可以一起工作
桥接模式 处理多层继承结构,处理多维度变化的场景,将各个维度设计成独立的继承结构,使各个维度可以独立的扩展在抽象层建立关联。
组合模式 将对象组合成树状结构以表示”部分和整体”层次结构,使得客户可以统一 的调用叶子对象和容器对象
装饰模式 动态地给一个对象添加额外的功能,比继承灵活
外观模式 为子系统提供统一的调用接口,使得子系统更加容易使用
享元模式 运用共享技术有效的实现管理大量细粒度对象,节省内存,提高效率

行为型模式

  • 行为型模式关注的是系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责。

  • 创建型模式关注的是对象的创建过程。

  • 结构型模式关注的是对象和类的组织。

十二、责任链模式(chain of responsibility)

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。

img

  • Handler:抽象处理者。定义了一个处理请求的接口。
  • ConcreteHandler:具体处理者。处理它所负责的请求,可访问它的后继者。如果可处理该请求就处理,否则就将该请求转发给它的后继者。

Implementation

抽象处理者

  抽象处理者实现了三个职责:

  • 定义一个请求的处理方法handlerMessage(),是唯一对外开放的方法
  • 定义一个链的编排方式setNext(),用于设置下一个处理者
  • 定义了具体的请求者必须实现的两个方法,即定义自己能够处理的级别的getHandlerLevel()方法及具体的处理任务echo()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public abstract class Handler {

private Handler nextHandler; //下一个处理者

public final Response handlerMessage(Request request) {
Response response = null;

if(this.getHandlerLevel().equals(request.getRequestLevel())) { //判断是否是自己的处理级别
response = this.echo(request);
} else {
if(this.nextHandler != null) { //下一处理者不为空
response = this.nextHandler.handlerMessage(request);
} else {
//没有适当的处理者,业务自行处理
}
}

return response;
}

//设定下一个处理者
public void setNext(Handler handler) {
this.nextHandler = handler;
}

//每个处理者的处理等级
protected abstract Level getHandlerLevel();

//每个处理者都必须实现的处理任务
protected abstract Response echo(Request request);

}

具体处理者

  这里我们定义三个具体处理者,以便能组成一条链,ConcreteHandlerB及ConcreteHandlerC就不再赘述了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ConcreteHandlerA extends Handler {

@Override
protected Level getHandlerLevel() {
//设置自己的处理级别
return null;
}

@Override
protected Response echo(Request request) {
//完成处理逻辑
return null;
}

}

责任链模式的应用

开发中常见的场景:

  • Java中,异常机制就是一种责任链模式。一个try可以对应多个catch, 当第一个catch不匹配类型,则自动跳到第二个catch.
  • Javascript语言中,事件的冒泡和捕获机制。Java语言中,事件的处理 采用观察者模式。
  • Servlet开发中,过滤器的链式处理
  • Struts2中,拦截器的调用也是典型的责任链模式

  1. 何时使用

  • 处理消息时

  2. 方法

  • 拦截的类都实现同一接口

  3. 优点

  • 将请求和处理分开,实现解耦,提高系统的灵活性
  • 简化了对象,使对象不需要知道链的结构

  4. 缺点

  • 性能会收到影响,特别是在链比较长的时候
  • 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
  • 不能保证请求一定被接收

  5. 使用场景

  • 有多个对象可以处理同一个请求
  • 在不明确指定接收者的情况下,向多个对象中的提交请求
  • 可动态指定一组对象处理请求

  6. 应用实例

  • 多级请求
  • 击鼓传花
  • 请假/加薪请求
  • Java Web中Tomcat对Encoding的处理、拦截器

  7. 注意事项

  • 需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能

十三、迭代器模式(iterator)

提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。

img

  • Aggregate:聚集抽象类。负责提供创建具体迭代器角色的接口
  • Iterator:迭代抽象类,用于定义得到开始对象、得到下一个对象、判断是否到结尾、当前对象等抽象方法,统一接口
  • ConcreteAggregate:具体聚集类,继承Aggregate
  • ConcreteIterator:具体迭代器类,继承Iterator,实现开始、下一个、是否结尾、当前对象等方法

Implementation

抽象容器

  负责提供接口,比如存在一个类似createIterator()这样的方法,在Java中一般是iterator()方法。

1
2
3
4
5
6
7
8
9
public interface Aggregate {

public void add(Object object);

public void remove(Object object);

public Iterator iterator();

}

抽象迭代器

  负责定义访问和遍历元素的接口,基本上有固定的三个方法,即first()获取第一个元素、next()访问下一个元素、hasNext()是否已经遍历到底部。

1
2
3
4
5
6
7
8
9
public interface Iterator {

public Object next(); //遍历到下一个元素

public boolean hasNext(); //是否已经遍历到尾部

public boolean remove(); //删除当前指向的元素

}

具体容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ConcreteAggregate implements Aggregate {

private Vector vector = new Vector();

@Override
public void add(Object object) {
this.vector.add(object);
}

public void remove(Object object) {
this.remove(object);
}

@Override
public Iterator iterator() {
return new ConcreteIterator(this.vector);
}

}

具体迭代器

 简单的实现就是通过一个游标,在一个容器中上下翻滚,遍历所有它需要查看的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class ConcreteIterator implements Iterator {

private Vector vector = new Vector();
public int cursor = 0; //定义当前游标

public ConcreteIterator(Vector vector) {
this.vector = vector;
}

@Override
public Object next() {
Object result = null;

if (this.hasNext()) {
result = this.vector.get(this.cursor ++);
} else {
result = null;
}

return result;
}

@Override
public boolean hasNext() {
if (this.cursor == this.vector.size()) {
return false;
}

return true;
}

@Override
public boolean remove() {
this.vector.remove(this.cursor);

return true;
}

}

迭代器模式的应用

  1. 何时使用

  • 遍历一个聚合对象时

  2. 方法

  • 把在元素间游走的责任交给迭代器,而不是聚合对象

  3. 优点

  • 支持以不同的方式遍历一个聚合对象
  • 迭代器简化了聚合类
  • 在同一个聚合上可以有多个遍历
  • 增加新的聚合类和迭代器类都很方便,无需修改原有代码

  4. 缺点

  • 增加了系统的复杂性。因为迭代器模式将存储数据和遍历数据的职责分离,增加了新的聚合类需要对应增加新的迭代器类,增加了系统的复杂性。

  5. 使用场景

  • 访问一个聚合对象的内容无需暴露它的内部表示时
  • 需要为聚合对象提供多种便利方式时
  • 为遍历不同的聚合结构提供一个统一的接口

  6. 应用实例

  • Java中的Iterator迭代器
  • foreach遍历