Design-Pattern 学习笔记
设计模式是什么
百科:是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结
设计模式有什么用
在项目开发中构建一个优秀的系统最困难之处不在于编码(coding),而是在早期做出的设计(design)上的决定。
设计是软件开发生命周期中的关键阶段,好的设计能产生好的产品,而不当的设计则会影响最终产品的质量。
设计模式是拥有多年开发设计经验的人给我们的经验传承,它是作为专家的建议而引入的,它背后的真正威力是其对真实世界的抽象。
如果我们没有足够的经验,我们很难做出好的设计,但是富有经验的开发者和设计者以设计模式的形式将他们的经验传授给我们,拥有了这些经验之后,我们也能做出好的设计来了。
不仅如此,设计模式还提高了我们软件复用的水平,从而提高了生产效率
设计模式的六大原则
- 单一职责原则
定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
遵循单一职责原的优点有:- 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
- 提高类的可读性,提高系统的可维护性;
- 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
- 里氏替换原则
定义:所有引用基类的地方必须能透明地使用其子类的对象。
通俗讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能 - 依赖倒置原则
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置
通俗的讲就是:降低类之间的耦合性,提高系统的稳定性 - 接口隔离原则
定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
简单的说就是:
将臃肿的接口拆分为独立的几个接口,提高内聚,减少对外交互,接口尽量小,但是要有限度。但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。 - 迪米特法则
定义:一个对象应该对其他对象保持最少的了解。
通俗讲就是:尽量降低类与类之间的耦合。 - 开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
简单理解:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
设计模式列表:
23 种设计模式,可分三大类别
创建型模式
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 单例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
结构型模式
- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 过滤器模式(Filter、Criteria Pattern)
- 组合模式(Composite Pattern)
- 装饰器模式(Decorator Pattern)
- 外观模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
行为型模式
- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 备忘录模式(Memento Pattern)
- 观察者模式(Observer Pattern)
- 状态模式(State Pattern)
- 空对象模式(Null Object Pattern)
- 策略模式(Strategy Pattern)
- 模板模式(Template Pattern)
- 访问者模式(Visitor Pattern)
一、工厂模式(Factory Pattern)
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
优点:
1、一个调用者想创建一个对象,只要知道其名称就可以了。
2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,
在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
代码实现:
public interface Fruits { |
二、抽象工厂模式
抽象工厂模式 是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式.
抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。他与工厂方法模式的区别就在于,工厂方法模式针对的是一个产品等级结构; 而抽象工厂模式则是针对的多个产品等级结构。在编程中,通常一个产品结构,表现为一个接口或者抽象类,也就是说,工厂方法模式提供的所有产品都是衍生自同一个接口或抽象类,而抽象工厂模式所提供的产品则是衍生自不同的接口或抽象类
三、单例模式
单例应该是最简单的设计模式之一了。
优点:
在内存中只有一个对象,节省内存空间。避免频繁的创建销毁对象,可以提高性能。避免对共享资源的多重占用。可以全局访问。
缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
单例模式根据实例化对象时机的不同分为两种:
一种是饿汉式单例,一种是懒汉式单例。
饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用;而懒汉式在调用取得实例方法的时候才会实例化对象
/** |
四、建造者模式
使用多个简单的对象一步一步构建成一个复杂的对象。
比如吃饭,可以单点一份饭,也可以点套餐(饭+汤+小吃),多种饭,可以组成多种套餐,部分代码如下
|
五、原型模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。当直接创建对象的代价比较大时,则采用这种模式.
代码思路:实现Cloneable 接口,重写 clone() 方法,demo 如下:
public abstract class Shape implements Cloneable{ |
六、适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
|
七、桥接模式
将抽象部分与实现部分分离,使它们都可以独立的变化。
有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。所以可以多角度分类分离出来,让它们独立变化,减少它们之间耦合
|
八、过滤器模式
顾名思义就是筛选出符合条件的数据。
下面的例子,就是从一推人中,过滤获取到 单身的人 和 性别是女性的人,然后再 过滤 单身并且是女性的集合
|
九、组合模式
组合模式,就是在一个对象中包含其他对象,这些被包含的对象可能是终点对象(不再包含别的对象),也有可能是非终点对象(其内部还包含其他对象,或叫组对象),我们将对象称为节点,即一个根节点包含许多子节点,这些子节点有的不再包含子节点,而有的仍然包含子节点,以此类推。
就跟电脑的磁盘很像,磁盘下有很多文件夹或文件,然后文件夹下又有文件或文件夹…..
又如公司,老板之下有主管经理,之下还有组长员工…
|
十、装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能,动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
十一、外观模式
隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口
// 接口 |
十二、享元模式
享元模式,换句话说就是共享对象,在某些对象需要重复创建,且最终只需要得到单一结果的情况下使用。因为此种模式是利用先前创建的已有对象,通过某种规则去判断当前所需对象是否可以利用原有对象做相应修改后得到想要的效果,如以下教程的实例,创建了20个不同效果的圆,但相同颜色的圆只需要创建一次便可,相同颜色的只需要引用原有对象,改变其坐标值便可。此种模式下,同一颜色的圆虽然位置不同,但其地址都是同一个,所以说此模式适用于结果注重单一结果的情况。
public interface Shape { |
打印结果:
Creating circle of color : White |
优点:大大减少对象的创建,降低系统的内存,使效率提高。
缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
十三、代理模式
为其他对象提供一种代理以控制对这个对象的访问,我们创建具有现有对象的对象,以便向外界提供功能接口
比如要找 仙剑大佬 - 胡歌 表演、拍戏,直接通过经纪人谈即可。
抽象一个 明星 接口
/** |
上面就是简单的静态代理。
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展.
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
要解决上述缺点,可以使用动态代理
动态代理有以下特点:
- 代理对象,不需要实现接口
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
- 动态代理也叫做:JDK代理,接口代理
只需改变代理类即可
|
代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理
十四、责任链模式
通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
就比如项目中,有些请求我们会加一些过滤器,如果编码过滤器、敏感词过滤器、加解密过滤器等等
优点:
- 1、降低耦合度。它将请求的发送者和接收者解耦。
- 2、简化了对象。使得对象不需要知道链的结构。
- 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
- 4、增加新的请求处理类很方便。
缺点:
- 1、不能保证请求一定被接收。
- 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
- 3、可能不容易观察运行时的特征,有碍于除错。
代码示例
public interface Filter { |
十五、命令模式
以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令
就比如睡觉的时候,你女朋友叫你关灯
,这就是一条命令
。那么你就会去调用灯
的开关按钮
去执行命令
内容
代码示例:
/** |
优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。
缺点:使用命令模式可能会导致某些系统有过多的具体命令类。
十六、解释器模式
1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
2、一些重复出现的问题可以用一种简单的语言来进行表达。
3、一个简单语法需要解释的场景
/** |
十七、迭代器模式
提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示
在Java 中,遍历集合的时候,很常用:Collection.iterator()
即可得到一个 迭代器。
看简单的实现
/** |
十八、中介者模式
中介者模式是用来降低多个对象和类之间的通信复杂性,处理不同类之间的通信,并支持松耦合,使代码易于维护
在现实生活中,有很多中介者模式的身影,例如QQ游戏平台、微信,聊天室、QQ群、短信平台和房产中介。
就说微信吧,几乎天天都要用吧,在我们和朋友聊天的时候,它就充当一个中介的角色。通过它我们就可以和朋友远程联系。
讲着讲着,感觉和代理模式、外观模式 有点像。 区别如下
- 中介者模式:
A,B之间的对话通过C来传达。A,B可以互相不认识(减少了A和B对象间的耦合) - 代理模式:
A要送B礼物,A,B互相不认识,那么A可以找C来帮它实现送礼物的愿望(封装了A对象) - 外观模式:
A和B都要实现送花,送巧克力的方法,那么我可以通过一个抽象类C实现送花送巧克力的方法(A和B都继承C)。(封装了A,B子类)
代理模式和外观者模式这两种模式主要不同就是代理模式针对的是单个对象,而外观模式针对的是所有子类
看看中介模式的代码示例:
|
十九、备忘录模式
保存一个对象的某个状态,以便在适当的时候恢复对象
玩过单机版游戏的大佬应该深有体会,那就是游戏存档。比如游戏到达某一关卡时会自动存档,或者当你退出游戏的时候手动存档
来看看代码示例:
/** |
优点:
1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
2、实现了信息的封装,使得用户不需要关心状态的保存细节。
缺点:
消耗 资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
二十、观察者模式
在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。
其实就是发布订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。
比如,帅大叔
微信订阅号,不定时发布一些消息,关注这个订阅号就可以收到推送消息,取消关注就收不到推送消息。
代码示例:
/** |
张三 收到了第一条信息,因为恐慌过度取消了关注,当第二条信息发布的时候,张三并没有收到
优点:
1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点:
1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
二十一、状态模式
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
比如人,人有很多种情绪,喜怒哀乐。现实生活中,可能会遇到一些不如意的事,所以我们的情绪就会变化无常。
代码如下:
/** |
通过Context 进行 状态的切换
二十二、空对象模式
一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为
为了解决什么问题:
1、避免程序抛出NullPointerException异常
2、去掉null 检查
代码示例:
/** |
上面的代码,虽然工厂中没有找到赵六
,但是程序并不会报错
二十三、策略模式
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
和状态模式有点像呢
代码示例:
/** |
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
二十四、模板模式
一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
public abstract class Game { |
优点:
1、封装不变部分,扩展可变部分。
2、提取公共代码,便于维护。
3、行为由父类控制,子类实现。
缺点:
每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
二十五、访问者模式
我们使用了一个访问者类,它改变了元素类的执行算法,通过这种方式,元素的执行算法可以随着访问者改变而改变。
这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。
以,电脑、鼠标、键盘、显示器 举例,代码如下:
/** |
优点:
1、符合单一职责原则。
2、优秀的扩展性。
3、灵活性。
缺点:
1、具体元素对访问者公布细节,违反了迪米特原则。
2、具体元素变更比较困难。
3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。