天天看点

设计模式

目录

概述

创建型

单例(Singleton)

1. 懒汉式(线程不安全)

2. 懒汉式(线程安全) 

3. 饿汉式(线程安全)

4. 静态内部类

5. 双重校验锁

6. 枚举实现

简单工厂(Simple Factory)

工厂方法(Factory Method)

抽象工厂(Abstract Factory)

生成器(Builder)

原型模式(Prototype)

结构型

装饰者模型(Decorator)

代理(Proxy)

设计模式可以做到经验复用

一个类只有一个实例

什么时候用到这个实例对象,什么时候创建,节约资源

缺点:存在并发安全问题,如果多个线程能够同时进入 <code>if (instance == null)</code> ,并且此时 instance 为 null,那么会有多个线程执行 <code>instance = new Singleton();</code> 语句,这将导致实例化多次 instance

对getInstance方法加锁,此时,在一个时间点只能有一个线程能够进入该方法,所以安全

缺点:效率低,当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,即使 instance 已经被实例化了。这会让线程阻塞时间过长,因此该方法有性能问题,不推荐使用

缺点:有可能会浪费资源,不管需不需要直接实例化对象,浪费资源

既能保证并发安全,又能保证效率。内部类只加载一次,剩下的要在调用getInstance方法时才会在加载。

1. 没有并发安全问题,因为外部类对象的创建是在内部类加载时创建,然而类只加载一次,不存在并发安全问题。

2. 可以保证效率,只有我们在调用getInstance方法时,内部类才会加载,可以实现懒加载(lazy-load)

给变量加volatile,然后在实例化为null时,给这个类加锁,加完锁在判断实例对象是否为null,在实例化

volatile关键字的意义:

    1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

    2)禁止进行指令重排序:

<code>   instance = new Singleton();</code>这段代码其实是分为三步执行:

          1.为 instance 分配内存空间

          2.初始化 instance

          3.将 instance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1&gt;3&gt;2。

指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。

使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。

该实现在多次序列化和序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法。

在简单工厂中,创建对象的是另一个类

在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。

简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。

以下的 Client 类包含了实例化的代码,这是一种错误的实现。如果在客户类中存在这种实例化代码,就需要考虑将代码放到简单工厂中。

以下的 SimpleFactory 是简单工厂实现,它被所有需要进行实例化的客户类调用。

工厂方法中,是由子类来创建对象

在父类写一个抽象方法。其他子类都继承父类重写方法,构建实例时,用的是其子类

抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。

而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。

抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。

封装一个对象的构造过程,并允许按步骤构造。

StringBuilder 实现原理

原型实例指定要创建对象的类型,通过复制这个原型来创建新对象

克隆原理

类应该对扩展开放,对修改关闭:也就是添加新功能时不需要修改代码。

不可能把所有的类设计成都满足这一原则,应当把该原则应用于最有可能发生改变的地方。

代理有以下四类:

远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。

虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。

保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。

智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。

以下是一个虚拟代理的实现,模拟了图片延迟加载的情况下使用与图片大小相等的临时内容去替换原始图片,直到图片加载完成才将图片显示出来。

面试:

a. 可以降低代码重复,如果创建对象的过程很复杂或者创建过程中需要大量的代码去是实现,那么此时就可以把这个创建对象的过程整合到工厂模式中,既方便代码的重复使用也有利于以后代码的修改和维护(虽然构造方法也能做到这样的事,但是不符合java的设计规则),

b. 另外工厂模式管理了对象的创建逻辑,也就是说对于工厂模式我只需要知道怎么去用的不需要理会怎么创建出来的,如果还要理会创建的逻辑还可能导致因创建逻辑出错导致创建对象不成功,可以进行解耦,将对象的创建以及使用分开

1.对象的创建过程/实例化准备工作很复杂,需要初始化很多参数、查询数据库等。

2.类本身有好多子类,这些类的创建过程在业务中容易发生改变,或者对类的调用容易发生改变。

继续阅读