目录
- 创建型
- 1.简单工厂(Simple Factory)
- 2.工厂方法(Factory Method)
- 3.抽象工厂(Abstract Factory)
- 4.建造(Builder)
- 5.单例(Singleton)
- 6.原型(Prototype)
- 结构型
- 7.外观(Facade)
- 8.适配器(Adapter)
- 9. 桥接(Bridge)
- 10.组合(Composite)
- 11.装饰(Decorator)
- 12.享元(Flyweight)
- 13.代理(Proxy)
- 行为型
- 14.责任链(Chain of Responsibility)
- 15.策略(Strategy)
- 16.模板(Template)
- 17.命令(Command)
- 18.观察者(Observer)
- 19.访问者(Visitor)
创建型
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。就像我们去商场购买商品时,不需要知道商品是怎么生产出来一样,因为它们由专门的厂商生产。
1.简单工厂(Simple Factory)
定义一个工厂类,它可以根据参数的不同返回不同类的实例,使客户端不需要选择实例化的类及其实现过程,被创建的实例通常都具有共同的父类。其中工厂类中创建实例的方法通常是静态方法,因此又称静态工厂方法;
使用场景:对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。
缺点:工厂类集中了创建实例的逻辑,扩展时除了增加功能子类,还需要修改工厂类,破坏了其封闭性。
2.工厂方法(Factory Method)
将简单工厂模式中工厂创建对象的选择延迟到其子类,增强其扩展能力。具体形式为:一个抽象工厂类或者一个工厂接口和多个具体的生成对象的工厂;
使用场景:1. 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。2. 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。3. 客户不关心创建产品的细节,只关心产品的品牌;
优点是这样在扩展时只需要增加功能类和相应的工厂类即可,免去了对原有工厂类的修改中选择逻辑带来的模块耦合;
缺点是会将选择逻辑转移到客户端。
3.抽象工厂(Abstract Factory)
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。简言之,工厂类中里使用了一系列的抽象接口,拥有创建多个相应实例对象的方法。
使用场景:1. 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。2. 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。3. 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
缺点:在增加功能时需要增加相应的接口和具体类,并修改多个工厂类,带来较大的修改。类的增加是一个拓展过程,无可避免,但可以精简修改的过程。
解决:1. 简单工厂模式:用一个工厂代替多个工厂,用 case 取代多个工厂类,减少需要修改多个工厂的繁琐。不足:会有较多的 case 语句,需要对程序进行修改;2. 反射+抽象工厂:利用反射技术,以字符串变量替代程序中实例化工厂类的选择,从而起到统一在程序中进行集体更换的目的,减少需要修改多个工厂的繁琐。不足:仍需要在程序中通过修改字符串变量以选择实例化对象;3.反射+配置文件:通过读配置文件赋字符串变量值,可以在不改变程序的前提下进行修改;
4.建造(Builder)
将一个复杂对象的构建和它的表示分离,使得同样的构造过程可以创建不同的对象。简言之,构造一个指挥类去决定抽象构造类的使用,而抽象构造类定义固定的抽象方法,具体构造类继承它并实现相应接口,包装相关产品类。总体就是指挥类通过具体构造类创造和包装产品,使客户端不需要知道具体的每个方法,只需要使用指挥类即可。
使用场景:适用于创建复杂对象,可以在抽象建造类定义公用的组成构建,差异组件的组成和顺序让具体的建造类来装配;例如JDK中StringBuilder;优点:组成不需要了解内部组成的构建的细节,只需要选择具体的指挥类就可以,并且可以直接通过建造类定制组件的组装;
缺点:内部组成复杂,当内部发生改变时的维护成本高;
5.单例(Singleton)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。将类的构造方法私有化,使得外部不能使用 new 创建实例,同时让这个类本身去负责保存它的唯一实例,来保证没有其他实例被创建,并提供一个访问该实例的静态公共方法。
使用场景:应用中频繁使用的类,占用较多资源的类,需要被共享的类。例如Spring的ApplicationContext,J2EE的ServletContext和数据库的连接池等,JDK中的System类;
优点:降低了内存开销,全局唯一的访问点便于资源管理;
缺点:一般没有接口,并且单例的方法都在构建类中,对扩展不友好;
最佳实践:适用于jdk1.5后实例属性不改变的单例对象创建。
6.原型(Prototype)
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。就是从一个对象再创建一个可定制的对象,而不需要知道创建的细节,保留重复的部分并再定制信息。将引用的对象类独立出来,并实现克隆自身的方法,在原型中使用该方法,这可以实现深复制。
使用场景:构建的对象之间只有部分属性是不同的;创建时需要各种数据准备和权限访问时可以简化创建过程;例如spring的prototype的Bean和JSON.parseObject方法,订单创建等运行时动态指定部分属性业务场景;
优点:原型模式的性能消耗比new更低;
缺点:需要为每一个类都配置一个 clone 方法,可能出现深嵌套增加复杂度,并且类改变时需要对应修改,违背开闭原则;
结构型
7.外观(Facade)
为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,使得这一子系统更容易使用。简言之,定义一个内聚多个功能方法的外观功能类,让使用者无需关心其内部的实现,便于不同层次部分的分别开发和连接使用。
使用场景:1. 分层结构的系统构建,定义统一的低层入口,简化子系统的依赖;2.客户端与子系统之间较大耦合,使用该模式解耦,提高子系统的独立性;
优点:降低了子系统与客户端的耦合,子系统的变化对客户端是透明的;
缺点:扩展子系统的功能会影响外观功能接口,违背开闭原则;
8.适配器(Adapter)
将一个类的接口转换成客户希望的另一个接口,使得原本因为接口不兼容而不能一起工作的类可以一起工作。类适配器模式:通过多重继承对一个接口与另一个接口进行匹配。对象适配器模式:适配器继承目标接口,并通过内部包装需要适配的对象,将原接口转换成目标接口;
使用场景:存在过往满足需求的类或者需要使用第三方的接口,但接口不一致的场景;例如ArrayList.toList()、Collections.list()等;
优点:对现有的类实现了复用;解耦了期望类和适配类的接口不一致的问题;通常满足开闭原则;
缺点:降低了代码可读性,增加系统复杂度;
9. 桥接(Bridge)
将抽象部分与它的实现部分分离,使它们都可以独立地变化。简言之,当总体可以从多角度进行抽象拆分时,会有多个可能的实现过程,如果将抽象部分与实现部分耦合,难以应对某些部分的拓展和修改带来的变化。因此,为了减小耦合度,不要在实现抽象部分时通过继承套接多层的派生类,致使针对某个或类的修改拓展需要改变父类或抽象部分而影响其他类的实现。对总体进行角度拆分,分离出多个抽象部分,并独立实现和独立变化,再用合成或者聚合来组装总体功能。
使用场景:1.当一个类具多个独立变化的维度且需要扩展时;2.不希望使用继承导致具体类的数量剧增;比如JDBC等;
优点:由于抽象和实现分离,实现细节对用户透明,因此扩展能力强;
缺点:聚合关系放在抽象层,独立地变化维度大,系统的设计难度大;
10.组合(Composite)
将对象组合成树形结构以表示‘部分-整体’的层次结构,该模式使得用户对单个对象和组合对象的使用具有一致性。
使用场景:当需求的功能类中包含部分与整体的结构时,并且希望用户忽略组合对象与单个对象的不同,统一地使用组合中的所有对象时,可以考虑使用该模式;两种模式:透明模式(抽象构件中定义了所有操作,虽然让客户端无法察觉构件的不同,但会使得树叶和树枝都得实现所有方法,尽管对该构件无用;安全模式(抽象构建只定义了必要方法,管理子件的方法定义在树枝部件。由于树枝树叶的功能接口不同,使得客户端需要意识到两者的存在);比如Map.putAll(Collection)、List.addAll(Collection)等;
优点:客户端可以一致地使用组合对象和单个对象,无须关注两者的不同,简化了代码;组合对象内部的变化对客户端是透明的,满足开闭原则;
缺点:设计复杂,很难通过继承来扩展功能;
11.装饰(Decorator)
动态地给一个对象添加一些额外的职责,适用于在已有功能时动态增加功能。抽象装饰器和具体被装饰类继承同一抽象类,利用装饰器导入装饰的对象,为对象进行包装后的装饰器作为新的装饰对象,以实现在已有功能的基础上添加功能。把类的核心职责和装饰功能区分开来,并去除相关类重复的装饰逻辑。
使用场景:需要为原有类增加功能又不想扩展子类时,或者频繁地以不同顺序增加不同功能时,可以采用该方法;例如BufferedInputStream(InputStream);
优点:继承的有力补充,在不改变原有对象的基础上,动态地为其扩展功能;不同装饰类的排列组合具有不同的功能;
缺点:会增加许多子类,增加了程序复杂性;
12.享元(Flyweight)
运用共享技术有效地支持大量细粒度的对象。在享元对象内部且不随着环境而改变的共享部分,可以称为内部状态,而随着环境变化的、不可以共享的部分称为外部状态。享元模式可以避免大量非常相似类的开销。如果需要大量细粒度的类实例且这些实例除了几个参数外基本相同,就可以把那些参数传递到类实例外面,通过方法传递进来,从而通过共享对象大幅度减少单个实例的数目。就是重复使用不变的内部状态,使用者只改变其外部状态,减少实例对象数量的同时保证具有一定的变化性。使用享元模式需要维护一个记录了系统已有的所有享元的类别,可以使用工厂类负责检查、管理已有的共享状态和新的实例生成。
使用场景:例如JDK中包装器类的valueOf缓存池;
优点:相同的对象只需要保存一份,减低了细粒度对象带来的内存开销;
缺点:不共享的外部状态需要暴露,增加了使用的复杂性;外部状态的读取会增加运行时间;
13.代理(Proxy)
为其他对象提供一种代理以控制这个对象的访问。简言之,代理和其代理的类具有相同的功能,两者实现相同的接口,代理通过引入被代理对象,实现接口是调用被代理对象的方法,从而与其他对象进行交互。
使用场景:无法或不想直接让客户端调用某个对象时,主要用于保护和增强目标;例如远程代理(隐藏实际访问地址和对象)、虚拟代理(创建小的代理隐藏对大对象的创建)、安全访问(在代理类增加权限来控制访问);JDK中的CGlib等;
优点:代理类屏蔽了调用者和被调用者的可见性,提高了被调用者的安全访问性;代理类对被代理者透明地扩展功能,
缺点:增加了类的数量和负杂度;多层调用减慢了运行速度;
行为型
14.责任链(Chain of Responsibility)
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。这可以让客户端不知道确切的请求处理对象,系统的更改可以在不影响客户端的情况下动态地组织和分配责任。简言之,如果请求具有不同权限处理者,由低级到高级地串联每层权限处理者,将请求传递给低级处理者,无法解决就传递给后继的高级处理者,直到某个权限处理,这降低了权限管理者之间的耦合,方便了动态调整职责链。
使用场景:
优点:
缺点:
15.策略(Strategy)
定义算法家族,分别封装,可以互相替代而不影响使用者,即封装变化点,减少算法类和使用算法类的耦合度。定义父类策略方法,规定公共功能,通过子类继承实现具体的不同策略,规定不同之处,这还方便了各类的单元测试。然后功能类引用父类策略类作为参数,并使用相应功能。这样在客户端只需要选择合适的子类具体策略进行导入即可。拓展时,只需要增加策略子类,并修改客户端的导入子类。策略模式封装变化点,适用于不同时期使用不同算法的情况。
使用场景:多个对象可以处理一个请求,具体的处理器在运行时自动确定;
优点:对请求而言屏蔽了具体的处理器;动态地扩展处理器很方便;
缺点:处理器的指定可能导致请求处理的延迟,特别是链很长时;
16.模板(Template)
定义一个操作里算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构而可重定义该算法的某些特定步骤。
使用场景:算法的整体流程固定,个别部分易变时,将变化的部分抽象,固定的部分和工作流程固定在父类中,子类重写变化的部分以实现相同的流程而不同的功能;
优点:封装了不变的流程框架,可定制的是某些部分的变化,有利于代码复用。
缺点:父类的功能增加可能导致子类均要重写;
17.命令(Command)
将一个请求封装成一个对象,从而可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。简言之,该模式将请求的接收者(invoker)和请求的执行者(receiver)分割开来,接收者接收请求并处理,请求控制执行者的行为,这方便了请求的记录、判断、撤销和增减等。
使用场景:认为是命令就可以使用该模式,或者在请求接收者和请求执行者之间添加记录请求的执行、撤销等功能;
优点:降低了系统耦合度;因为系统只负责接收请求,因此新的功能可以通过增加命令类,将其方便往系统类里添加扩展新的服务;
缺点:系统内部会存在许多具体而细小的命令类;
18.观察者(Observer)
定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态变化时,会通知所有观察者对象,使它们自动更新自己。缺点在于观察者可能是不同的类,无法继承同一抽象类。委托事件技术:委托是一种引用方法的类型,为委托分配方法可以使委托具有与该方法相同的功能,并且可以同时分配多个分属于不同类的方法。通过委托,将不同观察者类的更新方法绑定在通知者的通知方法中,就可以解决绑定者类和更新方法名不同的问题。
使用场景:一个对象的状态变化会引起多个对象的响应;适用于适用广播机制的场景;
优点:观察目标和观察者之间是抽象耦合的关系,建立了合理的触发机制;
缺点:多个观察类会让主题类通知的效率变慢;
19.访问者(Visitor)
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。该模式适用于数据结构相对稳定的系统,它将数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集可以相对自由地演化。操作中定义使用数据结构类的方法,数据结构类将该操作作为参数导入,并将自身传递给该操作。因此数据结构类进行的工作只是接收某种操作,并将自身传递给它。具体的行为由该操作执行,将这些行为集中到一个访问者中,这样可以方便地进行新功能的增加,而不影响数据结构的类。
使用场景:1.将访问一个聚合类的操作与其内部的细节分离,使得遍历无须暴露自身结构;2. 定义统一的结构来访问不同的聚合类;3.同时为一个聚合类创建多个遍历
优点:1. 解耦结构与遍历,简化聚合类代码;2.创建多种不同的遍历操作方便;3. 增加聚合类及其访问者方便,而无需改变原有的类;
缺点:访问者与聚合类成对存在,一定程度增加了系统复杂度;