代理模式
为其他对象提供一种代理以控制对这个对象的访问。
组成
(1)抽象角色(Subject):通过接口或抽象类声明真实角色实现的业务方法。
(2)代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
(3)真实角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
代理模式的应用
- (1)远程代理。为一个位于不同的地址空间的对象提供一个本地的代理对象。这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。这样可以隐藏一个对象存在于不同地址空间的事实,使得客户端可以访问在远程机器上的对象。
- (2)虚拟代理。根据需要创建开销很大的对象。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时
才会被真正创建。- (3)安全代理。用来控制真实对象访问时的权限。
- (4)智能指引。当调用真实对象时,代理处理另外的一些事。
应用实例
Spring 的AOP
优点
1.职责清晰: 真实角色就是实现实际的业务逻辑,不用关心其他非本职的事物
2.高扩展性: 真实角色可以随时更换或扩展,只需要实现接口就行,而代理不需要有任何变化
缺点
(1)由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
(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("访问真实主题之后的后续处理。");
}
}
动态代理
在实现阶段不需要关心代理谁,在运行阶段会动态生成一个代理类去代理指定的对象
默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用CGlib来生成代理
(1)jdk动态代理:接口+InvocationHandler+目标对象
主要用到java.lang.reflect中的两个类:'Proxy和InvocationHandler’
InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类代码,动态的将横切逻辑与业务逻辑编织在一起
1.定义业务逻辑接口
2.实现业务逻辑接口创建业务实现类
3.实现(implements)InvacationHandler代理接口,创建代理类
4.创建业务类和代理类对象,通过代理类对象.bind(业务类对象)返回一个动态代理对象,然后通过动态代理对象对方法进行调用。
缺点:要求目标类必须实现对应方法非接口,它只能为接口创建代理
//InvocationHandler + invoke()
public class Proxy implements InvocationHandler {
// 被代理类的实例
Object obj;
// 将被代理者的实例传进动态代理类的构造函数中
public Proxy(Object obj) {
this.obj = obj;
}
/**
* 覆盖InvocationHandler接口中的invoke()方法
* 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构
* 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到
* 控制被代理对象的行为,下面的before、after就是我们可以进行特殊
* 代码切入的扩展点了。
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/*
* before :doSomething();
*/
Object result = method.invoke(this.obj, args);
/*
* after : doSomething();
*/
return result;
}
}
(2)cglib动态代理:接口或类+MethodInterceptor+目标对象
主要用到java.lang.reflect中的两个类:‘MethodInterceptor和CglibProcy’
1.定义业务类,无需实现接口(也可以实现)
2.实现MethodInterceptor方法代理接口,创建代理类
3.创建业务类和代理类对象,通过代理类对象.getInstance(业务类对象)返回一个动态代理对象,然后通过动态代理对象对方法进行调用。
特点:能代理类和接口,但是不能代理final类
//MethodIntercepter + intercept()
public class Proxy implements MethodInterceptor {
// 单例模式
private static Proxy instance = new CGLibProxy();
private Proxy() {}
public static Proxy getInstance () {
return instance;
}
public <T> T getProxy(Class<T> cls) {
return (T) Enhancer.create(cls, this);
}
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
sayBefore();
Object result = methodProxy.invokeSuper(obj, objects);
sayAfter();
return result;
}
private void sayBefore() { System.out.println("before..."); }
private void sayAfter() { System.out.println("after..."); }
}
享元模式
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。
代码实现
在享元模式中引入了享元工厂类FlyweightFactory,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
享元工厂类:FlyweightFactory
享元类:ConcreteFlyweight
享元对象:Flyweight
class FlyweightFactory {
//定义一个HashMap用于存储享元对象,实现享元池
private HashMap flyweights = newHashMap();
public Flyweight getFlyweight(String key){
//如果对象存在,则直接从享元池获取
if(flyweights.containsKey(key)){
return(Flyweight)flyweights.get(key);
}
//如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
else {
Flyweight fw = newConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
优点
- 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
- 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享
缺点
- 享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
- 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
应用
当我们项目中创建很多对象,而且这些对象存在许多相同模块,这时,我们可以将这些相同的模块提取出来采用享元模式生成单一对象,再使用这个对象与之前的诸多对象进行配合使用,这样无疑会节省很多空间。
1.String中的享元模式
Java中将String类定义为final(不可改变的),JVM中字符串一般保存在字符串常量池中,java会确保一个字符串在常量池中只有一个拷贝,这个字符串常量池的特点就是有且只有一份相同的字面量,如果有其它相同的字面量,jvm则返回这个字面量的引用,如果没有相同的字面量,则在字符串常量池创建这个字面量并返回它的引用。
2.Integer中的享元模式
可以看到
Integer
默认先创建并缓存-128 ~ 127
之间数的Integer
对象,当调用valueOf
时如果参数在-128 ~ 127
之间则计算下标并从缓存中返回,否则创建一个新的Integer
对象
3.Long中的享元模式
与Integer原理类似
4.Apache Commons Pool2中的享元模式(对象池)
将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。用于充当保存对象的“容器”的对象,被称为“对象池”
桥接模式
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
组成
- '实现化'(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
- '具体实现化'(Concrete Implementor)角色:给出实现化角色接口的具体实现。
- '抽象化'(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
- '扩展抽象化'(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
优点
- (1) 由于抽象与实现分离,所以扩展能力强;
- (2) 可动态的切换实现
由于桥接模式实现了抽象和实现的分离,所以在实现桥接模式时,就可以实现动态的选择和使用具体的实现。- (3) 实现细节对客户端透明,可以对用户隐藏实现细节。
缺点
- (1) 由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。
- (2) 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。
应用场景
- (1)当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- (2)当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- (3)当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
实现
public class BridgeTest
{
public static void main(String[] args)
{
Implementor imple=new ConcreteImplementorA();
Abstraction abs=new RefinedAbstraction(imple);
abs.Operation();
}
}
//实现化角色
interface Implementor
{
public void OperationImpl();
}
//具体实现化角色
class ConcreteImplementorA implements Implementor
{
public void OperationImpl()
{
System.out.println("具体实现化(Concrete Implementor)角色被访问" );
}
}
//抽象化角色
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();
}
}
适配器模式
将一个类的接口转换成客户希望的另外一个接口
。Adapter模式使原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器模式分为类结构型模式
和对象结构型模式
两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
优点
- 客户端通过适配器可以透明地调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
缺点
对类适配器来说,更换适配器的实现过程比较复杂。
组成
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
模式的应用场景
适配器模式(Adapter)通常适用于以下场景。
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
实际应用
https://blog.csdn.net/wwwdc1012/article/details/82780560
(1)spring AOP中的适配器模式
- 在Spring的Aop中,使用的 Advice(通知) 来增强被代理类的功能。
- Advice的类型有:MethodBeforeAdvice、AfterReturningAdvice、ThrowsAdvice
- 在每个类型 Advice 都有对应的拦截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptorSpring
- 需要将每个 Advice 都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对 Advice 进行转换
(2)spring JPA中的适配器模式
- 在Spring的ORM包中,对于JPA的支持也是采用了适配器模式,首先定义了一个接口的 JpaVendorAdapter,然后不同的持久层框架都实现此接口。
- jpaVendorAdapter:用于设置实现厂商JPA实现的特定属性,如设置Hibernate的是否自动生成DDL的属性generateDdl;这些属性是厂商特定的,因此最好在这里设置;
- 目前Spring提供 HibernateJpaVendorAdapter、OpenJpaVendorAdapter、EclipseLinkJpaVendorAdapter、TopLinkJpaVendorAdapter 四个实现。
- 其中最重要的属性是 database,用来指定使用的数据库类型,从而能根据数据库类型来决定比如如何将数据库特定异常转换为Spring的一致性异常,目前支持如下数据库
- DB2、DERBY、H2、HSQL、INFORMIX、MYSQL、ORACLE、POSTGRESQL、SQL_SERVER、SYBASE
(3)spring MVC中的适配器模式
Spring MVC中的适配器模式主要用于执行目标 Controller 中的请求处理方法。在Spring MVC中,DispatcherServlet 作为用户,HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。
外观模式
为多个子系统提供一个统一的接口,而使这些子系统更加容易被访问的模式。降低系统的耦合度。
迪米特法则的典型应用,但是增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
组成
- '外观'(Facade)角色:为多个子系统对外提供一个共同的接口。
- '子系统'(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
- '客户'(Client)角色:通过一个外观角色访问各个子系统的功能。
应用场景
- (1) 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
- (2) 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
- (3) 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
实现
//客户角色
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();
}
}
//子系统角色
class SubSystem01
{
public void method1()
{
System.out.println("子系统01的method1()被调用!");
}
}
//子系统角色
class SubSystem02
{
public void method2()
{
System.out.println("子系统02的method2()被调用!");
}
}
//子系统角色
class SubSystem03
{
public void method3()
{
System.out.println("子系统03的method3()被调用!");
}
}
组合模式
有时又叫作部分-整体模式,将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。
优点
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
缺点
1.设计较复杂,客户端需要花更多时间理清类之间的层次关系;
2.不容易限制容器中的构件;
3.不容易用继承的方法来增加构件的新功能;
组成
- '抽象构件'(Component)角色:它的主要作用是为树叶构件和树枝构件'声明公共接口,并实现它们的默认行为'。在透明式的组合模式中抽象
构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
- '树叶构件'(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于'实现抽象构件角色中声明的公共接口'。
- '树枝构件'(Composite)角色:是组合中的分支节点对象,它有子节点。它'实现了抽象构件角色中声明的接口',它的主要作用是存储和管理
子部件,通常包含 Add()、Remove()、GetChild() 等方法。
分类
(1) 透明式
抽象构件声明了所有子类中的全部方法,包括add,remove等,使树叶和树枝具备完全一致的行为接口。所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。
缺点:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
(2) 安全式
抽象构件不去声明add、remove方法,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题。
缺点:由于不够透明,树叶和树枝不具备相同的接口,客户端的调用需要做相应的判断带来不便。
应用场景
1.在需要表示一个对象整体与部分的层次结构的场合。
2.要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
Java AWT/Swing中的简单组件 JTextComponent 有子类 JTextField、JTextArea,容器组件 Container 也有子类 Window、Panel。
实现
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();
}
}
}
装饰模式
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
就增加功能来说,装饰模式比生成子类更为灵活。
结构与实现
- 通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。
- 如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。
组成
- '抽象构件'(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- '具体构件'(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- '抽象装饰'(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- '具体装饰'(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
应用场景
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
实际中的应用
Java I/O 标准库的设计。例如,
- (1) InputStream 的子类 FilterInputStream,
- (2) OutputStream 的子类 FilterOutputStream,
- (3) Reader 的子类BufferedReader 以及 FilterReader,
- (4) Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。
实现
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()");
}
}