文章目录
- 设计模式你知道哪些?
- 工厂模式
- 单例模式★★★
- 适配器模式
- 代理模式
- 定义
- 作用
- 静态代理
- 动态代理★★★
- 观察者模式★★★
- 责任链模式
设计模式你知道哪些?
创建型模式(Creational Pattern):对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。
(5种)工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式
记忆口诀:创工原单建抽(创公园,但见愁)
结构型模式(Structural Pattern):关注于对象的组成以及对象之间的依赖关系,描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。
(7种)适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式
记忆口诀:结享外组适代装桥(姐想外租,世代装桥)
行为型模式(Behavioral Pattern):关注于对象的行为问题,是对在不同的对象之间划分责任和算法的抽象化;不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。
(11种)策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
记忆口诀:行状责中模访解备观策命迭(形状折中模仿,戒备观测鸣笛)
工厂模式
工厂模式是一种非常常用的创建型设计模式,其提供了创建对象的最佳方式。在创建对象时,不会对客户端暴露对象的创建逻辑,而是通过使用共同的接口来创建对象。
工厂模式的优点
- 解耦:将对象的创建和使用进行分离
- 可复用:对于创建过程比较复杂且在很多地方都使用到的对象,通过工厂模式可以提高对象创建的代码的复用性。
- 降低成本:由于复杂对象通过工厂进行统一管理,所以只需要修改工厂内部的对象创建过程即可维护对象,从而达到降低成本的目的。
工厂模式可以分为3类:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
简单工厂模式
简单工厂模式本身是违背开闭原则的,虽可通过反射+配置文件解决,但总体来说不友好。
- 何时使用简单工程模式?
- 需要创建的对象少
- 客户端不需要关注对象的创建过程
- 优点
- 调用者想创建一个对象,只需要知道其名称即可
- 缺点
- 违背开闭原则,每增加一个对象都需要修改工厂类。
- 总结
简单工厂模式代码简单,虽有多处if分支且违背开闭原则,但扩展性和可读性尚可,这样的代码在大多数情况下并无问题。
工厂方法模式
简单工厂模式违背了开闭原则,而工厂方法模式则是简单工厂模式的进一步深化,其不像简单工厂模式通过一个工厂来完成所有对象的创建,而是通过不同的工厂来创建不同的对象,每个对象有对应的工厂创建。
- 何时使用工厂方法模式?
- 一个类不需要知道所需对象的类,只需要知道具体类对应的工厂类。
- 一个类通过其子类来决定创建哪个对象,工厂类只需提供一个创建对象的接口。
- 将创建对象的任务委托给具体工厂,也可以动态指定由哪个工厂的子类创建。
- 简单工厂模式和工厂方法模式对比
当对象的创建过程比较复杂,需要组合其他类对象做各种初始化操作时,推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中使得每个工厂类不过于复杂。而使用简单工厂模式则会将所有的创建逻辑都放到一个工厂类,会导致工厂类过于复杂。 - 优点
- 调用者想创建一个对象,只需要知道其名称即可。
- 扩展性高,如果增加一个类,只需要扩展一个工厂类。
- 对调用者屏蔽对象具体实现,调用者只需要关注接口。
- 缺点
- 当增加一个具体类时,需要增加其对应的工厂类,在一定程度上增加了系统的复杂度。
抽象工厂模式
抽象工厂模式是对工厂方法模式的进一步深化。在工厂方法模式中,工厂仅可创建一种对象;然而,在抽象工厂模式中,工厂不仅可创建一种对象,还可创建一组对象。
- 何时使用抽象工厂模式?
- 需要一组对象完成某种功能或多组对象完成不同的功能。
- 系统稳定,不会额外增加对象
- 优点
- 扩展性高,可通过一组对象实现某个功能
- 缺点
- 一旦增加就需要修改原有代码,不符合开闭原则,所以尽可能用在不需要修改的场景。
单例模式★★★
当一个类的实例可以有且只可以一个的时候就需要用到了。为什么只需要有一个呢?有人说是为了节约内存,但这只是单例模式带来的一个好处。只有一个实例确实减少内存占用,可是我认为这不是使用单例模式的理由。我认为使用单例模式的时机是当实例存在多个会引起程序逻辑错误的时候。比如类似有序的号码生成器这样的东西,怎么可以允许一个应用上存在多个呢?
Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。
一般Singleton模式通常有三种形式:
第一种形式:懒汉式,也是常用的形式。
public class SingletonClass{
private static SingletonClass instance=null;
public static synchronized SingletonClass getInstance(){
if(instance==null){
instance=new SingletonClass();
}
return instance;
}
private SingletonClass(){
}
}
第二种形式:饿汉式
//对第一行static的一些解释
// java允许我们在一个类里面定义静态类。比如内部类(nested class)。
//把nested class封闭起来的类叫外部类。
//在java中,我们不能用static修饰顶级类(top level class)。
//只有内部类可以为static。
public class Singleton{
//在自己内部定义自己的一个实例,只供内部调用
private static final Singleton instance = new Singleton();
private Singleton(){
//do something
}
//这里提供了一个供外部访问本class的静态方法,可以直接访问
public static Singleton getInstance(){
return instance;
}
}
第三种形式: 双重锁的形式。
public class Singleton{
private static volatile Singleton instance=null;
private Singleton(){
//do something
}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
//这个模式将同步内容下方到if内部,提高了执行的效率,不必每次获取对象时都进行同步,只有第一次才同步,创建了以后就没必要了。
//这种模式中双重判断加同步的方式,比第一个例子中的效率大大提升,因为如果单层if判断,在服务器允许的情况下,
//假设有一百个线程,耗费的时间为100*(同步判断时间+if判断时间),而如果双重if判断,100的线程可以同时if判断,理论消耗的时间只有一个if判断的时间。
//所以如果面对高并发的情况,而且采用的是懒汉模式,最好的选择就是双重判断加同步的方式。
适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
**意图:**将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
**主要解决:**主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
**如何解决:**继承或依赖(推荐)。
**关键代码:**适配器继承或依赖已有的对象,实现想要的目标接口。
应用实例:
1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。
2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。
3、在 LINUX 上运行 WINDOWS 程序。
4、JAVA 中的 jdbc。
优点:
1、可以让任何两个没有关联的类一起运行。
2、提高了类的复用。
3、增加了类的透明度。
4、灵活性好。
缺点:
1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
**使用场景:**有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
**注意事项:**适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
代理模式
定义
代理模式:对某一个目标对象提供它的代理对象,并且由代理对象控制对原对象的引用。
例如,我们想访问某个对象A时,不能直接访问,需要由对象A的代理对象A Proxy进行代理。通俗来说,A Proxy我们可以认为是A的助理、中介、对外联络人。
所以,整个类图十分简单,如下所示。
原本客户需要直接调用目标类(委托类),而现在客户需要通过代理类来调用目标类。
作用
在客户类和委托类中增加了代理类这一层,就可以在代理类中增加一些功能,例如起到下面的作用:
- 隔离作用:可以防止对目标对象的直接访问,实现目标对象与外部的隔离,从而提供安全保障等。例如:在代理中增加权限身份验证。
- 扩展功能:代理对象可以在目标对象的基础上增加功能。例如:Java切面操作通过建立代理实现。
- 直接替换:代理对象可以直接替换目标对象的功能,带来全新的实现方式。例如:RPC通过建立代理,直接实现了不存在的接口实现(消费者中只有接口,没有实现类,RPC直接把对实现类的访问转走了)。
静态代理
静态代理就是按照代理模式书写的代码,其特点是代理类和目标类在代码中是确定的,因此是静态的。
通过静态代理,我们在目标方法的前后增加了一些操作。
但是,静态代理显然不够灵活。
- 必须要为每个对象创建一个实现了相同接口的代理对象,并且代理对象中的方法也要设置的和原对象一致。因此任何目标对象的变动,代理对象都要变
- 所有代码写死了,不够灵活,不能在运行时改变。
这时,就需要动态代理。他能在代码运行时动态地改变某个对象的代理,并且能为代理对象动态地增加方法、增加行为。
动态代理★★★
有些时候,我们想要根据运行环境(客户类传来的参数等)动态决定代理类的行为,甚至是动态决定要调用哪个目标类。这就需要动态代理。
动态代理有以下特点:
-
代理对象,不需要实现接口
-
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
-
动态代理也叫做:JDK代理,接口代理
观察者模式★★★
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
**意图:**定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
**主要解决:**一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
**何时使用:**一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
**如何解决:**使用面向对象技术,可以将这种依赖关系弱化。
**关键代码:**在抽象类里有一个 ArrayList 存放观察者们。
应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
注意事项:
1、JAVA 中已经有了对观察者模式的支持类。
2、避免循环引用。
3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
责任链模式
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
**意图:**避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
**主要解决:**职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
**何时使用:**在处理消息的时候以过滤很多道。
**如何解决:**拦截的类都实现统一接口。
**关键代码:**Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
应用实例: 1、红楼梦中的"击鼓传花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,SpringMVC的拦截器,jsp servlet 的 Filter。
优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。
使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。
**注意事项:**在 JAVA WEB 中遇到很多应用。
责任链模式在我们生活中有着诸多的应用。比如,在我们玩打扑克的游戏时,某人出牌给他的下家,下家会看看手中的牌,如果要不起上家的牌,则将出牌请求再转发给他的下家,其下家再进行判断,如此反复。一个循环下来,如果其他人都要不起该牌,则最初的出牌者可以打出新的牌。在这个过程中,扑克牌作为一个请求沿着一条链(环)在传递,每一位纸牌的玩家都可以处理该请求。在设计模式中,我们也有一种专门用于处理这种 请求链式传递问题 的模式,即责任链模式 (Chain of Responsibility Pattern)。
此外,采购的分级审批问题也是责任链模式的一个应用典范。我们知道,采购审批往往是分级进行的。也就是说,其常常根据采购金额的不同由不同层次的主管人员来审批。例如,主任可以审批 5 万元以下(不包括 5 万元)的采购单,副董事长可以审批 5 万元至 10 万元(不包括 10 万元)的采购单,董事长可以审批 10 万元至 50 万元(不包括 50 万元)的采购单,50 万元及以上的采购单就需要开董事会讨论决定。此案例如图所示: