设计模式概述
2020-06-28 15:44:52
设计模式层次
- 利剑:剑越锋利越牛
- 软剑:已经不够那么锋利了,需要内力支撑
- 重剑:不再追求形式,追求内在
- 木剑:举重够轻,一个问题很难,你可以轻轻松松搞定
- 无剑:无剑胜有剑,信手拈来
设计模式介绍
1、设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern)代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
2、设计模式的本质:提高软件的维护性,通用性和扩展性,并降低软件的复杂度。
3、《设计模式》是经典的书,作者是 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides Design(俗称 “四人组 GOF”)
4、设计模式并不局限于某种语言,java,php,c++ 都有设计模式.
设计模式类型
设计模式分为三种类型,共 23 种
创建型模式
(对象的创建):单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。结构型模式
(伸缩性更好):适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。行为型模式
(方法设计更合理):模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter 模式)、状态模式、策略模式、职责链模式(责任链模式)。
注意:不同的书籍上对分类和名称略有差别
设计模式应用场景
以下为自己理解
》》》创建型模式(用于new对象的)《《《
单例模式:有些对象我们只需要一个,创建多个就浪费了
原型模式:也就是多例,适用于复杂对象,不用去创建,直接克隆
建造者模式:很多类似的对象,创建也很复杂,不用我们自己创建了,让建造者去创建,实现Builder接口即可
工厂模式:工厂用来管理多个对象,想创建对象,使用工厂进行创建
、简单工厂:一个工厂创建多种类型对象
、工厂方法:多个工厂,每个工厂创建不同类型的对象
、抽象工厂:一个超级工厂,负责创建不同的工厂,再由工厂创建不同对象。(相同于简单工厂 + 工厂方法)
》》》结构型模式《《《
》》》行为型模式《《《
单例模式
2020-06-29 08:01:43
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
例如:Hibernate 的 SessionFactory,它充当数据存储源的代理,并负责创建 Session 对象。SessionFactory 并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory 就够,这是就会使用到单例模式。
》》》单例模式的八种实现方式《《《
1)、饿汉式(静态常量)
2)、饿汉式(静态代码块)
3)、懒汉式(线程不安全)
4)、懒汉式(线程安全,同步方法)
5)、懒汉式(线程安全,同步代码块)
6)、双重检查
7)、静态内部类
8)、枚举
》》》注意事项《《《
1)、单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2)、当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
3)、单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)
饿汉(静态常量)
1 | public class Person{ |
1)、优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
2)、缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
3)、这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大多数都是调用 getInstance 方法, 但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果
4)、结论:这种单例模式可用,线程安全,没有懒加载,可能造成内存浪费
饿汉(静态代码块)
1 | public class Person { |
1)、这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
2)、结论:这种单例模式可用,线程安全,没有懒加载,可能造成内存浪费
懒汉(线程不安全)
1 | public class Person { |
1)、起到了 Lazy Loading 的效果,但是只能在单线程下使用。
2)、如果在多线程下,一个线程进入了 if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
3)、结论:在实际开发中,线程不安全,有懒加载,不要使用这种方式.
懒汉(同步方法)
1 | public class Person { |
1)、解决了线程安全问题
2)、效率太低了,每个线程在想获得类的实例时候,执行 getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低
3)、结论:在实际开发中,不推荐使用这种方式,线程安全,但会阻塞,效率太低
懒汉(同步代码块)
1 | public class Person { |
同上,还是不推荐使用
DCL
Double Check Lock(双重检验锁)
1 | public class Person { |
1)、Double-Check 概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if (singleton == null)检查,这样就可以保证线程安全了。
2)、这样,实例化代码只用执行一次,后面再次访问时,判断 if (singleton == null),直接 return 实例化对象,也避免的反复进行方法同步.
3)、线程安全;延迟加载;效率较高
4)、结论:在实际开发中,推荐使用这种单例设计模式
静态内部类
1 | public class Person { |
1)、这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
2)、静态内部类 Inner 在 Person 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 Inner 类,从而完成 Person 的实例化。
3)、类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
4)、优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
5)、结论:推荐使用.
枚举
1 | public enum Person { |
1)、这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
2)、这种方式是 Effective Java 作者 Josh Bloch 提倡的方式
3)、结论:推荐使用
反射演示
1 | import java.lang.reflect.Constructor; |
使用案例
JDK中的java.lang.Runtime中就使用到了饿汉式单例(静态成员变量)
工厂模式
2020-06-29 09:01:20
1)、工厂模式的意义
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。
从而提高项目的扩展和维护性。
2)、三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
3)、设计模式的依赖抽象原则
、创建对象实例时,不要直接 new 类, 而是把这个 new 类的动作放在一个工厂的方法中,并返回。有的书上说, 变量不要直接持有具体类的引用。
、不要让类继承具体类,而是继承抽象类,或实现 interface(接口)
、不要覆盖基类中已经实现的方法。
普通实现
使用的时候就需要进行new创建,如果对象创建过程是复杂的,就会增加我们的代码量。
简单工厂模式
1)、简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种的实例。简单工厂模式是工厂模式家族中最简单实用的模式
2)、简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
3)、在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式.
存在问题:在新增种类的时候,需要修改工厂类的代码。
方式一:会在类的方法中新增代码
方式二:会在类中新增方法
工厂方法模式
如果增加了车辆WuLing2和Tesla2,显然放到简单工厂中就不太合适。
此时,可以抽取出来一个接口,让各种工厂去实现。
工厂方法模式更符合七大设计原则,但实际业务中我们更常用简单工厂模式。
所以说,七大设计原则只是为了解耦而产生的,如果为了解耦而增加了很大的代码量也不太友好。
抽象工厂模式
1)、抽象工厂模式:定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
2)、抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
3)、将工厂抽象成两层:抽象工厂和具体实现类。
根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
我的画图总结
使用案例
日历类Calendar.getInstance();就使用到了简单工厂模式。
原型模式
2020-06-29 13:52:46
原型模式就是多例模式,应用例如:Spring中的@Scope("prototype")
1)、原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
2)、原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
3)、工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
普通实现
浅拷贝&深拷贝
》》》浅拷贝《《《
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,
浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。
实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值 - 浅拷贝是使用默认的 clone()方法来实现,没有实现对引用成员变量的clone
》》》深拷贝《《《
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。
也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝 - 深拷贝实现方式 1:重写 clone 方法来实现深拷贝
- 深拷贝实现方式 2:通过序列号流,把对象序列化实现深拷贝(推荐)
浅拷贝(重写clone方法)
深拷贝(重写clone方法)
深拷贝(对象流)
使用案例
1 | ApplicationContext ac= new ClassPathXmlApplicationContext(); |
建造者模式
2020-06-30 08:09:30
1)、客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
2)、每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
3)、可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰, 也更方便使用程序来控制创建过程
4)、增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
5)、建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
6)、如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.
7)、抽象工厂模式 VS 建造者模式
抽象工厂模式:实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式:是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
指挥者
构建对象的过程被封装成一个方法,直接调用方法就可以创建复杂对象。
链式编程
无指挥者,也可以说是自己当自己的指挥者
我的画图总结
使用案例
1 | StringBuilder sb=new StringBuilder(); |
适配器模式
2020-06-30 10:57:59
1)、适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
2)、适配器模式属于结构型模式
3)、主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
》》》注意事项《《《
1)、三种命名方式,是根据 src 是以怎样的形式给到 Adapter(在 Adapter 里的形式)来命名的。
2)、三种功能简介
、类适配器:以类给到,在 Adapter 里,就是将 src 当做类,泛化
、对象适配器:以对象给到,在 Adapter 里,将 src 作为一个对象,聚合
、接口适配器:以接口给到,在 Adapter 里,将 src 作为一个接口,泛化
3)、Adapter 模式最大的作用还是将原本不兼容的接口融合在一起工作。
4)、实际开发中,实现起来不拘泥于我们讲解的三种经典形式
类适配器
这里src类就是Cable类(网线类),dest就是Net(网络)
1)、Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dest 必须是接口,有一定局限性;
2)、src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
3)、由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。
对象适配器
1)、对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。
根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dest 必须是接口。
2)、使用成本更低,更灵活。
接口适配器
1)、一些书籍称为:适配器模式(Default Adapter Pattern),或缺省适配器模式。
2)、核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
3)、适用于一个接口不想使用其所有的方法的情况。
下面是子类(匿名内部类)重写默认实现类的方法,实现功能的适配。
使用案例
1)、SpringMvc 中的 HandlerAdapter, 就使用了适配器模式
2)、SpringMVC 处理请求的流程回顾
3)、使用 HandlerAdapter 的原因分析:
可以看到处理器的类型不同,有多重实现方式,那么调用方法就是不确定的。
试想一下:如果需要直接调用 Controller 方法,需要调用的时候就得不断是使用 if else 来进行判断是哪一种Controller,然后执行对应方法。如果后面要扩展 Controller, 还得修改原来的代码,这样违背了 OCP 原则。
Controller就是Handler处理器。
自定义SpringMVC适配器
- Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类。
- 适配器代替Controller执行相应的方法。
- 扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展了。
- 这就是设计模式的精妙之处。OCP原则的体现。
桥接模式
2020-07-01 15:57:31
》》》简介《《《
1)、桥接模式(Bridge 模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
2)、是一种结构型设计模式
3)、Bridge 模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
》》》注意细节《《《
1)、实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
2)、对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
3)、桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
4)、桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
5)、桥接模式要求正确识别出系统中两个独立变化的维度(抽象、和实现),因此其使用范围有一定的局限性,即需要有这样的应用场景。
》》》应用场景《《《
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.
1)、JDBC 驱动程序
2)、银行转账系统
、转账分类: 网上转账,柜台转账,AMT 转账
、转账用户类型:普通用户,银卡用户,金卡用户..
3)、消息管理
、消息类型:即时消息,延时消息
、消息分类:手机短信,邮件消息,QQ 消息…
代码实现
使用案例
JDBC的 Driver 接口,如果从桥接模式来看,Driver 就是一个接口,下面可以有 MySQL 的 NonRegisteringDriver,Oracle OracleDriver,这些就可以当做实现接口类
DriverManager里面封装了成员变量Driver的引用。
装饰者模式
2020-07-03 08:08:33暂停一下学习
2020-07-14 09:29:44开始,之前去面试去了。