设计模式笔记
创建型模式
单例模式 Singleton
概念
保证一个类仅有一个实例,并提供一个访问它的全局访问点
适用性
在内存中,如果需要某个类的对象,在程序上保证,有且只有一个该类的对象
例子
- 饿汉式
Class.forName("");
类加载到内存后,就实例化一个单例,该实例和构造方法都是私有的,无法通过new的方式创建对象。缺点:不管用到与否,类装载时就完成实例化。 - 懒汉式
首次使用该类时,创建该实例。每次调用getInstance()时,会检查INSTANCE对象是否已经创建,没有则会去创建,有则直接返回。
会引发线程安全问题:判断为空之后,进行实例创建,在这两条语句之间其他线程可能也会去做检测和创建,可能会创建多次。并不能保证获得的实例时同一个。 - 解决懒汉式线程安全问题
- 上锁
给静态获得实例方法加上synchronized关键字,锁住的是这个类,效率下降。 - 锁代码块
不给静态方法加,而是在判空之后的创建实例的语句块前后加。但是创建语句这两行之间,也存在线程竞争的问题。 - 双判空
代码块加锁之后,再进行一次实例的判空,以防加锁前已经有线程对该变量进行操作。
要用volatile修饰实例变量,volatile保证可见性,防止指令重排序。如果不用的话,该对象在加锁内部判空之后,进行创建时,由于new关键字的创建过程(分配内存,创建实例[默认值,并未初始化],进行初始化),可能第二步就被其他线程访问(判空时此时不为空)并返回了,此时未完成初始化。 - 静态内部类
静态内部类中,创建该类的final static型的实例。
加载时,不会加载内部类,可以实现懒加载。 - 枚举单例
可以解决线程同步,也可以防止反序列化。
抽象工厂模式 Abstract Factory
概念
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
用法
任意定制产品一族 (一系列)
一个抽象工厂,含有不同族的具体工厂,可以创建不同的系列。而具体系列会随着族的改变而改变
例子
我有一个抽象工厂,有一套方法,但是没有实现,我也不知道我会创建出什么内容。我有若干子类工厂,和我有相同的这套方法,不同的子类工厂,会有不同系列的该套方法的实现方式。
抽象工厂.png
工厂方法 Factory Method
概念
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
用法
任意定制生产过程创建某某具体实例的工厂类,含有该类的实例的创建方法。如,XXXFactory.craete(),通过工厂的不同创建方法去完成不同实例的创建。
例子
我有一个工厂接口,含有创建食物的方法,但是我不实现。我有3种工厂子类/实现类,它们也有创建食物方法,是具体的特定的食物。那么我在获得该工厂时可以指定获得哪个子工厂,交由子工厂去完成创建这一过程。
建造者模式
概念
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
作用
分离复杂对象的构建和表示;同样的构建过程可以创建不同的表示。
建造者模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时适用的模式
例子
Person类含有一个personBuilder的构建类,内涵一Person成员变量;
含有不同的赋予Persion属性的方法,返回的是PersonBuilder这个类。
最后用build()方法将该Person类返回出去。
原型模式
概念
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
例子
Java的客隆clone()
原型模式需要实现标记型接口Cloneable,一般会重写clone()方法,如果只是重写clone()方法而未实现接口,调用会报异常。一般用于一个对象的属性已经确定,需要产生很多相同对象的时候。
深克隆与浅克隆
原类中的属性,也要进行克隆,开辟一块新的内存,而不是指向之前对象的内存。这个时候克隆前后的两个成员属性就不会相等了。
但是String 类型,不会被深克隆,String类型的成员属性克隆后依然是原来那个String。如果用的是StringBuilder类型,克隆时不对该类型做克隆方法的重写,那么依然指向的是原来那个StringBuilder,类似于原来那个成员属性。
如果克隆对象含有复杂成员对象,那么也要重写该成员对象对应类的clone方法,让它克隆时去自己开辟内存而非指向原油对象的那块内存地址。
客隆模式.png
结构型模式
适配器模式 Adapter
概念
将一个类的接口转换为客户希望的另一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适用场合
- 应用于希望复用一些现存的类,但是接口与复用环境要求不一致的情况
例子
Java.io包中,FileInputStream,获得文件的输入流,是按字节读取的;InputStreamReader,将文件字节输入流转化为字符输入流;BufferedRead,将字符输入流转化为可缓冲的,按行读取的流。
微软的ODBC,可以使用桥接方法转化为JDBC供Java访问SQL Server。
适配器模式.png
桥接模式 Bridge
概念
将抽象部分与它的实现部分分离,使它们都可以独立地变化
理解
双维度扩展;
分离抽象与具体,用聚合的方式连接抽象和具体。
实现,指的是抽象类和它的派生类用来实现自己的对象。
实现系统可能有多角度分类,每一种分类都可能变化,那么把这种多角度分离出来,让它们独立变化,减少它们之间的耦合。
例子
比如把人进行分类,按性别分类可以分为男女;按照职业分类可以分为运动员与艺术家;如果在男人类下面再分为难艺术家和女艺术家,那么当职业分类有新扩展时,要重写男人类,也要重写女人类。但是,人这个父类,可以拥有一个叫做职业的子类,扩展的时候只要想职业子类增加其他具体职业;从另一个性别类的维度看,即当前性别的人可以拥有/持有职业这么一重身份,也就是成员属性,实现【用聚合代替继承】,只需要传递进来即可。再职业类进行扩展而不会影响性别类的内部结构。
组合模式 Composite
组合模式可以让客户一致地使用组合结构体和单个对象。
概念
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性
例子
打印的时候如果是分支节点则递归地调用分支节点中的tree()方法,完成文件结构的展示
使用场合
需求中使体现部分与整体层次时的结构时,以及希望用户可以忽略组合对象与单个对象的不同,同一地使用组合结构中的所有对象时。
组合模式.png
装饰器模式 Decorator
用聚合替代继承
向装饰者类传递一个基础类,可以丰富或者说增强基础类的一些同名方法。
概念
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式比生成子类更加灵活。
特点
- 无限继承会产生类爆炸的情况,采用装饰模式,修饰部分的类可以自由发展。
- 新的装饰类,含有原有的类,而不是继承关系。聚合非继承。可以在已经有的类的基础上,新增业务功能。
例子
在Java的IO流中,Reader类 聚合了一个 InputStream。
装饰者模式.png
分离每个装饰对象的实现和使用,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中。
门面模式 Facade
概念
为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
用途
- 隐藏内部细节,实现模型与视图分离
使用场合
- 设计初期,将架构分层,如MVC的层与层之间建立外观Facade。
- 开发节点,子系统因重构而变复杂,增加外观Facade可以提供一个简单的接口,减少依赖
- 维护遗留的大型系统,开发Facade类,为设计粗糙或高度复杂的遗留代码提供比较清晰的借口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。
享元模式 Flyweight
概念
运用共享技术有效地支持大量细粒度的对象
理解
共享元数据,把数据放到一个大池子里下次不需要再创建只需要去池子里拿
例子
String类型 的intern()方法,获得该字符串对象在常量池中的引用,如果字面量相等,那么从池中获得的是同一个引用
代理模式 Proxy
概念
为其他对象提供一种代理,以控制对这个对象的访问
例子
- 静态代理
知道代理的对象和需要增强的方法;
代理类实现Movable接口,完成对坦克的记录时间和记录日志的功能。聚合了他Moable对象,增强了move()方法。 - 动态代理
- jdk动态代理必须面向接口,调用Proxy类的newInstance方法创建代理对象,传递被代理对象的类加载器,接口数组和调用处理器(传递被代理对戏,重写其invoke方法,动态实现方法增强)
- cglib动态代理则面向类(待补充)
行为模式
命令模式 Command
概述
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
适用性
- 抽象出待执行的动作以参数化某对象(ConcreteCommand)
- 在不同的时刻指定、排列和执行请求
- 撤销
- 支持修改日志,系统崩溃时,丢改可以重新执行
责任链模式 Chain of Responsibility
概述
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。这一模式的想法是,给多个对象处理一个请求的机会,从而解耦发送者和接受者。
适用性
- 多个对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
- 可处理一个请求的对象集合应被动态地指定
类图
责任链模式.jpg链对象持有一个集合,用于存放不同的接收器,可以向链对象添加两个具体的过滤器接收器,完成不同的过滤功能。当需求来时,链会遍历旗下所有的接收器,执行处理功能,而接收器也可以是一个链对象。类似广义数组。
解析器模式 Interpretor
概述
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子
适用性
当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时,该模式效果最好
- 该文法简单,对于复杂的文法,文法的层次变得庞大而无法管理
- 效率不是一个关键问题,最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。
迭代器模式 Iterator
概述
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示
适用性
- 对聚合有多种遍历方式时,可以考虑迭代器模式
- 为遍历不同的聚集结构,提供如开始,下一个,是否结束,当前那一项等统一接口
- 分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可以让外部代码透明地访问集合内部的数据
类图
迭代器模式.png集合接口持有自己的迭代器,具体的集合持有自己的具体的迭代器,在具体迭代器中含有下一个,访问当前元素等方法的具体实现(与容器结构有关)。
中介者模式 Mediator
概念
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
适用性
- 一组对象以定义良好但是复杂的方式进行通信,产生的相互依赖关系结构混乱,难以理解。
- 一个对象引用其他很多对象,并且直接与这些对象通信,难以复用该对象。
-
定制一个分布在多个类中的行为,而又不想生成太多子类。
中介者模式.png
为了遵循迪米特法则,尽量降低类之间的耦合程度,避免直接交互;如果两个类不必彼此直接通信,那么这两个类不应当发生直接的相互作用。如果其中一个类要调用另一个类的方法,可以通过第三个类转发这个调用。
demo理解:中介者类持有所有打交道的类的实例,并且有其他下属类的共同方法。下属类调用自己的方法时,实际上调用的是中介者类的同名方法,而中介者类在执行该方法时会根据传递进来的类进行判断,并执行对应的操作。
优点:Mediator减少了各个Colleague的耦合,使得可以独立地改变和复用各个Colleague类和Mediator;由于把对象如何协作做了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到它们之间的交互上来,也就是站在一个更宏观的角度去看待系统(也就是具体实现细节上交给中介者去做,自己的交互/沟通对象也由中介者去指派和维护)。
缺点:由于ConcreteMediator控制了集中化,于是就把交互复杂性变味了中介者的复杂性(印证了上条结论),这就使得中介者会比任何一个ConcreteColleague都复杂。
备忘录模式 Momento
概述
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
适用性
- 必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时,它才能恢复到之前的状态
-
如果一个用接口来让其他对象直接得到这些状态,将会暴露对象的实现细节,并破坏对象的封装性
备忘录模式.png
快照存储那一时刻原对象的所有内容,保存在快照文件中,可以在后续时间内将快照文件的内容恢复给原对象。
观察者模式
概念
定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
组成
事件源对象:含有一揽子观察者(list),含有自己的触发若干观察者的方法,触发之后遍历观察者,传递事件,并调用它们的方法。
观察者对象:观察者接口,含有一些事件的处理方法(将事件传递进来);观察者接口的具体实现类,能够依据传来的事件,完成具体的事件处理方法。
事件:是一个抽象类,又一个获得事件源的钩子方法;对应有一个事件的具体实现列,依据与源对象有关的信息创建具体事件。
状态模式 State
概念
当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
适用性
- 一个对象的行为,取决于它的状态,并且它必须在运行时刻根据状态改变它的行为
-
一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量来表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
状态模式.png
主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
方法会依据状态不同而执行不同的操作,可以将所有操作移交给状态类再去执行,状态类要有客户类中的所有方法。
TCP会根据连接的不同状态,比如Established,Listen,Closed,而对操作Open(),Close(),Acknowledge()有不同的具体执行。
策略模式 Strategy
概念
定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户
适用情况
- 许多相关的类仅仅是行为有异。“策略”提供了一种多个行为中的一个行为来配置一个类的方法。
- 需要使用过一个算法的不同变体。
- 算法使用户不应知道的数据,可用策略模式以避免暴露复杂的、与算法相关的数据结构。
-
使用不同策略实现不同的条件分支执行语句。
策略模式.png
本质:完成相同工作的实现手段不同,可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。Strategy类层次为Context定义了一系列的可供重用的算法或行为,继承有助于吸取出这些算法中的公共功能。
模板方法 Template
概念
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构,即可重新定义该算法的某些特定步骤
适用性
- 通过把不变行为搬迁到父类,去除子类中的重复代码。
-
提供了一个很好的代码复用平台,摆脱不变行为的纠缠。
模板方法.png
访问者模式 Visitor
概念
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各类的前提下定义作用于这些元素的新操作
适用性
- 增加新的操作(来访者)很容易,访问者模式能够将有关的行为集中到一个访问者对象中。
-
应该是固定的不同的数据结构,去调用(accept方法接收)变化多端的不同的来访者,不同的来访者对于不同的调用者有不同的执行方法
访问者模式.png
六大原则
单一职责原则 Single Responsibility Principle
- 高内聚,低耦合
- 一个类不要太大,别太累,负责单一的职责
开闭原则 Open-Closed Principle
- 对扩展开放,对修改关闭;尽量不修改原来代码的情况下进行扩展
- 抽象化,多态是开闭原则的关键
里式替换原则 Liscov Substitution Principle
- 所有使用父类的地方,必须能够透明的使用子类对象
依赖倒置原则 Dependency Inversion Principle
- 依赖抽象,而不是依赖具体;
接口隔离原则 Interface Segregation Principle
- 每一个接口应该承担独立的角色,不干自己不该干的事
迪米特法则 Law of Demeter
- 尽量不要和陌生人说话
- 非陌生人比如
- 当前对象本身 this
- 以参数形式传入当前对象方法中的对象
- 当前对象的成员对象
- 如果当前对象的成员对象是一个集合,那么集合中的元素也是朋友
- 当前对象所创建的对象