设计模式研究,java 23种设计模式
Posted Leo Han
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式研究,java 23种设计模式相关的知识,希望对你有一定的参考价值。
众所周知,我们在实际编写代码时,为了更好的组织代码,都会在代码中采用的一定的设计模式。一般我们在代码的设计中会遵循如下几大原则:
- 开闭原则(OCP open closed Proiciple),一个类应该对扩展开放,对修改关闭
- 单一职责原则(SRP single responsibility prociple)
- 依赖倒置原则(DIP depedence inversion principle),实际上就是要依赖于抽象,高层模块不应该依赖于底层模块,二者应该依赖于抽象;抽象不应该依赖于具体实现,具体实现应该依赖于抽象
- 里氏替换原则(LSP liskov substitution principle)只要父类能够出现的地方,子类都能够出现替换。一般来说,如果子类继承一个父类,不那么子类就拥有了父类可以继承的属性和方法,这个时候用子类替换父类,应该不会引起原来使用父类类型的程序出现错误,但是如果子类修改了父类型的某些属性或者覆盖了某些方法,那么原来父类型可以运行的程序,替换换为子类型之后可能会出现问题。从开闭原则的角度来说,里氏替换原则则是开闭原则实现方式之一,开闭原则要对扩展开放,对修改关闭,扩展的一个实现就是通过继承,里氏替换原则能够保证子类替换父类
- 接口隔离原则(ISP interface segreation principle) 客户端应该只依赖于他所需要的接口,不应该依赖不需要的接口。实际上就是需要接口尽量细化,接口中的方法尽量少,这个与单一职责看上去类似,但是二者角度不同,例如一个接口中10个方法,都是同一职责相关的,给不同模块使用,这符合单一职责原则,但是接口隔离原则是不允许的,因为接口隔离原则希望每个模块访问一个单一的接口,而不是多个模块公用一个接口,调用接口中的不同方法。
- 最少知识原则(LKP least knowledge principle)也称迪米特法则,指的是在设计系统构建代码的时候,应该尽量较少对象之间的交互,对象只和自己相关的部分交互,达到松散类之间的耦合,减少类之间的相互依赖。
面向对象原则
- 多用组合,少用继承
- 针对接口编程,不针对实现编程
- 为交互对象之间的松耦合设计而努力
- 类应该对扩展开放,对修改关闭
- 依赖抽象,不依赖具体类
在设计模式中,一般又分为:创建类模式,结构类模式,行为类模式
创建类模式
主要有:工厂方法模式、抽象工厂、单例模式、建造者模式、原型模式
工厂方法模式(Factory Method)
目标
:定义一个创建对象的接口,让其子类决定实例化哪一个类,工厂方法是一个类的实例化延迟到子类。
其构件如下:
- Product 工厂方法需要创建的对象德吉接口
- ConcreteProduct 实现Product接口的具体对象
- Provider 声明工厂方法,返回一个Product
- ConcreteProvider 具体的工厂方法实现类,覆盖Factory中定义的工厂方法,返回具体的Product实例
类结构关系如下:
工厂方法模式实际上是为了解耦对象的实例,一般我们都是自己手动来通过new关键字来获得一个对象的实例化,但是如果这个实例化的对象有一个共有特征(能够抽象为一个共有父类),并且在可预期的未来,可能还会有扩展,这个时候我们可以通过工厂方法模式,将该类的实例化步骤提取出来内聚到一起,或者说在实例化的时候需要基于不同的条件实例化不同类型,我们可以将这部分提取出来,通过工厂方法模式来提供统一的访问。
优点
- 良好的封装,客户端需要一个对象并不需要知道这个对象具体的创建过程,只需要知道这个产品的相关信息,交由工厂去创建,降低了模块之间的耦合
- 在不改变产品类型的情况下,可以很方便的实现扩展。工厂方法在不改变产品类型时,扩展只需要实现实现Provider 提供的方法即可
缺点
- 如上所示,工厂方法模式的缺点主要在于具体的产品需要和工厂耦合。
抽象工厂模式(Abstract Factory)
目标
:为创建一组相关或相互依赖的对象提供一个接口,而无需指定他们的具体类。这里可以这样简单理解,工厂方法是为了创建某个类型产品,而抽象工厂模式,则是为了创建一个产品族。
拿我们常见的生产汽车来举例:
- 对于工厂方法模式来说,关心的是生产的车,比如生成奔驰车,宝马车
- 对于抽象工厂模式,则关注的是生产汽车中的各个部件,比如:轮胎,发动机,变速箱
一般抽象工厂模式包含构件如下: - AbstractFactory:抽象工厂,定义创建一些产品的接口,如汽车工厂,定义 创建轮胎、发动机、变速箱的创建接口
- ConcreteFactory:具体的工厂,实现抽象工厂定义的方法,实现一系列产品的创建,如创建奔驰汽车工厂,需要实现创建奔驰汽车需要的轮胎、发动机、变速箱;宝马汽车工厂,需要实现创建宝马汽车需要的轮胎、发动机、变速箱
- Product: 定义一类产品对象的接口
- ConcreteProduct:具体产品对象
类结构关系如下:
优点
- 分离了抽象和实现。客户端使用抽象工厂来创建需要的对象,但是客户端不需要知道具体的实现,客户端只需要知道这类产品的抽象,不用关心具体的实现,,实现了抽象和实现的分离
- 切换产品簇变得容易。如果要获取奔驰车相关的产品,直接用奔驰车相关的抽象工厂,宝马这则切换到宝马车即可
缺点
- 不容易扩展新的产品。如果要给产品簇增加一个新的产品,则需要修改抽象工厂,同时所有已经实现的工厂类都需要调整
使用场景
单例模式(Singleton)
目标
:确保一个类仅有一个实例,并提供一个全局访问点
优点
- 优化了资源,由于单例模式全局只存在一个实例,减少了资源和系统的性能开销
缺点
- 单例模式一般没有接口,扩展比较困难
使用场景
建造者模式(Builder)
目标
:将一个复杂对象的构建与他的表示分离,使同样的构建过程可以创建不同的表示
其构件如下:
- Builder:生成器接口,定义一个创建Product对象实例所需的各个部件的操作
- ConcreteBuilder:具体的生成器的实现,实现各个部件的创建,并组装成一个Product对象实例
- Director:主要是使用Builder接口,确定构件过程中需要哪些部件怎么初始化这些部件最终获取到一个Product实例
- Product: 需要构建的复杂对象,包含多个部件
类结构关系如下:
建造者模式实际上是将一个复杂对象的构建过程进行了拆解,让调用者可以随意组合来构建对象。可以理解为建造者模式将构建复杂对象的过程抽象为一个算法,通过建造者模式将复杂对象的实例化解耦出来。
使用场景
原型模式(Prototype)
目标
:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
原型模式实际上就是通过一个对象克隆自身的接口,拷贝这个对象本身来创建一个新的实例,而无需通过new来创建,相当于是复制了一份这个对象。
原型模式的功能点主要在两个:
- 通过调用克隆方法来创建新的实例
- 克隆的实例复制原来实例的属性值
使用场景
结构类模式
结构类模式主要有:桥接模式、适配器模式、代理模式、装饰模式、组合模式、门面模式、享元模式
桥接模式(Bridge)
目标
:将抽象和实现分离,使得二者可以独立的变化
构件如下:
- Abstraction 定义的抽象部分的接口,一般在这个对象中会持有一个实现部分对象的引用,抽象接口的实现部分需要调用实现对象的方法来完成。抽象部分的接口一般是和具体业务相关的方法
- RefinedAbstraction:扩展抽象部分的接口。定义跟业务方法,这些方法一般可能会使用Abstraction中定义的方法,也可能需要调用实现部分的对象来完成
- Implementor:定义实现部分的接口,这里的接口不需要和Abstraction中的方法一致,一般这个对象中提供方法供Abstraction使用,相当于是一个低层次,而Abstraction是一个较高层次的调用
- ConcreteImplmentor:实现Implementor中定义的方法
类结构关系如下:
桥接模式的主要目标就是分离抽象和实现部分,在java中比较明显的例子就是JDBC,java定义了JDBC的抽象接口,与具体数据无关,但是实际实现由各个数据库厂商自己实现,通过DriverManager来进行抽象和实现部分的桥接,将JDBC的接口与数据库的具体实现耦合到一起。桥接模式可以让两个不同维度的实现分离,各自发展,通过桥接将二者组合到一起,即所谓的桥。
优点
- 将抽象和实现分离。这是桥接模式的主要目的,通过桥接模式,实现可以不受抽象的约束,不用和抽象绑定在同一个层次上面
- 良好的扩展能力。由于抽象和接口进行了分离,如果对外接口层允许变化,那么抽象和实现可以独自随意扩展
- 对客户是透明的。客户调用的是抽象部分的接口,不关注具体的实现细节
缺点
使用场景
- 不希望或不适用通过继承来实现抽象。如:不希望抽象和实现部分采用固定的绑定关系(继承),可以通过桥接模式来分离抽象和实现;如果采用继承方式会产生很多子类,这时可以分析变化的原因,分离成不同维度,通过桥接模式分离不同维度,从而减少子类数目
- 复用性较高的场景。设计的粒度越细,越有可能被重用,而采用继承的话会受到继承层次的限制。
适配器模式(Adapter)
目标
:将一个借口转换成客户希望的例外一个接口,使得原本由于接口不兼容的类可以一起工作
主要构件如下:
- Target:定义客户端期望使用的接口
- Adaptee:需要被适配的接口,该接口已经存在并实现
- Adapter:适配器,将Adaptedd转换成Target接口
- Client:客户端,调用自己期望的Target接口
适配器模式有类适配器和对象适配器:
- 类适配器。类适配器通过继承来实现接口适配,Adapter同时继承了Target和Adaptee,但是java里不能多继承,一般只能将Target为interfat类型,这样
Adapter extends Adaptee implements Target
- 对象适配器。对象适配器主要是Adapter持有一个Adaptee的引用,在Adapter直接使用Adaptee的实例引用
类适配器 类结构关系如下:
对象适配器模式 类结构关系如下::
适配器模式的主要目的是复用现有的功能,将现有功能进行转化能够匹配预期的目标接口。
优点
- 提高了类的复用度
- 可以让两个没有任何联系的类一起运行
使用场景
- 当我们存在一个已有的功能,新的需求该功能接口已经实现了,但是其接口不符合需求,这时候可以使用适配器模式将已有的实现转换成需要的接口
- 如果想建立一个可以服用的类,但是这个类需要和其他一些不兼容的类一起工作,这时候可以使用适配器模式
- 如果想使用一些已经存在的子类,但是不可能对每一个子类都进行适配,这时候可以用对象适配器,直接适配父类即可
代理模式(Proxy)
目标
:为其他对象提供一种代理以控制对这个对象的访问
主要构件如下:
- Subject:目标接口,要代理访问的地方
- RealSubject:目标接口实现,具体的目标对象
- Proxy:代理对象,一般代理对象会实现Subject一样的接口,类似模拟Subject的子类,但是其会持有一个RealSubject的具体实现的引用,当调用Proxy接口的时候,会进行一定的控制处理,如果没有问题,最终会调用RealSubject的实现去处理
类结构关系如下:
代理模式是通过创建一个代理对象,用这个代理对象去模拟代表真实的对象,对于客户端而言,由于是面向接口编程,即客户端只知道Subject,对于Proxy的实现并不关心,而代理的实际实现是通过RealSubject去实现的,而代理实际上是中间的一个中转,这样的话,我们在代理上就可以实现一些额外的功能,比如判断权限,记录日志等。
在spring中常见的AOP编程就是通过代理实现的,如果类配置了相关切面,我们在程序中获得的实际上并不是该类,而是其生成的一个代理类
代理模式的本质其实就是控制对象访问
。引入代理之后,在客户端和目标对象之间引入了一个中间层,在这个中间层中加入自己的逻辑
优点
缺点
使用场景
装饰模式(Decorator)
目标
:动态的给一个对象添加额外的职责,单就增加功能来说,装饰模式比生成子类更灵活
其构件主要如下:
- Componet:抽象模块,定义接口
- ConcreteComponet:具体实现,被装饰的类
- Decorator:抽象装饰器,继承Componet,并在内部持有一个具体的Componet引用
- ConcreteCompone:具体的装饰器对象,实现给Componet接口添加功能
类结构关系如下:
优点
- 装饰模式可以动态的扩展一个类的功能
- 可以视为继承的一个替代方案
- 装饰类和被装饰类可以独立发展
缺点
多层的装饰比较繁杂
使用场景
- 需要扩展一个类的功能,或者给一类增加附件功能
- 动态的给一个对象增加功能,这些功能可以动态的撤销
组合模式(Composite)
目标
:将对象组合成竖向结构以表示部分-整体
的层次结构,使用户对单个对象的访问和组合对象的访问具有一致性
其构件如下:
- Component:抽象构件角色,定义外部访问的结构
- Leaf:叶子节点对象,地应以和实现叶子对象的行为,不在包含其他的子节点对象,是最小的单位
- Composite:组合对象(也称树枝节点),组合树枝节点和叶子结点
类结构关系如下:
组合模式是为了让客户端不区分操作的是组合对象还是叶子对象,而是用一个统一的方式来操作,通过抽象一个抽象构件角色来代表组合对象和叶子对象,对客户端而言,访问的就是抽象构件角色
优点
- 对外提供了统一的视图,客户端调用不管是叶子节点还是树枝节点,都统一视为一个Component
- 可以自由增加节点,使用组合模式,增加树枝节点和树叶节点都很方便
缺点
树枝节点和树叶节点是直接实现的,不是面向接口编程的
使用场景
外观模式(Facade)也称门面模式
目标
:为子系统中的一组接口提供一个一致的界面,外观模式提供一个高层次的接口,使得子系统更加容易使用
其构件如下:
- Facade:定义子系统中多个模块对外的高层接口
- subsystem:子系统,可以同时拥有一个或多个子系统,每一个子系统都不是一个单独类,子系统并不知道Facade的存在,Facade实时它的一个客户端
外观模式并不是给子系统添加新的功能接口,而是为了让外部系统减少与子系统内的多个模块交互,松散耦合
优点
- 松散耦合,外观模式松散了外部客户端和子系统内部的多个模块,减少了客户端和子系统各个模块之间的依赖,客户端只和Facade产生依赖
- 简单易用,外观模式是得客户端只需要关心Facade,不需要关心内部各个模块之间的调用关系
缺点
外观模式中Facade不符合开闭原则,对修改关闭对扩展开放
使用场景
- 为一个复杂的子系统或模块对外提供一个统一的访问接口
- 子系统相对独立,外界对系统的访问字需要访问Facade即可
享元模式(Flyweight)
目标
:使用共享技术有效地支持大量的细粒度对象
主要构件如下:
- Flyweight:抽象享元角色,定义享元的接口
- ConcreteFlyweight:具体享元角色,实现享元接口
- unsharedConcreteFlyweight:不可共享的享元角色
- FlyweightFactory:享元工厂,创建并管理共享的享元对象,职责非常简单,构造一个池容器,同时从池中获得对象的的方法
类结构关系如下:
享元模式的主要运用共享技术,是一些对象可以共享,是池化技术的来源,在系统中如果有多个相同的对象,只需要共享一份即可,而不必实例化每一个对象。
优点
缺点
使用场景
行为类模式
行为类模式主要有:责任链模式、观察者模式、策略模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、状态模式、模板方法模式、访问者模式
观察者模式(Observer)
目标
:定义对象之间一种一对多的依赖关系,让一个或多个观察者对象监察一个主题对象,这样当一个主题对象状态发生改变的时候,所有观察者能够得到通知并被自动更新。
主要构件如下:
- Subject:抽象主题角色,主题角色会将所有的观察者对象的引用保存在一个地方,同时可以添加或者移除观察者
- Observer:抽象观察者角色,为所有观察者定义一个更新接口,给主题调用,当主题更新的时候调用观察者的更新接口,让其更新自己
- ConcreteSubject:具体实现主题对象,维护主题状态,当主题状态发生改变时,通知所有观察者对象
- ConcreteObserver:观察者对象的具体实现,接收主题对象的通知,并进行相应的处理
UML类结构关系如下:
优点
- 观察者模式是在主题与观察者之间建立了一个抽象的耦合,并没有耦合到具体实现,因此这两个可以独立发展实现,抽象主题只知道抽象观察者,并不知道具体的观察实现
- 观察者模式能够支持广播通信
缺点
- 如果观察者太多,则广播可能会花费一定的时间
- 如果主题之前有循环依赖,可能造成循环调用
使用场景
策略模式(Stragety)
目标
:定义一组算法,并将其一个个封装起来,且可以相关替换,使得算法部分可以独立变化
其主要构件如下:
- Context:上下文,负责与具体的策略交互
- Strategy:策略接口,用来约束一系列具体算法,Context使用这个接口来调用具体的策略
- ConcreteStrategy:具体的策略实现,即具体算法实现
类结构关系如下:
策略模式实际上是将解决问题的一类算法,抽象提取共有部分,分离算法,选择实现。
优点
- 策略模式实际上是将解决同一个问题的不同算法进行了抽象,只要一个算法实现了抽象的策略算法,那么他就是这一组算法中的一个,这一组算法的每个算法可以自由切换
- 避免了多重条件判断语句
缺点
- 必须了解每种算法的不同
- 增加了对象数目
- 只适合扁平的算法结构
使用场景
命令模式(Command)
目标
:将一个请求封装为一个对象(Command),这样我们可以用不同的请求对用户进行参数化,这样的话我们就能够对请求进行排队或者记录请求的日志以及可以支持撤销操作
其主要构件如下:
- Command:定义命令的接口,声明执行的方法
- ConcreteCommand:具体命令实现者,但注意这是一个虚的实现,通常会持有一个接受者,由接受者来完成实际要执行的操作
- Receiver:接受者,真正执行命令的地方
- Invoker:要求命令对象执行请求,一般会持有命令对象,可以持有很多的命令对象,相当于使用命令对象的入口
命令模式实际上是将请求封装成了一个对象,定义了统一执行操作的接口,将请求和处理进行了解耦,本质就是封装请求。命令模式实际上是从界面开发中演化而来
类结构关系如下:
优点
- 松散耦合。命令模式将用户和具体的执行对象(接受者)进行了解耦
- 更加动态的控制。命令模式将请求封装起来,可以动态的参数化
缺点
使用场景
解释器模式(Interpreter)
目标
:按照规定语法进行解析,给定一个语言,定义它的文法的一种表示,定义一个解释器,该解释器使用这个表示来解释语言中句子。
其主要构件如下:
- AbstractExpression:抽象解释器,定义解释器的接口
- TerminalExpression: 终结符表达式,实现解释器接口,实现语法规则中和终结符相关的操作,不在包含其他的解释器
- NoneterminalExpression:非终结解释器,实现语法规则中非终结相关操作
- Context:上下文
迭代器模式(Iterator)
目标
:提供一个中方法访问一个容器中各个对象元素而又无需暴露该对象内部细节
其主要构件如下:
- Iterator:抽象迭代器
- ConcreteIterator:具体迭代器
- Container:抽象容器角色
- ConcreteContainer:具体容器角色
类结构关系如下:
优点
缺点
使用场景
中介者模式(Mediator)
目标
:用一个中介对象来封装一系列对象的交互,使得个对象不需要显示的相互引用他,从而松散耦合,并且可以独立地改变他们之间的交互
其主要构件如下:
- Mediator:中介者接口,定义各个同事对象之间交互需要的方法
- ConcreteMediator:具体中介者实现对象,维护各个同事对象,并负责协调各个同事对象之间的交互关系
- Colleague:同事类,每一个同事对象都知道中介者角色,并且与其他同事交互的时候一定要通过中介者协作
- ConcreteColleague:具体的同事类,实现自己的业务,在需要与其他同事交互时与持有的中介者通信,由中介者负责与其他同事交互
中介者模式其实就是封装了对象之间的交互,以前可能一个同事类要与多个同事类交互,现在只需要与中介者交互即可,将原本直接相互依赖的对象分解,加入中介者
这个角色,使得两头的对象只与中介者交互
优点
- 松散耦合,将多个同事对象之间的交互封装到中介者中,使得同事对象之间松散耦合,不互相依赖,各自可以独立变化
- 集中控制,同事类之间的交互都在中介者中,便于集中管理
缺点
- 过度集中化
使用场景
备忘录模式(Memento)
目标
:不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态
其主要构件如下:
- Originator:原发器,记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据,
- CareTaker:备忘录管理者,负责保存备忘录对象,不能检察或操作备忘录的内容
- Memento:备忘录,将原发器的内部状态存储起来,但是具体要储存哪些数据由原发器对象来决定,另外备忘录只能由原发器对象来访问它内部的数据,原发器外部对象不应该访问到备忘录对象的内部数据
通俗点来说,备忘录模式就是保存了一个对象的备份,本质是保存和恢复内部状态
优点
缺点
使用场景
- 一个类需要保存它的对象的状态
状态模式(State)
目标
:允许一个对象再起内部状态改变时改变它的行为,看起来这个对象似乎修改了它的类
其主要构件如下:
- Context:上线文环境,定义客户端需要的接口,持有一个具体状态类的实例,并负责具体状态的转换
- State:抽象状态角色,定义一个接口,用来封装与上下文的一个特定状态对应的行为
- ConcreteState:具体实现状态处理的类
类结构关系如下:
状态模式主要是根据状态来分离和选择行为,这里的状态一般指的是对象的一个属性,行为指的就是对象的功能,通过维护状态的变化,来调用不同状态对应的不同功能,状态和行为是相互关联的。
优点
- 分离了状态和行为,避免了为了判断状态而产生的if else判断,将对象的行为交给状态类维护之后,对于上层模块来说,只需要维护状态之间的转换规则
缺点
- 某些场景下会导致有过多具体状态类
使用场景
模板方法模式(Template Method)
目标
:定义一个操作中算法的骨架,而降一些步骤延迟到子类中,模板方法是得子类可以在不改变一个算法的机构即可重新定义该算法的某些特定步骤
其主要构件如下:
- AbstractClass:抽象类,定义算法骨架
- ConcreteClass:具体实现类,用来实现算法骨架中某些步骤,完成与特定子类相关的功能
模板方法在于固定了算法的骨架,而让具体的算法实现可扩展
优点
- 实现了代码的复用,将公用部分提取出来放到父类的模板中实现
缺点
- 算法骨架定义下来后不好升级
使用场景
访问者模式(Visitor)
目标
:在不改变已有程序结构的前提下,通过添加额外的访问者来对现有代码功能实现提升
其主要构件如下:
- Visitor:访问者接口,为所有访问者对象生命一个visit方法,该接口的名字和参数表示了发送访问请求给具体访问者的具体元素角色,这样访问者角色酒而已通过该元素角色的特定接口直接访问它
- Concrete Visitor:具体访问者,实现真正要被添加到对象结构中的功能
- Element:抽象的元素对象,定义接受访问的操作
- ConcreteElement:具体元素对象,对象结构中具体的对象,即被访问的对象,通常会回调访问者的真实功能,同时开放自身的数据供访问者使用
- ObjectStructure:对象结构,包含多个被访问的对象,可以遍历多个被访问的对象,也可以让访问者访问它的元素
类结构关系如下:
优点
- 访问者模式可以给一系列对象透明地添加功能,增加新功能非常方便
缺点
- 增加新的节点类很困难
- 破坏了封装,访问者模式要求访问者能够访问并调用每一个节点对象的操作,暴露了节点的内部状态和操作
使用场景
职责链(Chain of Responsibility)
目标
:避免请求的发送者和接收者之间耦合,使多个对象都有机会处理请求,将这些对象连成一条链,沿着这条链传递该请求,知道有对象处理它为止
其主要构件如下:
- Handler:定义职责的接口,也可以在这里实现后继链
- ConcreteHandler:实现职责接口,可以选择将请求处理掉,也可以将请求传给下家
类结构关系如下:
优点
- 请求者和接受者松散耦合,在职责链模式中请求者并不知道接收者是谁,只是向职责链发出请求即可
- 动态组合职责
缺点
- 产生很多细粒度对象
- 请求不一定能够被处理
使用场景
以上是关于设计模式研究,java 23种设计模式的主要内容,如果未能解决你的问题,请参考以下文章