23种设计模式说明与类比举例
- 一、外观模式(Facade)
- 二、适配器模式(Adapter)
- 三、策略模式(Strategy)
- 四、桥接模式(Bridge)
- 五、静态工厂模式(Static Factory,又称简单工厂,非23种模式之一)
- 六、工厂方法模式(Factory)
- 七、抽象工厂模式(Abstract Factory)
- 八、装饰模式(Decorator)
- 九、观察者模式(Observer)
- 十、模板模式(Template)
- 十一、单例模式(Singleton)
- 十二、对象池模式(非23种模式之一)
- 十三、建造者模式
- 十四、原型模式
- 十五、组合模式
- 十六、享元模式
- 十七、代理模式(Proxy)
- 十八、访问者模式
- 十九、状态模式
- 二十、备忘录模式
- 二十一、中介者模式
- 二十二、迭代器模式
- 二十三、解释器模式
- 二十四、责任链模式
- 二十五、命令模式
一、外观模式(Facade)
将一个大系统里需要用到的子系统统一封装成一个Facade类,客户对接的时候只需要调用Facade里的方法,而不用和各个子系统对接。
例:你去超市买东西,并不需要每买一样付一次钱,而是统一在收银台付款,这个收银台就相当于外观模式中的Facade类。
优点:
1、符合迪米特原则。
2、简化客户端对接难度。
3、将客户端与子系统解耦。
4、在原系统功能基础上可以添加新的功能。
缺点:
1、子系统有变动的时候,Facade类也要变,不符合开闭原则。用Facade抽象类可以解决。
二、适配器模式(Adapter)
使原有对象能适应新的类结构,不受其接口的限制。多用在维持多态性,保证其他模式所需要的多态。
例:你需要在原有的水管A上接另一个水管B,但它们的接口不匹配,这时你需要一个转接头连接。水管A为适配对象,水管B为被适配对象。水管B连上之后变成了水管A的一部分。
两种模式:
1、对象Adapter模式,即适配对象包含被适配对象。
2、类Adapter模式,创建一个新类,同时继承两个类,公开继承定义其接口的抽象类(即适配对象类),私有继承访问其实现的原有类(即被适配对象)。
三、策略模式(Strategy)
在处理业务逻辑时,将对算法的选择和算法的实现相分离,允许业务主体使用一个通用的抽象类计算,并可以根据上下文选择具体算法类型。
例:你打王者荣耀想计算英雄的最高伤害,但由于每个英雄的技能机制不一样,导致最高伤害的计算规则都不一样,这时候你只需要在主程序中引用一个计算最高伤害的抽象类,让每个英雄单独继承这个抽象类,在各自的子类中按自己的规则计算。主程序只需要通过一个配置类或多态等别的方法选择对象实例对应的是哪个子类。
四、桥接模式(Bridge)
为所有实现定义一个接口,供抽象类的所有派生类使用,将一组实现与另一组使用它们的对象分离。
例:你要描述一个人用一种武器打架的行为,其中人的种类很多,比如老人、男人、女人等,武器的种类也很多,比如刀、剑、锤、斧等。打架是具体实现,根据武器不同,用的招式也不同。如果只用继承,那么你需要写n*m个类,比如男人用剑打架的类,女人用刀打架的类,老人用斧打架的类等等。如果用桥接模式,可将人抽象成一个类,分别用男人、女人、老人等实现,将武器抽象成一个类,分别用刀、剑、锤、斧等实现,这样只需要写n+m个类即可。
注意:
1、在桥接模式中,需要找到变化并封装之,且优先使用对象聚集而不是类继承。
2、桥接模式被描述为“将抽象与其实现解耦,使它们都可以独立地变化”,刚接触的时候可能会有人对这个概念不太理解。所谓“将抽象与其实现解耦”,结合上面的例子是指:抽象出了一个人用一种武器打架的行为,但不要具体实现这个行为的类,比如不要直接去写男人用剑打架的类。而应该将其中的变化抽象出来,比如人和武器,再重新组合成具体的行为。
五、静态工厂模式(Static Factory,又称简单工厂,非23种模式之一)
单一工厂,用户通过传值决定生成的对象。就像只有一家OEM玩具厂,但什么玩具都能做,用户告诉它做什么它就做什么。它违背了开闭原则,所有变动都只能在一个类里修改。
六、工厂方法模式(Factory)
多个工厂做不同的产品,用户通过调用不同的工厂获取不同的对象。就像A玩具厂只做超人,B玩具厂只做变形金刚,用户要哪个就去哪个玩具厂。
七、抽象工厂模式(Abstract Factory)
多个工厂做不同种类的产品,每个工厂都可以做该种类的多个系列,用户通过调用不同工厂的不同方法获取对象。就像A玩具厂只做超人,但可以做蓝色和紫色等不同颜色,B玩具厂只做变形金刚,但可以做绿色和黑色等不同颜色,用户要什么就去哪里买。
工厂模式汇总说明:
该系列模式主要是将“使用哪些对象”的规则与“如何使用这些对象”的逻辑分离开来。在对象有不同分类和不同系列的情况下,业务类通过调用工厂方法获得对象,而不用操心对象的具体生成过程。
八、装饰模式(Decorator)
动态的给一个对象添加职责。创建一个抽象类表示原类和要添加到这个类的新功能。在装饰类中,将对新功能的调用放在对紧随其后对象的调用之前或之后,以获得正确的顺序。每一个装饰类都必须要在业务代码中调用父类的业务方法,如此才能达到业务链的效果,这条业务链总是终于被修饰的类的对象。
例:我想做一个彩色的盒子,做盒子就是我的对象,涂红色、涂黄色、涂绿色等是我修饰它的动作,我想任意执行动作,比如按红黄绿,或者黄红绿的顺序涂色,于是我将涂红色、涂黄色、涂绿色这几个动作分别封装写成继承同一个抽象父类的装饰类(制作盒子这个业务类也同样继承这个抽象父类),以链式new对象的形式,在使用类的时候,以我想要的顺序执行动作。需要注意的是,无论怎么涂,最终它都是个盒子。
该模式最重要的两点:
1、所有装饰类和业务类都继承自同一个抽象父类。
2、每个装饰类的业务代码中必须要调用父类的业务方法。
java中的io流就是典型的装饰模式。
九、观察者模式(Observer)
在对象之间定义一种一对多的依赖关系,这样当一个对象的状态改变时,所有依赖者都将得到通知。被观察对象需自己维护一个注册list,用于添加与删除观察者,还要一个通知作用的方法,遍历list里的观察者,告诉每个观察者执行处理任务。所有观察者继承同一个抽象类,如果遇到不匹配的情况下还需要用适配器模式转换。如果被观察对象的改变有选择性的通知观察者,那么需要被观察对象自己维护通知策略,这里可能需要策略模式。
例:被观察者就好比是个无线电台,观察者是听众,当听众调频到该无线电台的频率时,意味着这名听众向无线电台的list里注册了自己的信息,听众调到其它频道相当于删除了自己的信息,无线电台广播的时候只会播给list里面的(也就是调频到该无线电台频率的)听众听。
JAVA本身也维护了一个Observable类和Observer接口。
十、模板模式(Template)
定义一个操作中算法的骨架,将一些步骤推迟到子类中实现。可以不改变算法的结构而重定义该算法的步骤。
例:我想写一个把动物装进冰箱的过程,总共分为三步:一是打开冰箱门,二是将动物塞进去,三是关上冰箱门。针对不同的动物,第一步和第三部总是相同的,只有第二步不同。所以我的抽象父类会统一实现第一步和第三步的方法,而将第二步写成抽象方法,让各个子类去实现各自的方法,比如塞大象的子类实现塞大象进去的方法,塞狐狸的子类实现塞狐狸进去的方法。如此一来,不论我要塞多少种动物,都只需要不停添加子类即可,符合开闭原则,且减少代码冗余。
十一、单例模式(Singleton)
确保对象只有一个实例,且所有实体都使用该对象相同的实例。
例:你家只需要一把剪刀,不论裁纸、剪商标、剪线头都用这一把剪刀。
五种写法:
1、懒汉式
用的时候才去检查是否实例化,有则返回,无则新建。若要实现线程安全可加Synchroniezd。最简单,但较少用。
2、饿汉式
实例在初始化的时候就建好,写法简单,解决线程安全问题,但浪费内存空间。
3、双检锁
综合懒汉式和饿汉式两种的优缺点整合而成,在sychronized内外都加一层if判断,既保证线程安全,又比直接上锁快,还节省空间。
4、静态内部类
实现简单,只适用于静态域情况,浪费内存空间。
5、枚举
比较少见,支持序列化机制,绝对防止多次实例化。
十二、对象池模式(非23种模式之一)
在创建对象比较昂贵,或者对于特定类型能够创建的对象数目有限制时,管理对象的重用。
例:一个超市有数目固定的多个收银台,这些收银台加在一起形成了一个对象池,所有买单的人都排成一列,代表任务序列,每轮到一个人的时候,他会查看是否有收银台空闲,有的话他就会去选择一个空闲的收银台买单。人少的时候会有空闲的收银台,人多的时候大家需要排队,任务等待的时间就会变长。
线程池就是典型的对象池模式,负责管理对象池的对象必须唯一,使用单例模式创建。
十三、建造者模式
将一个复杂对象的构建与表示分离,使用多个简单的对象一步步构建成一个复杂的对象。
例:描述一条狗,需要依次建立动物类,哺乳动物类,哺乳陆生动物类,哺乳陆生四条腿的动物类。。。。。。最后才到狗类。
优点:将对象的创建过程拆分到最小精度,所有步骤清晰明了。
缺点:产品内部结构太复杂,则会使系统变得极其庞大,且难以复用不同的对象,故而该模式较少使用。
十四、原型模式
如果一个对象的新建过程很繁琐或者开销特别大,我们在重复创建对象的时候可以直接克隆已经创建好的对象。该对象必须继承Cloneable,重写clone()方法。实现深拷贝的话,可以实现Serializable读取二进制流。一般和工厂模式一起使用。
例:做一份试卷很难,但如果有现成做好的试卷,直接抄写一遍答案就很简单。
十五、组合模式
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户可以使用一致的方法操作单个对象和组合对象。组合模式里最关键的是所有的分支和节点往上走都继承自同一个抽象父类或接口。
例:一所学校里有食堂、教室等,这所学校的分校里也有食堂、教室等,组合模式下我可以统一调度主校和分校的所有食堂与教室。
优点:忽略组合对象和单个对象的不同,实现了容器间的解耦,有新部件时容易添加进来。
缺点:客户端需要花时间理清类之间的层次关系。
十六、享元模式
当需要创造大量对象,而这些对象都有一定共性和非共性的时候,就可以考虑享元模式。享元模式中的内部状态是对象共性的部分,外部状态是区分对象的部分。享元模式中需要一个工厂统一创建对象,一个池统一存放对象,可以看作是工厂模式的改进版。和对象池模式的区别在于,对象池模式中所有实例都没有区别,而享元模式的池中所有对象都是不同的。
例:图书馆有许多本书,书是所有对象共性的部分,可看作内部状态,书名是不同的,可看作外部状态。图书馆作为一个对象池,将所有的书都存放了一个单例,每个人来借的时候只需通过书名查询这个池中是否有想要的单例即可。
优点:减少相同及相似实例的重复创建,节省内存空间。
缺点:要提取出对象的共性和非共性,会使系统变得复杂。
十七、代理模式(Proxy)
通过代理对象访问目标对象,可以在目标对象实现的功能上,增加额外的功能补充。符合开闭原则,即在对现有代码不改变的情况下进行功能的扩展。
例:明星出演活动,活动举办方需要跟明星的经纪人对接,经纪人的角色即是代理对象。明星对象只负责演出部分,代理对象则负责行程安排和结束收钱等琐碎的事情。
有三种代理模式:
1、静态代理
代理对象和被代理对象需要实现相同的接口或继承同一个父类
2、动态代理
只有被代理对象需要实现接口,利用反射在运行时生成对象,也叫JDK代理。代理对象需实现InvocationHandler接口
3、cglib代理
目标对象没有实现接口可以用cglib代理。
十八、访问者模式
是一种将数据操作和数据结构分离的设计模式,较少应用。需要被访问对象结构比较稳定,且会有不同的访问者访问对象的不同属性和方法。
例:员工为被访问对象,公司高层比如CEO\CTO等为访问者。员工有不同的类型,访问者也有不同类型,且每类访问者关注的点都不同。比如针对程序员,CEO关注他的KPI,CTO关注他的KPI和写代码的数量;而针对产品经理,CEO会关注他产品的数量和KPI,CTO会关注他的KPI。
优点:
1、各角色职责分离,符合单一职责原则
2、扩展性好,可任意添加新的访问者
3、数据结构(被访问对象)和数据操作(访问者)解耦
4、灵活性好
缺点:
1、具体元素对访问者公布细节,违反了迪米特原则。(访问者直接调用被访问对象的方法)
2、被访问对象变更时修改成本大。(因为访问者会根据不同的访问对象写相应的访问方法,所以会导致所有访问者被修改)
3、违反了依赖倒置原则,为了达到区别对待而依赖了具体类,没有用抽象。(访问者会根据不同的访问对象写相应的访问方法)
十九、状态模式
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。该模式主要解决的是当控制一个对象状态的条件表达式过于复杂的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。让不同的状态类继承同一个抽象父类,在每个状态类中处理下一步的逻辑。如此就可以将状态的判断与控制都放在服务端内部,客户端不需要写代码控制节点跳转。
例:你提交了一个请假申请,需要经过部门领导、HR和总经理的审批,每一个审批人可以看做是一个状态类,你的申请经过每一个状态类后都会得到处理(即改变状态),如果通过则转移到下一个审批人那里。
该模式的主要缺点是类的耦合度比较高,并且需要创建大量的节点类。
二十、备忘录模式
在不破坏封闭的前提下,将对象当前的内部状态保存在对象之外,之后可以再次恢复到此状态。
例:游戏的存档和读档。
优点:
1、给用户提供了一种可以恢复状态的机制,方便回溯历史的某个时刻。
2、实现了信息的封装,用户不需要关心状态的保存细节。
缺点:
1、消耗内存资源。
二十一、中介者模式
把不同的对象放到同一个公共类中进行交互,使对象之间解耦。
例:买房子的人与卖房子的人不直接交易,而是通过第三方中介进行交易。
优点:
1、对象解耦,提高复用性。
2、集中控制逻辑,简化系统维护。
缺点:
1、中介者类的逻辑过于复杂,单点故障可能性太高。
二十二、迭代器模式
让用户通过特定的接口访问容器的数据,不需要了解容器内部的数据结构。将容器的遍历操作和容器本身解耦,用迭代器封装遍历的具体实现,对外提供一个更为简单的接口方法,不仅符合单一职责原则,也简化了容器的使用。
例:如果想知道一个图书馆里所有书的书名,不需要图书馆一本一本的往外拿,而可以用一个扫描仪去扫描所有的书名,列出一个目录更为直观。
二十三、解释器模式
定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。该模式使用场景较特殊,使用频率低。
例:编译器、正则表达式、XML文档解释。
二十四、责任链模式
为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而练成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。代码与数据结构同链表的实现相似。
例:假如你是一个学渣,你有一道题不会做,去请教一群学霸,学霸们挨个研究题目,不会做的就将题目传给下一个人看,会做的就直接给你反馈答案,如果所有人都不会做,那你就得不到答案。
二十五、命令模式
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加和管理。调用者对象依赖抽象命令类,具体命令类依赖实现者对象。
例:用遥控器发射信号给电视机,遥控器是调用者,信号是命令,电视机是实现者。