一、概述
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分为以下 7 种:
1、代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性
2、适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作
3、桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度
4、装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能
5、外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问
6、享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用
7、组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性
以上 7 种结构型模式,除了适配器模式分为类结构型模式和对象结构型模式两种,其他的全部属于对象结构型模式。
二、代理模式
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点有:
1、代理对象在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
2、代理对象可以扩展目标对象的功能;
3、代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性;
其主要缺点是:
1、代理模式会造成系统设计中类的数量增加
2、在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
3、增加了系统的复杂度;
但使用动态代理方式可以解决上面提到的缺点。
代理模式的结构
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问。
代理模式的主要角色
1、抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法
2、真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
3、代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。
根据代理的创建时期,代理模式分为静态代理和动态代理:
1、静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了
2、动态代理:在程序运行时,运用反射机制动态创建而成
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.Request();
}
}
//抽象主题
interface Subject {
void Request();
}
//真实主题
class RealSubject implements Subject {
public void Request() {
System.out.println("访问真实主题方法...");
}
}
//代理
class Proxy implements Subject {
// 引用真实主题
private RealSubject realSubject;
public void Request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.Request();
postRequest();
}
public void preRequest() {
System.out.println("访问真实主题之前的预处理。");
}
public void postRequest() {
System.out.println("访问真实主题之后的后续处理。");
}
}
说明
:其实就是在代理类中引用真实主题然后在同名方法中调用真实主题的对应方法,只不过在调用的前后做了代码增强。
代理模式的应用场景
当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
1、远程代理:这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间
2、虚拟代理:这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉
3、安全代理:这种方式通常用于控制不同种类客户对真实对象的访问权限
4、智能指引:主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它
5、延迟加载:指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载
动态代理
上面介绍的代理模式中,代理类中包含了对真实主题的引用,这种方式存在两个缺点:
①真实主题与代理主题一一对应,增加真实主题也要增加代理
②设计代理以前真实主题必须事先存在,不太灵活
采用动态代理模式可以解决以上问题,如 SpringAOP,其结构图如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
public static void main(String[] args) {
DynamicProxy proxy = new DynamicProxy();
AbstractSubject proxy2 = proxy.getProxy(new RealSubject1());
proxy2.request();
AbstractSubject proxy3 = proxy.getProxy(new RealSubject2());
proxy3.request();
}
}
// 抽象主题
interface AbstractSubject {
void request();
}
//真实主题1
class RealSubject1 implements AbstractSubject {
@Override
public void request() {
System.out.println("真实主题1的请求...");
}
}
//真实主题2
class RealSubject2 implements AbstractSubject {
@Override
public void request() {
System.out.println("真实主题2的请求...");
}
}
// 动态代理
class DynamicProxy implements InvocationHandler {
// 这里如果是Object类则可以代理所有类型,看具体需求
private AbstractSubject subject;
public AbstractSubject getProxy(AbstractSubject subject) {
this.subject = subject;
// 返回一个代理类对象
return (AbstractSubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object obj, Method m, Object[] args) throws Throwable {
preRequest();
// 注意这里是代理对象subject不是入参obj
m.invoke(subject, args);
postRequest();
return null;
}
private void preRequest() {
System.out.println("方法调用前的增强...");
}
private void postRequest() {
System.out.println("方法调用后的增强...");
}
}
动态代理也叫 JDK 代理或接口代理,利用 JDK 的 API 动态的在内存中构建代理对象,能在代码运行时动态地改变某个对象的代理,并且能为代理对象动态地增加方法、增加行为。目前普遍使用的是 JDK 自带的代理和 CGLib 提供的类库。JDK 实现代理只需要使用 newProxyInstance 方法,该方法需要接收三个参数,语法格式如下:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h )
该方法在 Proxy 类中是静态方法,且接收的三个参数说明依次为:
①ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的
②Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
③InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,把当前执行目标对象的方法作为参数传入,其实就是代码增强的部分
动态代理要对目标类的增强逻辑进行扩展,可结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
三、适配器模式
当需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这个问题。
适配器模式(Adapter)的定义如下:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
该模式的主要优点如下:
1、客户端通过适配器可以透明地调用目标接口
2、复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类
3、将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题
4、在很多业务场景中符合开闭原则
其缺点是:
1、适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性
2、增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱
适配器模式的结构
类适配器模式可采用多重继承方式实现,如 C++ 可定义一个适配器类来同时继承当前系统的业务接口和现有组件库中已经存在的组件接口;Java 不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口(和静态代理有些相像,只不过适配者模式中的适配者接口需要按照业务需要设计和实现)。
适配器模式(Adapter)包含以下主要角色:
1、目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口
2、适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口
3、适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者
类适配器模式的结构
//客户端代码
public class ClassAdapterTest {
public static void main(String[] args) {
Target target = new ClassAdapter();
target.request();
}
}
//目标接口
interface Target {
public void request();
}
//适配者接口
class Adaptee {
public void specificRequest() {
System.out.println("适配者中的业务代码被调用!");
}
}
//类适配器类
class ClassAdapter extends Adaptee implements Target {
public void request() {
specificRequest();
}
}
对象适配器模式的结构
//客户端代码
public class ObjectAdapterTest {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new ObjectAdapter(adaptee);
target.request();
}
}
//目标接口
interface Target {
public void request();
}
//适配者接口
class Adaptee {
public void specificRequest() {
System.out.println("适配者中的业务代码被调用!");
}
}
//对象适配器类
class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
}
适配器模式(Adapter)通常适用于以下场景:
1、以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致
2、使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同
扩展
适配器模式(Adapter)可扩展为双向适配器模式,双向适配器类既可以把适配者接口转换成目标接口,也可以把目标接口转换成适配者接口。
//客户端代码
public class TwoWayAdapterTest {
public static void main(String[] args) {
System.out.println("目标通过双向适配器访问适配者:");
TwoWayAdaptee adaptee = new AdapteeRealize();
TwoWayTarget target = new TwoWayAdapter(adaptee);
target.request();
System.out.println("-------------------");
System.out.println("适配者通过双向适配器访问目标:");
target = new TargetRealize();
adaptee = new TwoWayAdapter(target);
adaptee.specificRequest();
}
}
//目标接口
interface TwoWayTarget {
public void request();
}
//适配者接口
interface TwoWayAdaptee {
public void specificRequest();
}
//目标实现
class TargetRealize implements TwoWayTarget {
public void request() {
System.out.println("目标代码被调用!");
}
}
//适配者实现
class AdapteeRealize implements TwoWayAdaptee {
public void specificRequest() {
System.out.println("适配者代码被调用!");
}
}
//双向适配器实现两个相互转换的接口
class TwoWayAdapter implements TwoWayTarget, TwoWayAdaptee {
private TwoWayTarget target;
private TwoWayAdaptee adaptee;
public TwoWayAdapter(TwoWayTarget target) {
this.target = target;
}
public TwoWayAdapter(TwoWayAdaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
public void specificRequest() {
target.request();
}
}
双向适配器其实就是实现了需要双向适配的接口,然后在具体实现中完成方法调用的转换。
四、桥接模式
在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。
桥接(Bridge)模式的定义如下:将抽象与实现分离,使它们可以独立变化。它使用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。桥接模式遵循了里氏替换原则和依赖倒置原则,最终实现了开闭原则,对修改关闭,对扩展开放。
桥接(Bridge)模式的优点是:
1、抽象与实现分离,扩展能力强
2、符合开闭原则
3、符合合成复用原则
4、其实现细节对客户透明
缺点是:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。
桥接模式的结构
将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。
桥接(Bridge)模式包含以下主要角色:
1、抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用
2、扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
3、实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用
4、具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现
public class BridgeTest {
public static void main(String[] args) {
Implementor imple = new ConcreteImplementorA();
Abstraction abs = new RefinedAbstraction(imple);
abs.operation();
Implementor imple1 = new ConcreteImplementorB();
Abstraction abs1 = new RefinedAbstraction(imple1);
abs1.operation();
}
}
//实现化角色
interface Implementor {
public void operationImpl();
}
//具体实现化角色A
class ConcreteImplementorA implements Implementor {
public void operationImpl() {
System.out.println("具体实现化(Concrete Implementor A)角色被访问");
}
}
//具体实现化角色B
class ConcreteImplementorB implements Implementor {
public void operationImpl() {
System.out.println("具体实现化(Concrete Implementor B)角色被访问");
}
}
//抽象化角色:注意是抽象类
abstract class Abstraction {
protected Implementor imple;
protected Abstraction(Implementor imple) {
this.imple = imple;
}
public abstract void operation();
}
//扩展抽象化角色
class RefinedAbstraction extends Abstraction {
protected RefinedAbstraction(Implementor imple) {
super(imple);
}
public void operation() {
System.out.println("扩展抽象化(Refined Abstraction)角色被访问");
imple.operationImpl();
}
}
总结
:在抽象类中引用接口,在抽象类的具体实现中再调用被引用的接口的方法,但最终的结果和传入的具体实现有关。
桥接模式的应用场景
当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。桥接模式通常适用于以下场景:
1、当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时
2、当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时
3、当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时
桥接模式的一个常见使用场景就是替换继承。我们知道,继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。
扩展
在软件开发中,有时桥接(Bridge)模式可与适配器模式联合使用。当桥接(Bridge)模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来。
五、装饰器模式
在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现。
装饰器(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
装饰器模式的主要优点有:
1、装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
2、通过使用不同装饰类及这些装饰类的排列组合,可以实现不同效果
3、装饰器模式完全遵守开闭原则
其主要缺点是:装饰器模式会增加许多子类,过度使用会增加程序的复杂性。
装饰器模式的结构
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰器模式的目标。
装饰器模式主要包含以下角色:
1、抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象
2、具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责
3、抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能
4、具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任
public class DecoratorPattern {
public static void main(String[] args) {
Component p = new ConcreteComponent();
p.operation();
System.out.println("---------------------------------");
Component d = new ConcreteDecorator(p);
d.operation();
}
}
//抽象构件角色
interface Component {
public void operation();
}
//具体构件角色
class ConcreteComponent implements Component {
public ConcreteComponent() {
System.out.println("创建具体构件角色");
}
public void operation() {
System.out.println("调用具体构件角色的方法operation()");
}
}
//抽象装饰角色
class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}
//具体装饰角色
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation();
addedFunction();
}
public void addedFunction() {
System.out.println("为具体构件角色增加额外的功能addedFunction()");
}
}
解构
:具体构件和抽象装饰角色都实现抽象构件,不同的是抽象装饰中引用了具体构件对象,且实现抽象装饰的方法时实际上是调用的具体构件的实现方法,而到了具体装饰时则先继承抽象装饰然后重写抽象装饰的方法,在调用父类的方法前后可调用具体的自定义的装饰方法,这样装饰的前后互不影响。如果将装饰方法改为抽象的,交由子类实现,则会更灵活。
装饰器模式的应用场景
1、当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类
2、当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现
3、当对象的功能要求可以动态地添加,也可以再动态地撤销时
六、外观模式
当一个系统的功能越来越强,子系统越来越多,客户对系统的访问也会变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。
外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点:
1、降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类
2、对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易
3、降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象
外观(Facade)模式的主要缺点如下:
1、不能很好地限制客户使用子系统类,很容易带来未知风险
2、增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”
外观模式的结构
外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。
外观(Facade)模式包含以下主要角色:
1、外观(Facade)角色:为多个子系统对外提供一个共同的接口
2、子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它
3、客户(Client)角色:通过一个外观角色访问各个子系统的功能
public class FacadePattern {
public static void main(String[] args) {
Facade f = new Facade();
f.method();
}
}
//外观角色
class Facade {
private SubSystem01 obj1 = new SubSystem01();
private SubSystem02 obj2 = new SubSystem02();
private SubSystem03 obj3 = new SubSystem03();
// 通过外观模式的一个方法访问所有子系统的方法
public void method() {
obj1.method1();
obj2.method2();
obj3.method3();
}
}
//子系统角色1
class SubSystem01 {
public void method1() {
System.out.println("子系统01的method1()被调用!");
}
}
//子系统角色2
class SubSystem02 {
public void method2() {
System.out.println("子系统02的method2()被调用!");
}
}
//子系统角色3
class SubSystem03 {
public void method3() {
System.out.println("子系统03的method3()被调用!");
}
}
外观模式的应用场景
1、对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系
2、当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问
3、当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性
外观模式的扩展
在外观模式中,当增加或移除子系统时需要修改外观类,这违背了“开闭原则”。如果引入抽象外观类,则在一定程度上解决了该问题。
七、享元模式
享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
其主要缺点是:
1、为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性
2、读取享元模式的外部状态会使得运行时间稍微变长
享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。内部状态指对象共享出来的信息,存储在享元信息内部,并且不会随环境的改变而改变;外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。享元模式的本质是缓存共享对象,降低内存消耗。
享元模式的结构
1、抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入
2、具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口
3、非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中
4、享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象
①UnsharedConcreteFlyweight 是非享元角色,里面包含了非共享的外部状态信息 info;
②Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部状态以参数的形式通过该方法传入;
③ConcreteFlyweight 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;
④FlyweightFactory 是享元工厂角色,它是关键字 key 来管理具体享元;
⑤客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法;
public class FlyweightPattern {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight f01 = factory.getFlyweight("a");
Flyweight f02 = factory.getFlyweight("a");
Flyweight f03 = factory.getFlyweight("a");
Flyweight f11 = factory.getFlyweight("b");
Flyweight f12 = factory.getFlyweight("b");
f01.operation(new UnsharedConcreteFlyweight("第1次调用a。"));
f02.operation(new UnsharedConcreteFlyweight("第2次调用a。"));
f03.operation(new UnsharedConcreteFlyweight("第3次调用a。"));
f11.operation(new UnsharedConcreteFlyweight("第1次调用b。"));
f12.operation(new UnsharedConcreteFlyweight("第2次调用b。"));
}
}
//非享元角色
class UnsharedConcreteFlyweight {
private String info;
UnsharedConcreteFlyweight(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
//抽象享元角色
interface Flyweight {
public void operation(UnsharedConcreteFlyweight state);
}
//具体享元角色
class ConcreteFlyweight implements Flyweight {
private String key;
ConcreteFlyweight(String key) {
this.key = key;
System.out.println("具体享元" + key + "被创建!");
}
public void operation(UnsharedConcreteFlyweight outState) {
System.out.print("具体享元" + key + "被调用,");
System.out.println("非享元信息是:" + outState.getInfo());
}
}
//享元工厂角色
class FlyweightFactory {
private HashMap<String, Flyweight> flyweights = new HashMap<String, Flyweight>();
public Flyweight getFlyweight(String key) {
Flyweight flyweight = (Flyweight) flyweights.get(key);
if (flyweight != null) {
System.out.println("具体享元" + key + "已经存在,被成功获取!");
} else {
flyweight = new ConcreteFlyweight(key);
flyweights.put(key, flyweight);
}
return flyweight;
}
}
示例:五子棋
public class WzqGame {
public static void main(String[] args) {
new Chessboard();
}
}
//棋盘
class Chessboard extends MouseAdapter {
WeiqiFactory wf;
JFrame f;
Graphics g;
JRadioButton wz;
JRadioButton bz;
private final int x = 50;
private final int y = 50;
private final int w = 40; // 小方格宽度和高度
private final int rw = 400; // 棋盘宽度和高度
Chessboard() {
wf = new WeiqiFactory();
f = new JFrame("享元模式在五子棋游戏中的应用");
f.setBounds(100, 100, 500, 550);
f.setVisible(true);
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel SouthJP = new JPanel();
f.add("South", SouthJP);
wz = new JRadioButton("白子");
bz = new JRadioButton("黑子", true);
ButtonGroup group = new ButtonGroup();
group.add(wz);
group.add(bz);
SouthJP.add(wz);
SouthJP.add(bz);
JPanel CenterJP = new JPanel();
CenterJP.setLayout(null);
CenterJP.setSize(500, 500);
CenterJP.addMouseListener(this);
f.add("Center", CenterJP);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
g = CenterJP.getGraphics();
g.setColor(Color.BLUE);
g.drawRect(x, y, rw, rw);
for (int i = 1; i < 10; i++) {
// 绘制第i条竖直线
g.drawLine(x + (i * w), y, x + (i * w), y + rw);
// 绘制第i条水平线
g.drawLine(x, y + (i * w), x + rw, y + (i * w));
}
}
public void mouseClicked(MouseEvent e) {
Point pt = new Point(e.getX() - 15, e.getY() - 15);
if (wz.isSelected()) {
ChessPieces c1 = wf.getChessPieces("w");
c1.DownPieces(g, pt);
} else if (bz.isSelected()) {
ChessPieces c2 = wf.getChessPieces("b");
c2.DownPieces(g, pt);
}
}
}
//抽象享元角色:棋子
interface ChessPieces {
public void DownPieces(Graphics g, Point pt); // 下子
}
//具体享元角色:白子
class WhitePieces implements ChessPieces {
public void DownPieces(Graphics g, Point pt) {
g.setColor(Color.WHITE);
g.fillOval(pt.x, pt.y, 30, 30);
}
}
//具体享元角色:黑子
class BlackPieces implements ChessPieces {
public void DownPieces(Graphics g, Point pt) {
g.setColor(Color.BLACK);
g.fillOval(pt.x, pt.y, 30, 30);
}
}
//享元工厂角色
class WeiqiFactory {
private ArrayList<ChessPieces> qz;
public WeiqiFactory() {
qz = new ArrayList<ChessPieces>();
ChessPieces w = new WhitePieces();
qz.add(w);
ChessPieces b = new BlackPieces();
qz.add(b);
}
public ChessPieces getChessPieces(String type) {
if (type.equalsIgnoreCase("w")) {
return (ChessPieces) qz.get(0);
} else if (type.equalsIgnoreCase("b")) {
return (ChessPieces) qz.get(1);
} else {
return null;
}
}
}
运行结果如下:
享元模式的应用场景
当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多处需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。
享元模式其实是工厂方法模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能。
前面分析了享元模式的结构与特点,下面分析它适用的应用场景。享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式:
1、系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源
2、大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态
3、由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式
扩展
在实际使用过程中,有时候会稍加改变,即存在两种特殊的享元模式:单纯享元模式和复合享元模式。
单纯享元模式,这种享元模式中的所有的具体享元类都是可以共享的,不存在非共享的具体享元类。
复合享元模式,这种享元模式中的有些享元对象是由一些单纯享元对象组合而成的,它们就是复合享元对象。虽然复合享元对象本身不能共享,但它们可以分解成单纯享元对象再被共享。
八、组合模式
组合(Composite Pattern)模式的定义:有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式。
组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,树形结构图如下:
由上图可以看出,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于同一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。
组合模式的主要优点有:
1、组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
2、更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
其主要缺点是:
1、设计较复杂,客户端需要花更多时间理清类之间的层次关系;
2、不容易限制容器中的构件;
3、不容易用继承的方法来增加构件的新功能;
组合模式包含以下主要角色:
1、抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
2、树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件
3、树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法
组合模式分为透明式的组合模式和安全式的组合模式。
透明式的组合模式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
public class CompositePattern {
public static void main(String[] args) {
Component c0 = new Composite();
Component c1 = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
}
//抽象构件
interface Component {
public void add(Component c);
public void remove(Component c);
public Component getChild(int i);
public void operation();
}
//树叶构件:对子节点的操作空实现或抛异常
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
public void add(Component c) {
}
public void remove(Component c) {
}
public Component getChild(int i) {
return null;
}
public void operation() {
System.out.println("树叶" + name + ":被访问!");
}
}
//树枝构件
class Composite implements Component {
private ArrayList<Component> children = new ArrayList<Component>();
public void add(Component c) {
children.add(c);
}
public void remove(Component c) {
children.remove(c);
}
public Component getChild(int i) {
return children.get(i);
}
public void operation() {
for (Object obj : children) {
((Component) obj).operation();
}
}
}
安全式的组合模式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。
public class SafeCompositePattern {
public static void main(String[] args) {
Composite c0 = new Composite();
Composite c1 = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
}
//抽象构件
interface Component {
public void operation();
}
//树叶构件:没有对子节点的操作
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
public void operation() {
System.out.println("树叶" + name + ":被访问!");
}
}
//树枝构件
class Composite implements Component {
private ArrayList<Component> children = new ArrayList<Component>();
public void add(Component c) {
children.add(c);
}
public void remove(Component c) {
children.remove(c);
}
public Component getChild(int i) {
return children.get(i);
}
public void operation() {
for (Object obj : children) {
((Component) obj).operation();
}
}
}
示例:用组合模式实现当用户在商店购物后,显示其所选商品信息,并计算所选商品总价的功能
public class ShoppingTest {
public static void main(String[] args) {
float s = 0;
Bags BigBag, mediumBag, smallRedBag, smallWhiteBag;
Goods sp;
BigBag = new Bags("大袋子");
mediumBag = new Bags("中袋子");
smallRedBag = new Bags("红色小袋子");
smallWhiteBag = new Bags("白色小袋子");
sp = new Goods("婺源特产", 2, 7.9f);
smallRedBag.add(sp);
sp = new Goods("婺源地图", 1, 9.9f);
smallRedBag.add(sp);
sp = new Goods("韶关香菇", 2, 68);
smallWhiteBag.add(sp);
sp = new Goods("韶关红茶", 3, 180);
smallWhiteBag.add(sp);
sp = new Goods("景德镇瓷器", 1, 380);
mediumBag.add(sp);
mediumBag.add(smallRedBag);
sp = new Goods("李宁牌运动鞋", 1, 198);
BigBag.add(sp);
BigBag.add(smallWhiteBag);
BigBag.add(mediumBag);
System.out.println("您选购的商品有:");
BigBag.show();
s = BigBag.calculation();
System.out.println("要支付的总价是:" + s + "元");
}
}
//抽象构件:物品
interface Articles {
public float calculation(); // 计算
public void show();
}
//树叶构件:商品
class Goods implements Articles {
private String name; // 名字
private int quantity; // 数量
private float unitPrice; // 单价
public Goods(String name, int quantity, float unitPrice) {
this.name = name;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
public float calculation() {
return quantity * unitPrice;
}
public void show() {
System.out.println(name + "(数量:" + quantity + ",单价:" + unitPrice + "元)");
}
}
//树枝构件:袋子
class Bags implements Articles {
private String name; // 名字
private ArrayList<Articles> bags = new ArrayList<Articles>();
public Bags(String name) {
this.name = name;
}
public void add(Articles c) {
bags.add(c);
}
public void remove(Articles c) {
bags.remove(c);
}
public Articles getChild(int i) {
return bags.get(i);
}
public float calculation() {
float s = 0;
for (Object obj : bags) {
s += ((Articles) obj).calculation();
}
return s;
}
public void show() {
for (Object obj : bags) {
((Articles) obj).show();
}
}
}
组合模式的应用场景
1、在需要表示一个对象整体与部分的层次结构的场合
2、要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合
九、总结
结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构,就像搭积木, 可以通过简单积木的组合形成复杂的、功能更为强大的结构。结构型模式可以分为类结构型模式和对象结构型模式,也可分为代理模式(Proxy)、适配器模式(Adapter)、桥接模式(Bridge)、装饰模式 (Decorator )、外观模式(Facade)、享元模式(Flyweight)和组合模式(Composite)等 7 类。