什么是结构型模式
关注如何将现有类或对象组织在一起形成更加强大得结构,7种结构型设计模式,使用不同得方式组合类与对象,使之可以协同工作。
适配模式
如果在系统种存在不兼容得接口,可以通过引入一个适配器使原本不兼容得两个类能够一起协同工作
适配器的数据结构
- 目标抽象类(target):定义客户端直接调用的接口方法
- 适配器类(adaptee):作为一个转换器类,将适配者和目标类进行适配
- 适配者类(adaptor):被适配得角色,包含客户最终调用的业务方法。 UnSafe.java类
适配器的实现
Adaptee:specificRequest();
Target:request
Client:客户端想要调用Adaptee的request方法,但Adaptee只提供specificRequest方法
例如:购买第三方类库,但是没有源代码,此时可使用适配器模式统一访问接口
Adapter包装了一个适配者类
- 类适配器
public class Adapter extends Adaptee implements Target{
public void request(){
super.specificRequest();
}
}
- 对象适配器
public class Adapter extends Target{
private Adaptee adaptee; //维持一个适配者的引用
public Adapter(Adaptee adaptee){
this.adaptee=adaptee;
}
public void request(){
adaptee.specificRequest(); //转发调用
}
}
缺省适配器
当不需要实现接口中的所有方法时,可先设计一个抽象类实现该接口并实现默认方法,然后就可以有选择性的覆盖适配中的方法。
- 适配者接口
- 缺省适配器类
- 具体业务类:继承缺省适配器类,根据需要有选择性
适配器优缺点
优点:
- 系统通过适配器来解耦目标类和适配者
- 系统可以将多个适配者适配到一个目标
- 系统可以适配一个适配者的子类
缺点: - 适配者类不能为最终类
- 在适配器中修改适配者的某些方法很繁琐
适配器模式的使用环境
- 系统需要使用现有的类,而这些类不符合系统的需要
- 系统需要将第三方类集成到系统中
桥接模式
如果系统中的某个类存在两个变化的维度,通过桥接模式可以将这两个维度分离出来,使两者可以独立扩展。
抽象关联——>多层继承
桥接模式定义:将抽象部分和实现部分解耦,使得两者都能独立变化。
-
假如需要大中小3种型号,每种型号可以画出12种颜色的画笔盒子,蜡笔需要3*12=36支,毛笔需要3+12=15支。
-
分析:蜡笔对颜色扩展or对型号扩展都会影响另一个维度,但毛笔却不会。我们说毛笔实现了解耦合。
桥接模式数据结构
- 抽象类(Abstraction):包含了另一个实现类接口的对象,并维护该对象
- 补充抽象类(RefinedAbstraction):实现了Abstraction声明的抽象业务,并可调用Implementor的业务方法 ——>维度1继承结构
- 实现类接口(Implementor):此接口进提供基本操作,Abstraction定义复杂操作,通过关联关系,抽象类不仅可以调用到自己的方法,而且可以调用到Implementor定义的方法
- 具体实现类(ConcreteImplementor):提供实现类接口的不同实现,程序运行时将提供给抽象类多个业务方法 ——>维度2继承结构
桥接模式的实现
使用桥接模式首先应识别出一个类具有的两个独立变化维度,将他们设计成一个抽象角色,一个实现角色,为两个维度都提供抽象层,并建立抽象耦合
## 定义实现角色的抽象层,非固有维度
public interface Implementor{
public void operate();
}
## 定义实现角色的具体类
public class ConcreteImplementor implements Implementor{
public void operate(){
//实现类业务方法的实现
}
}
## 定义抽象角色的抽象类
public abstract class Abstraction{
protected Implementor imp;
public Abstract(Implementor imp){
this.imp=imp;
}
public abstract void oper;
}
## 定义抽象角色的实现类
public class RefinedAbstract extends Abstraction(){
public void oper(){
//定义抽象角色的业务方法
}
}
桥接模式和适配器模式的联用
某系统种的报表处理模块,需要将报表显示和数据输出分开,系统可以有多种报表显示方式,和多种报表导出方式(文本文件,excel文件)。若导出格式为excel文件,则需要调用与excel相关的API,此API由厂商提供。
实现角色:数据导出——>excelImplementor适配者角色
implementor/target:数据导出
textImplementor(文本导出)
excelImplementor(excel导出)—>包含excelAdaptee,通过excelAPI实现Implemenor
抽象角色:数据展示
Abstraction——>包含implementor
display1Abstraction
display2Abstraction
桥接模式的优缺点
优点:
- 系统通过关联关系解耦了抽象角色和实现角色,使得他们可以沿着各自的维度进行扩展
- 系统通过桥接模式代替了多层继承,极大的减少了子类的个数
- 扩展两个维度都不用修改原有代码,符合开闭原则
缺点: - 要求开发者一开始就使用抽象层进行设计
- 需要正确的识别出系统的两个独立变化的维度
桥接模式的适用环境
系统存在两个变化维度的类,可以将其定义为抽象角色和实现角色,使得他们独立进行扩展。
组合模式
例如:目录结构,菜单结构,公司组织结构等
叶子构件和容器构件可通过递归组合成一个树形结构,组合模式提供了一个公共抽象结构,客户端直接操作抽象结构,而无需关心是哪类构件
组合模式定义:组合多个对象形成树型结构,以表示部分-整体关系的层次结构,组合模式让客户端可以统一对待单个对象和组合对象。
组合模式数据结构
- 抽象构件(Component):为叶子构建和容器构建声明接口,包含所有构建的公共行为
- 叶子构件(Leaf):它在组合结构中表示叶子节点对象
- 容器构件(Composite):它在组合结构中表示容器对象,它包含子节点,提供一个集合存储子节点
组合模式实现
## 定义抽象构建
public abstract class Component{
public abstract void add(Component); //增加成员
public abstract void remove(Component); //删除成员
public abstract Component getChild(Component t); //获取成员
public abstract void operation(Component); //业务方法
}
public class Leaf extends Component{
public abstract void add(Component){错误提示}; //增加成员
public abstract void remove(Component){错误提示}; //删除成员
public abstract Component getChild(Component t){错误提示}; //获取成员
public abstract void operation(Component); //业务方法
}
public class Composite extends Component{
private List<Component> list=new ArrayList<Component>();
public abstract void add(Component){
list.add(c);
}; //增加成员
public abstract void remove(Component){
list.remove(c);
}; //删除成员
public abstract Component getChild(Component t){
return (Component)list.get(i);
}; //获取成员
public abstract void operation(Component); //业务方法
}
组合模式优缺点
为树形结构的面向对象提供了一种灵活的解决方案
优点
- 系统清晰定义层级结构,方便对整个树形结构进行控制
- 系统增加新的容器构建和叶子构建无需修改现有代码
- 客户端对叶子节点和容器节点一视同仁
缺点
很难对容器中的构建类型进行限制
组合模式适用环境
- 需要表示整体和部分的层次结构,也就是一个树形结构
- 系统中能够分离出叶子对象和容器对象
装饰模式
通过关联关系取代类之间的继承关系,使用一种无需定义子类的方式给对象动态的添加职责。
装饰模式定义:动态给一些对象增加一些额外的职责,就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案
装饰模式数据结构
- 抽象构件(Component):具体构件类和抽象装饰类的共同父类
- 具体构件(ConcreteComponent):定义具体构件对象,被装饰的对象
- 抽象装饰类(Decorator):抽象构件类的子类,用于给具体构件增加职责。关联了抽象构件对象的引用,可以调用具体构件的方法
- 具体装饰类(ConcreteDecorator):负责向构件添加新得职责,每一个具体构件都定义一些新得行为,可以调用抽象装饰类的方法
装饰模式实现
具体装饰类通过新增成员变量or成员方法来扩充具体构建类的功能。
如需在系统增加新的具体构件和新得具体装饰类,无需修改现有代码。
## 构建类和装饰类的公共方法,提供统一访问入口
public abstract class Component{
public abstract void operation();
}
## 具体构建类,待装饰的类
public class ConcreteComponent extends Component{
public void operation(){}
}
## 定义抽象装饰类,operation还是调用原有方法。所以他没有真正实现装饰,而是提供一个接口,将具体装饰过程交给子类
## 维护Component引用,可以将具体构件注入其中通过具体装饰类进行装饰
## 还可将已经装饰过的Decorator子类进行再次装饰
public class Decorator extends Component{
private Component component; //维护一个对抽象构件的引用
public Decorator(component){ //注入一个抽象构建类型
this.component=component;
}
public void operation(){
component.operation(); //调用原有业务方法,并没有在这里进行装饰
}
}
## 定义具体装饰类
public class ConcereteDecorator extends Decorator{
public ConcereteDecorator(Component component){
super(component);
}
public void opoeration(){
super.operation();//调用原有业务方法
addBehavior(); ///调用新增业务方法
}
//新增业务方法
public void addBehavior();
}
装饰模式优缺点
优点
- 系统可以通过一种动态方式扩展一个对象的功能
- 系统可以对对象进行多次装饰
- 系统可以随意扩展具体构建类和具体装饰类两个类,符合开闭原则
装饰模式的使用环境
- 在不影响其他对象的情况下,动态、透明的对单个对象添加职责
- 在不能采用继承的方式对系统扩展的情况下,可以使用装饰模式。
2.1 系统中存在大量独立扩展,为支持每一种扩展会产生大量子类
2.2 类被final修饰
代理模式
客户端不想或不能访问某个对象时,可以通过一个代理对象来间接访问。为了保证客户端使用的透明性,代理对象和真实对象需要实现相同的接口。
代理模式定义:给某一个对象提供有一个代理或占位符,并由对象控制对原对象的访问,它去掉客户不能看到的内容或服务,或者增加客户需要的新服务。
代理模式数据结构
为了统一访问真实对象and代理对象,在代理模式引入抽象层
- 抽象角色(Subject):声明了真实角色和代理角色的公共接口,客户端通常和抽象角色进行交互
- 代理角色(Proxy):包含对真实角色的引用,从而可以操纵真实角色。通常在客户端调用真实角色之前或之后会 执行其他操作,而不仅仅是单纯调用
- 真实角色(RealSubject):此对象执行真正的业务操作
代理模式实现
## 定义抽象角色,定义公共接口
public abstract class Subject{
public abstract void request();
}
## 定义真实角色
public class RealSubject extends Subject{
public void request();
}
## 定义代理类
public class Proxy extends Subject{
private RealSubject realSubject=new RealSubject();//维护一个真实对象的引用
public void preRequest(){}
public void request(){
preRequest();
realSubject.request();
postRequest();
}
public void postRequest();
}
不同类型的代理类
远程代理(Remote Proxy)
它使客户端程序可以访问在远程主机上的对象,远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在,客户端感知远程业务对象完全是在本地。
远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法进行调用。
Java语言可通过RMI(远程方法调用)的机制来实现远程代理,它可以实现某JVM中的对象调用另一个JVM中的方法。Stub对象=Proxy对象
Java动态代理
- 静态代理
传统的代理是通过一个Proxy对象调用真实角色的业务方法,代理类实现的接口和代理的方法都是固定的。
若需要为不同的真实角色提供代理类,或者代理一个真实角色的不同方法,都需要增加新得代理类。 - 动态代理
系统在运行时根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实角色类而且可以代理不同的方法。AOP和事务管理中发挥了重要作用。
Java动态代理重点类
- Proxy类提供了创建动态代理类和实例对象的方法
a) public static Class<?> getProxyClass(ClassLoader loader,Class<?> interface)
返回:**Class类型的代理类**
loader:类加载器
inerface:代理类实现接口数组
b) public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces InvocationHandler handler)
返回:**动态创建的代理类实例**
loader:类加载器
interfaces: 代理类实现的接口列表
handler:调用的处理类
- InvocationHandler接口
代理对象的调用处理者的抽象父类,每一个代理类的实例都可提供一个相关的具体调用处理者
public Object invoke(Object proxy,Method method,Object[] args)
该方法处理对**代理类实例方法**的调用并返回结果,当代理对象的业务方法别调用时将自动调用该方法。
proxy:代理类的实例
method:需要代理的方法
args:代理方法的参数数组
总结:动态代理类需要在运行时指定,所代理真实角色的接口,客户端在调用动态代理对象的方法时,调用请求会将请求自动转发给InvocationHandler的invoke方法,由invoke方法统一处理请求。
java动态代理实现
某公司要为公司OA系统的数据访问层(DAO)增加方法调用日志,记录每一个方法被调用的时间和调用结果,使用java动态代理设计和实现。
## 定义抽象角色:抽象用户dao
public interface AbstractUserDao{
public Boolean findUserById(String userID);
}
## 定义抽象角色:抽象文档dao
public interface abstractDocumentDao(){
public Boolean deleteDocumentById(String documentId);
}
## 定义具体角色:用户dao
public class UserDao implements AbstractUserDao{
public Boolean findUserById(String userID){
syso("查询Id为"+userID);
}
}
public class DocumentDao implements AbstractDocumentDao{
public Boolean deleteDocuementById(Stirng documentId){
syso("删除文档id"+documentId)
}
}
## 定义请求处理程序类
public class DaoLogHandler implements InvocationHandler{
private Calendar calendar;
private Object object;
public DaoLogHandler(Object){ //注入一个需要代理的 真实角色对象
this.object=object;
}
public Object invoke(Object proxy,Method method,Object[] args){
beforeInvoke();
Object result=method.invoke(object,args); //转发调用
afterInvoke();
return result;
}
public void beforeInvoke(){
Calendar calendar=Calendar.getInstance();
int hour=calendar.get(Calendar.HOUR_OF_MINUTE);
int minute=calendar.get(Calendar.MINUTE);
int second=calendar.get(Clendar.SECOND);
String time=hour+":"+minute+":"+second;
syso("调用时间"+time);
}
}
## 客户端程序
public class Client{
public static void main(String args[]){
AbstractUserDao userDao=new UserDao();
InvocationHandler handler=new DaoLogHandler(userDao);//将真实角色传入
//动态创建抽象角色的AbstractUserDao动态代理对象
//动态代理对象将对抽象角色的请求转发到真实角色中
AbstractUserDao proxy=(AbstractUserDao)Proxy.newProxyInstance(AbstractUserDao.class.getClassLoader(),new Class[]{AbstractUserDao.class},handler);
proxy.findUserById("张无忌"); //调用代理对象的业务方法
}
}
虚拟代理(Virtual Proxy)
当需要创建一个资源消耗大的对象,先创建一个消耗较小的对象来表示,当需要时才会真正的创建。
保护代理(Protect Proxy)
控制对一个对象的访问,给不同用户不同的使用权限
缓冲代理(Cache Proxy)
对一个操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
智能引用代理(Smart Reference Proxy)
对一个对象被引用时提供额外操作,比如记录被调用的次数
代理模式优缺点
优点:
- 解耦了调用者和被调用者
- 客户端针对抽象主角进行编程,添加代理类无需修改源代码,具有开闭原则
缺点: - 造成请求的处理请求速度慢
- 增加代码复杂度
代理模式的适应场景
- 客户端需要访问远程主机时
- 当需要一个消耗较少的对象代表消耗过多的资源
- 当需要为某个频繁使用的操作结果 提供一个临时存储空间,供多个客户端重用
- 当控制一个对象需要不同访问权限时
- 当需要为一个对象的访问提供额外操作时