结构型设计模式分类
结构型模式分为以下几种:
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
- 装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
1.代理模式
- 代理模式的定义
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象,比如房产中介。
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。 - 代理模式的优点
1.代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
2.代理对象可以扩展目标对象的功能;
3.代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性。 - 代理模式的缺点
1.代理模式会增加中介类,所以会造成系统设计中类的数量增加;
2.在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢,链路变长;
3.增加了系统的复杂度。 - 代理模式的角色
1.抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
2.真实主题(RealSubject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
3.代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。 - 代理模式的实现
根据代理的创建时期,代理模式分为静态代理和动态代理。
1.静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
2.动态:在程序运行时,运用反射机制动态创建代理类而成。
静态代理:
//抽象主题
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("访问真实主题之后的后续处理。");
}
}
//客户端
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.Request();
}
}
//运行结果
访问真实主题之前的预处理。
访问真实主题方法...
访问真实主题之后的后续处理。
动态代理:
动态代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
JDK中生成代理对象的API
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。
//抽象主题
public interface IUserDao {
void save();
void delete();
void saveAndDelete();
}
//真实主题
public class UserDaoImp implements IUserDao{
@Override
public void save() {
System.out.println("save");
}
@Override
public void delete() {
System.out.println("delete");
}
@Override
public void saveAndDelete() {
save();
delete();
}
}
//动态代理类的生成
public class ProxyFactory {
//维护一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象生成代理对象
public Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始do something");
//运用反射执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("结束do something");
return returnValue;
}
}
);
}
}
//客户端
public static void main(String[] args) {
// 目标对象
UserDaoImp target = new UserDaoImp();
// class aop.UserDao
System.out.println(target.getClass());
// 给目标对象,创建代理对象
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
// class $Proxy0 内存中动态生成的代理对象
System.out.println(proxy.getClass());
proxy.save();
proxy.delete();
proxy.saveAndDelete();
}
//运行结果
class com.bestpay.mobilebiz.mapi.common.my.UserDaoImp
class com.sun.proxy.$Proxy0
开始do something
save
结束do something
开始do something
delete
结束do something
开始do something
save
delete
结束do something
真实主题内部调用是不会触发代理的。
- 代理模式的应用场景
1.远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
2.虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
3.安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
4.智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
5.延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。
6.统一的代理处理,比如需要在某一功能类做统一的拦截处理。比如典型的aop和日志处理就是用的动态代理模式。
2.适配器模式
- 适配器模式的定义 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 适配器模式的优点
1.客户端通过适配器可以透明地调用目标接口。
2.复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
3.将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
4.在很多业务场景中符合开闭原则。 - 适配器的缺点
1.适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
2.增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。 - 适配器模式的角色
1.目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
2.适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
3.适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。 - 适配器模式的实现
//类适配器模式
//目标接口
public interface Target
{
public void request();
}
//适配者接口
public class Adaptee
{
public void specificRequest()
{
System.out.println("适配者中的业务代码被调用!");
}
}
//类适配器类
public class ClassAdapter extends Adaptee implements Target
{
public void request()
{
specificRequest();
}
}
//客户端代码
public static void main(String[] args)
{
System.out.println("类适配器模式测试:");
Target target = new ClassAdapter();
target.request();
}
//运行结果
类适配器模式测试:
适配者中的业务代码被调用!
//对象适配器模式
//目标接口
public interface Target
{
public void request();
}
//适配者接口
public class Adaptee
{
public void specificRequest()
{
System.out.println("适配者中的业务代码被调用!");
}
}
//类适配器类
public class ClassAdapter implements Target
{
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee)
{
this.adaptee=adaptee;
}
public void request()
{
adaptee.specificRequest();
}
}
//客户端代码
public static void main(String[] args)
{
System.out.println("对象适配器模式测试:");
Adaptee adaptee = new Adaptee();
Target target = new ObjectAdapter(adaptee);
target.request();
}
//运行结果
对象适配器模式测试:
适配者中的业务代码被调用!
- 适配器模式的应用场景
1.原系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
2.使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同
3.桥接设计模式
- 桥接设计模式的定义 将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
- 桥接设计模式的优点
1.抽象与实现分离,扩展能力强
2.符合开闭原则
3.符合合成复用原则
4.其实现细节对客户透明 - 桥接设计模式的缺点 1.由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。
- 桥接设计模式的角色
1.抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
2.扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
3.实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
4.具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。 - 桥接设计模式的实现
//实现化角色
public interface Implementor {
public void OperationImpl();
}
//具体实现化角色
public class ConcreteImplementorA implements Implementor {
public void OperationImpl() {
System.out.println("具体实现化(Concrete Implementor)角色被访问");
}
}
//抽象化角色
public abstract class Abstraction {
protected Implementor imple;
protected Abstraction(Implementor imple) {
this.imple = imple;
}
public abstract void Operation();
}
//扩展抽象化角色
public class RefinedAbstraction extends Abstraction {
protected RefinedAbstraction(Implementor imple) {
super(imple);
}
public void Operation() {
System.out.println("扩展抽象化(Refined Abstraction)角色被访问");
imple.OperationImpl();
}
}
public static void main(String[] args) {
Implementor imple = new ConcreteImplementorA();
Abstraction abs = new RefinedAbstraction(imple);
abs.Operation();
}
//运行结果
扩展抽象化(Refined Abstraction)角色被访问
具体实现化(Concrete Implementor)角色被访问
- 桥接模式的使用场景
1.当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
2.当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
3.当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
4.装饰设计模式
- 装饰设计模式的定义 指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
- 装饰设计模式的优点
1.装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
2.通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果 3。装饰器模式完全遵守开闭原则 - 装饰设计模式的缺点 1.装饰模式会增加许多子类,过度使用会增加程序得复杂性
- 装饰设计模式的角色
1.抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
2.具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
3.抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
4.具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。 - 装饰设计模式的实现
//抽象构件角色
public interface Component {
public void operation();
}
//具体构件角色
public class ConcreteComponent implements Component {
public ConcreteComponent() {
System.out.println("创建具体构件角色");
}
public void operation() {
System.out.println("调用具体构件角色的方法operation()");
}
}
//抽象装饰角色
public class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}
//具体装饰角色
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation();
addedFunction();
}
public void addedFunction() {
System.out.println("为具体构件角色增加额外的功能addedFunction()");
}
}
//客户端
public static void main(String[] args) {
Component p = new ConcreteComponent();
p.operation();
System.out.println("---------------------------------");
Component d = new ConcreteDecorator(p);
d.operation();
}
//运行结果
创建具体构件角色
调用具体构件角色的方法operation()
---------------------------------
调用具体构件角色的方法operation()
为具体构件角色增加额外的功能addedFunction()
- 装饰设计模式的应用场景
1.当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
2.当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
3.当对象的功能要求可以动态地添加,也可以再动态地撤销时。
5.外观模式
- 外观模式的定义 外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。比如统一的contorller封装,不同的底层子系统就是用的外观模式。
- 外观模式的优点
1.降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
2.对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
3.降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。 - 外观模式的缺点
1.不能很好地限制客户使用子系统类,很容易带来未知风险。
2.增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。 - 外观模式的角色
1.外观(Facade)角色:为多个子系统对外提供一个共同的接口。
2.子系统(SubSystem)角色:实现系统的部分功能,客户可以通过外观角色访问它。
3.客户(Client)角色:通过一个外观角色访问各个子系统的功能。 - 外观模式的实现
//外观角色
public 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();
}
}
//子系统角色
public class SubSystem01 {
public void method1() {
System.out.println("子系统01的method1()被调用!");
}
}
//子系统角色
public class SubSystem02 {
public void method2() {
System.out.println("子系统02的method2()被调用!");
}
}
//子系统角色
public class SubSystem03 {
public void method3() {
System.out.println("子系统03的method3()被调用!");
}
}
//客户端
public static void main(String[] args) {
Facade f = new Facade();
f.method();
}
//运行结果
子系统01的method1()被调用!
子系统02的method2()被调用!
子系统03的method3()被调用!
- 外观模式的应用场景
1.对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
2.当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
3.当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
6.享元模式
- 享元模式的定义 运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
- 享元模式的优点 1.相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
- 享元模式的缺点
1.为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
2.读取享元模式的外部状态会使得运行时间稍微变长。 - 享元模式的理解
享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。
1.内部状态指对象共享出来的信息,存储在享元信息内部,并且不回随环境的改变而改变; 2.外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。 - 享元模式的角色
1.抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
2.具体享元(ConcreteFlyweight)角色:实现抽象享元角色中所规定的接口。
3.非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
4.享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。 - 享元模式的实现
//非享元角色
public lass UnsharedConcreteFlyweight {
private String info;
UnsharedConcreteFlyweight(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
//抽象享元角色
public interface Flyweight {
public void operation(UnsharedConcreteFlyweight state);
}
//具体享元角色
public 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());
}
}
//享元工厂角色
public 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 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。"));
}
//运行结果
具体享元a被创建!
具体享元a已经存在,被成功获取!
具体享元a已经存在,被成功获取!
具体享元b被创建!
具体享元b已经存在,被成功获取!
具体享元a被调用,非享元信息是:第1次调用a。
具体享元a被调用,非享元信息是:第2次调用a。
具体享元a被调用,非享元信息是:第3次调用a。
具体享元b被调用,非享元信息是:第1次调用b。
具体享元b被调用,非享元信息是:第2次调用b。
可以看到a和b只被创建了一次,其余时候直接获取已创建好的享元角色,享元模式的本质是缓存共享对象,降低内存消耗。不会存在多个相似的对象存在于内存中。
- 享元模式的应用场景
1.系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
2.大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
3.由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
7.组合模式
- 组合模式的定义 组合模式有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式。
- 组合模式的优点
1.组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
2.更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”; - 组合模式的缺点
1.设计较复杂,客户端需要花更多时间理清类之间的层次关系;
2.不容易限制容器中的构件;
3.不容易用继承的方法来增加构件的新功能; - 组合模式角色 1.抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除) 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
- 组合模式的实现
//抽象构件
public interface Component {
public void add(Component c);
public void remove(Component c);
public Component getChild(int i);
public void operation();
}
//树叶构件
public 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 + ":被访问!");
}
}
//树枝构件
public 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 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();
}
//运行结果
树叶1:被访问!
树叶2:被访问!
树叶3:被访问!
- 组合模式的应用场景
1.在需要表示一个对象整体与部分的层次结构的场合。
2.要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。