java 设计模式类型
根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型模式和行为型模式 3 种。
创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。提供了单例、原型、工厂方法、抽象工厂、建造者 5 种创建型模式。
结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,提供了代理、适配器、桥接、装饰、外观、享元、组合 7 种结构型模式。
行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器 11 种行为型模式。
23 中设计模式介绍
https://www.runoob.com/design-pattern/design-pattern-tutorial.html
1. 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
2. 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
3. 工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
4. 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
5. 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
6. 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
7. 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
8. 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
9. 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
10. 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
11. 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
12. 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
13. 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
14. 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
15. 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
16. 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
17. 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
18. 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
19. 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
20. 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数
据,而不暴露聚合对象的内部表示。
21. 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
22. 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
23. 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
常用设计模式
单例模式
在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式. 例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
单例模式有 3 个特点:
1. 单例类只有一个实例对象;
2. 该单例对象必须由单例类自行创建;
3. 单例类对外提供一个访问该单例的全局访问点;
单例模式的结构
单例类:包含一个实例且能自行创建这个实例的类。
访问类:使用单例的类。其结构如图所示。
单例模式通常两种实现
第 1 种:懒汉式单例
该模式的特点是类加载时没有生成单例对象,只有当第一次调用 getlnstance方法时才去创建这个单例.这种写法会存在线程安全问题
第 2 种:饿汉式单例
该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了,这种写法在加载中创建,不会出现线程安全问题.
懒汉式单例双重检索+volatile
idea 安装 IDEA 字节码查看插件 "jclasslib Bytecode Viewer" 选中编译后的 class 文件
源码
public class D {
int i = 0;
public void test(){
}
public static void main(String[] args) {
D d = new D();
}
}
编译后的汇编指令码
正常执行顺序
0 new #3 //new 申请内存空间
3 dup
4 invokespecial #4 : ()V> //调用构造方法
7 astore_1 //将对象地址赋给引用变量
8 return
线程 1 开始执行,先执行 new,在内存中申请内存空间
此时指令可能发生重排序,先把半成品对象引用地址赋给引用变量 t
线程 1暂停执行,线程 2进入到 cpu执行,引用变量t 不为空,指向的是半成品对象.
Runtime 类
Jdk 中的源码 Runtime 类就是一个单例类,利用 Runtime 类可以启动新的进程或进行相关运行时环境的操作。比如,取得内存空间以及释放垃圾空间。
Runtime 类属于典型的单例设计。
工厂模式(Factory Pattern)
简单工厂模式
简单工厂模式并不是 23 种设计模式之一,因为它并不符合开闭原则。主要目的是为了引出工厂方法模式,适合产品子类比较少的、创建操作比较简单的情况。
由一个工厂类根据传入的参数(一般是字符串参数),动态决定应该创建哪一个产品子类的实例,并以父类形式返回。
该模式中包含的角色及其职责:
工厂角色:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类提供静态方法,可以被外界直接调用,创建所需的产品对象。
抽象产品角色:简单工厂模式所创建的所有对象的父类,描述所有实例共有的接口。可以是抽象类或接口。
具体产品角色:是简单工厂模式的创建目标。
优点
客户端不负责对象的创建,而是由专门的工厂类完成;客户端只负责对象的调用,实现了创建和调用的分离,降低了客户端代码的难度;
缺点
如果增加和减少产品子类,需要修改简单工厂类,违背了开闭原则如果产品子类过多,会导致工厂类非常的庞大,违反了高内聚原则,不利于后期维护.
适用场景
所有的产品子类都有同一个父类(或接口),属于同一个产品系列产品子类比较少的、创建操作比较简单.
工厂方法模式
与简单工厂模式不同,工厂方法模式的对工厂也进行了抽象。有一个抽象的Factory 类(可以是抽象类和接口),这个类将不在负责具体的产品生产,而是只制定一些规范,将实际创建工作推迟到子类去完成。
在简单工厂模式中,有个全能的园丁,控制所有作物的种植、生长和收获。现在农场规模变大了,管理更加专业化了。过去全能的园丁没有了,每一种作物都有专门的园丁管理,形成了规模化和专业化生产。
基本原理
在这个模式中,工厂类和产品类往往可以——对应。即一个抽象工厂对应一个抽象产品,一个具体工厂对应一个具体产品,这个具体的工厂就负责生产对应的产品。
该模式中包含的角色及其职责.
抽象工厂角色:工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
具体工厂角色:这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。
抽象产品角色:工厂方法模式所创建的对象的父类型,也就是产品对象的共同父类或共同拥有的接口。
具体产品角色:这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往——对应。
优点:
客户端不负责对象的创建,而是由专门的工厂类完成;客户端只负责对象的调用,实现了创建和调用的分离,降低了客户端代码的难度;若增加和减少产品子类,不需修改工厂类,只增加产品子类和工厂子类,符合开闭原则即使产品子类过多,不会导致工厂类的庞大,利于后期维护。
缺点:
需要额外的编写代码,增加了工作量
适用场景:
所有的产品子类都有同一个父类(或接口),属于同一个产品系列产品子类比较多的、创建操作比较复杂.
抽象工厂模式
简单工厂模式:一个工厂类负责同一类所有产品对象的创建,如果产品较多,职责大大增加。增加或减少产品时,需要修改工厂类,违背了开闭原则。
工厂方法模式:一个具体的工厂类负责创建一个单独的产品,如果产品增加或减少,一不需要修改工厂类,符合开闭原则。但是即使这些产品是有联系的,也必须由不同的工厂分别创建.
基本原理
抽象工厂模式中,一个具体的工厂负责创建一系列相互关联的产品。会简化客户端的调一用。并且更换产品系列非常方便,更换一个工厂类即可。
该模式中包含的角色及其职责: 抽象工厂、具体工厂、抽象产品、具体产品。
优点: 获取具体系列产品只需要通过具体系列工厂获取,无序关心创建的细节;
原型模式
有时候,我们需要多个实例,但是创建这个实例的过程比较复杂,比如构造函数非常的复杂,执行这个构造函数时会消耗较长的时间,但另外一方面,这个构造函数中的一些信息又没有什么变化(也就是说创建第一个实例时初始化信息是这样的,创建第二个实例时初始化信息还是还是这样的),那么直接使用 new再创建这样一个实例就显得太昂贵了,此时可以使用克隆,也就是复制,就是通过复制现在已经有了的实例来创建新的实例,提高创建速度.
生活案例:同一个简历模板打印了 5 分,要求同一个面试者进行填写,每份简历内容相同。面试者写了第一份后(多么复杂的一个创建对象的过程),还需要再重复的写四份吗?那太费时间了。直接找个复印机复印四份不就可以了。
原型模式就是这个道理.
代理模式
代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的引用。它使得客户不能直接与真正的目标对象通信。代理对象是目标对象的代表,其他需要与这个目标对象打交道的操作都是和这个代理对象在交涉。
生活案例:
买房找中介,中介帮助完成找房源、沟通协调、办手续等操作
买二手车找中介,中介负责找车源、做质量检测、过户等,我只要付钱即可购
买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点
代理模式的主要优点有:
1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
2. 代理对象可以扩展目标对象的功能;
3. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
代理模式结构
1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
其结构图如图所示。
代理实现可以分为静态代理和动态代理。
静态代理
静态代理模式的特点,代理类接受一个 Subject 接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:一个代理类只能代理一个接口,工作量太大;代理类是运行前编码已经完成的;必须先有接口,再有代理;接口一旦发生变量,代理类也要修改
动态代理
在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象在运行时为我们动态的来创建。
动态代理分为 jdk 动态代理和 cglib 动态代理
jdk 代理
动态代理是实现方式,是通过反射来实现的,借助 Java 自带的java.lang.reflect.Proxy,通过固定的规则生成。
其步骤如下:
1. 编写一个委托类的接口,即静态代理的
2. 实现一个真正的委托类,即静态代理的
3. 创建一个动态代理类,实现 InvocationHandler 接口,并重写该 invoke方法
4. 在测试类中,生成动态代理的对象。jdk 动态代理总结:
虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。
但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface 代理的桎梏,因为它的设计注定了这个遗憾。
Cglib 代理
JDK 实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要 CGLib 了。
CGLIB(Code Generator Library)是一个强大的、高性能的代码生成库。
CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
CGLIB 相比于 JDK 动态代理更加强大,JDK 动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么 Java 动态代理就无法使用了。
Cglib 子类代理实现方法:
1.需要引入 cglib 的 jar 文件,但是 Spring 的核心包中已经包括了 Cglib 功能,所以直接引入 spring-core-xxx.jar 即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为 final,否则报错
4.目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
CGLIB 创建的动态代理对象比JDK 创建的动态代理对象的性能更高,但是 CGLIB创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。同时由于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的方法无法进行代理。