一.装饰者模式
装饰者模式能够动态的给对象添加一个功能,同时又不改变原对象的原有结构,相比于继承父类,添加功能,这种方式更具有动态性。
比如:我们现在用的手机,正处于一个在4g向5g转型的过程,那么,假设这个时候我们已经有了4g手机对象,想给她添加一个5g的功能,我们就可以在原来对象结构不改变的情况下,给部分手机添加5g功能
-创建一个 Phone接口
-创建一个Phone的实现类
-创建一个5G功能类
-装饰者类要实现和目标类(也就是要装饰的类)一样的接口,这样才能保证行为的一致性。
-装饰者类要持有一个目标类的引用(即成员属性,而且变量类型最好是接口,面向接口编程嘛,实现松耦合),要不然我去装饰哪个。
-常用于 BufferedInputStream 对 FileInputStream 的包装
二.工厂模式--简单工厂模式,工厂方法模式,抽象工厂模式
工厂模式一般让开发人员更多的去关注对象的实际动作实现,而不去着重的关注对象的产生,一个调用者想创建一个对象,只要知道其名称就可以了。
1.简单工厂模式
我们这里拿泡茶举个例子,假设有一天有个朋友来家里做客,你问他,你想喝什么茶,他说想喝龙井你就给他泡龙井,想喝乌龙你就泡乌龙,至于茶怎么来的,就不需要这个朋友去管,他负责点茶就行
- 茶接口--里面有个沏茶的动作
-龙井茶对象
-乌龙茶对象
- 茶工厂
- 客户端点茶
我们这里可以看到,每当客户端去点茶的时候,只需提供茶名就可以了,然后工厂会为他生产一个茶对象,进行泡茶。
UML 图
但是这里有个问题就是 在上面的简单工厂中,如果要创建的产品类型较多,且各个产品创建的过程不尽相同,则一个工厂类职责会变得越来越多,不符合单一职责原则。
另外简单工厂也不符合开闭原则。要新增一种产品需要修改原来的工厂类。
2.工厂方法模式
如果对象创建的过多,则简单生产模式则不符合单一原则,且需要修改逻辑代码不符合开闭原则,比如说如果新产生一个对象则需要修改工厂中的creatTea代码。所以我们将工厂都抽象出来
这样我们如果再新增一种新茶的话,则只需要新增一个茶工厂和茶对象即可,每个对象对象之间没有耦合关系。但是也有缺点:
1.类的个数容易过多,增加复杂度。
2.增加了系统的抽象性和理解难度。
3.抽象工厂模式
抽象工厂模式则和上面两种有些不同了,上面的工厂每次只生产一种产品,如果这个时候我们加上一种产品,比如最近很活的奶茶,代码就是这个样子了。用一个抽象工厂类来决定生产那些产品,再由底下的工厂分别去实现
抽象工厂接口
乌龙工厂
龙井工厂
三.代理模式
四.单例模式
1.懒汉模式:
在需要的时候才创建对象,缺点是不保证线程安全。
2.饿汉模式:
在刚开始的时候就初始化一个实例,解决了线程安全问题,但是容易产生垃圾。
3.双重锁定模式(延迟加载模式--重点掌握)
1.这个模式首先有两个判断为空,主要是为了让初始化后面的线程,不需要再去重复的获取锁和释放锁
2.采用synchronized关键字,来解决线程安全问题。
3.采用volatile来防止new LazyloadSingleton()重排序
我们先看下 monitor = new Monitor();,在这个操作中,JVM主要干了三件事:
1)、在堆空间里分配一部分空间;
2)、执行 Monitor 的构造方法进行初始化;
3)、把 monitor 对象指向在堆空间里分配好的空间。
把第3步执行完,这个 monitor 对象就已经不为空了。
但是,当我们编译的时候,编译器在生成汇编代码的时候会对流程顺序进行优化。优化的结果不是我们可以控制的,有可能是按照1、2、3的顺序执行,也有可能按照1、3、2的顺序执行。
如果是按照1、3、2的顺序执行,恰巧在执行到3的时候(还没执行2),突然跑来了一个线程,进来 getMonitor() 方法之后判断 monitor 不为空就返回了 monitor 实例。此时 monitor 实例虽不为空,但它还没执行构造方法进行初始化(即没有执行2),所以该线程如果对那些需要初始化的参数进行操作那就悲剧了。但是加了 volatile 关键字的话,就不会出现这个问题。这是由 volatitle 本身的特性决定的。
4.静态内部类单例模式(重点掌握)
单例内部类不会随着外部类被加载而却加载,而是在第一次使用时去加载,所以,在第一次使用时获取对象的实例,同样多线程的时候也只能一个线程去实例化对象(由jvm机制决定的)