设计模式

单例模式

它属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

根据定义可以看到,单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。

优点:在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例以及避免对资源的多重占用。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

几种实现方式

懒汉式,线程不安全

这种实现最大的问题就是不支持多线程。因为没有加锁synchronized,所以严格意义上它并不算单例模式。

Singleton类刚刚被初始化,instance对象还是空,这时候两个线程同时访问getInstance方法,因为Instance是空,所以两个线程同时通过了条件判断,开始执行new操作,这样一来,显然instance被构建了两次

1
2
3
4
5
6
7
8
9
10
public class Singleton {  
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

懒汉式,线程安全

第一次调用才初始化,避免内存浪费。必须加锁 synchronized 才能保证单例,但加锁会影响效率。

1
2
3
4
5
6
7
8
9
10
public class Singleton {  
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

饿汉式,线程安全

这种方式比较常用,但容易产生垃圾对象。它没有加锁,执行效率会提高。但是类加载时就初始化,浪费内存。

1
2
3
4
5
6
7
public class Singleton {  
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}

典型的双重检查锁定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}

这是一种懒汉的单例模式,使用时才创建对象,而且为了避免初始化操作的指令重排序,给 instance 加上了volatile。它只是在第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。

其中 volatile 的作用是啥?

  • 禁止指令重排序:我们知道 new Singleton() 是一个非原子操作,编译器可能会重排序【构造函数可能在整个对象初始化完成前执行完毕,即赋值操作(只是在内存中开辟一片存储区域后直接返回内存的引用)在初始化对象前完成】。而线程 B 在线程 A 赋值完时判断 instance 就不为 null 了,此时 B 拿到的将是一个没有初始化完成的半成品。
  • 保证可见性:线程 A 在自己的工作线程内创建了实例,但此时还未同步到主存中;此时线程 B 在主存中判断instance 还是 null,那么线程 B 又将在自己的工作线程中创建一个实例,这样就创建了多个实例。

为什么双重检查?

考虑这样一种情况,就是有两个线程同时到达,即同时调用 getInstance() 方法,

此时由于 instance== null ,所以很明显,两个线程都可以通过第一重的 instance== null ,

进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重 instance== null ,

而另外的一个线程则会在 lock 语句的外面等待。

而当第一个线程执行完 new SingleTon()语句后,便会退出锁定区域,此时,第二个线程便可以进入lock 语句块,

此时,如果没有第二重 instance== null 的话,那么第二个线程还是可以调用 new SingleTon()语句,

这样第二个线程也会创建一个 SingleTon 实例,这样也还是违背了单例模式的初衷的。

在没有第一重 instance== null 的情况下,也是可以实现单例模式的?那么为什么需要第一重 instance== null呢?

涉及一个性能问题了,因为对于单例模式的话,newSingleTon()只需要执行一次就 OK 了,

而如果没有第一重 instance== null 的话,每一次有线程进入 getInstance()时,均会执行锁定操作来实现线程同步,

这是非常耗费性能的,而如果我加上第一重 instance== null 的话,

那么就只有在第一次,也就是 instance==null 成立时的情况下执行一次锁定以实现线程同步,

而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock语句块了,这样就可以解决由线程同步带来的性能问题了。

一个单例有两个变量的单例模式

工厂模式

简单工厂

定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

优点:一个调用者想创建一个对象,只要知道其名称就可以了。 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。屏蔽产品的具体实现,调用者只关心产品的接口。

缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。

实例:创建一个可以绘制不同形状的绘图工具,可以绘制圆形,正方形,三角形,每个图形都会有一个draw()方法用于绘图。

首先定义一个接口,作为这三个图像的公共父类,并在其中声明一个公共的draw方法。

1
2
3
public interface Shape {
void draw();
}

下面就是编写具体的图形:圆形,正方形,三角形,每种图形都实现Shape接口

1
2
3
4
5
6
7
8
9
10
// 圆形
public class CircleShape implements Shape {
public CircleShape() {
System.out.println( "CircleShape: created");
}
@Override
public void draw() {
System.out.println( "draw: CircleShape");
}
}
1
2
3
4
5
6
7
8
9
10
// 正方形
public class RectShape implements Shape {
public RectShape() {
System.out.println( "RectShape: created");
}
@Override
public void draw() {
System.out.println( "draw: RectShape");
}
}
1
2
3
4
5
6
7
8
9
10
11
// 三角形
public class TriangleShape implements Shape {
public TriangleShape() {
System.out.println( "TriangleShape: created");
}
@Override
public void draw() {
System.out.println( "draw: TriangleShape");
}

}

再是工厂类的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ShapeFactory {
public static final String TAG = "ShapeFactory";
public static Shape getShape(String type) {
Shape shape = null;
if (type.equalsIgnoreCase("circle")) {
shape = new CircleShape();
} else if (type.equalsIgnoreCase("rect")) {
shape = new RectShape();
} else if (type.equalsIgnoreCase("triangle")) {
shape = new TriangleShape();
}
return shape;
}
}

在这个工厂类中通过传入不同的type可以new不同的形状,返回结果为Shape 类型,这个就是简单工厂核心的地方了。

客户端使用

1
2
3
4
5
6
7
8
9
10
// 画圆形
Shape shape= ShapeFactory.getShape("circle");
shape.draw();
// 画正方形
Shape shape= ShapeFactory.getShape("rect");
shape.draw();
// 画三角形
Shape shape= ShapeFactory.getShape("triangle");
shape.draw();
// 只通过给ShapeFactory传入不同的参数就实现了各种形状的绘制

工厂模式

工厂方法模式是简单工厂的仅一步深化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。也就是说每个对象都有一个与之对应的工厂。

实例:现在需要设计一个这样的图片加载类,它具有多个图片加载器,用来加载jpgpnggif格式的图片,每个加载器都有一个read()方法,用于读取图片。

首先完成图片加载器的设计,编写一个加载器的公共接口。

1
2
3
public interface Reader {
void read();
}

完成各个加载器的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Jpg图片加载器
public class JpgReader implements Reader {
@Override
public void read() {
System.out.print("read jpg");
}
}
// Png图片加载器
public class PngReader implements Reader {
@Override
public void read() {
System.out.print("read png");
}
}
// Gif图片加载器
public class GifReader implements Reader {
@Override
public void read() {
System.out.print("read gif");
}
}

按照定义所说定义一个抽象的工厂接口ReaderFactory

1
2
3
public interface ReaderFactory {
Reader getReader();
}

接下来我们把上面定义好的每个图片加载器都提供一个工厂类,这些工厂类实现了ReaderFactory。在每个工厂类中我们都通过复写的getReader()方法返回各自的图片加载器对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Jpg加载器工厂
public class JpgReaderFactory implements ReaderFactory {
@Override
public Reader getReader() {
return new JpgReader();
}
}
// Png加载器工厂
public class PngReaderFactory implements ReaderFactory {
@Override
public Reader getReader() {
return new PngReader();
}
}
// Gif加载器工厂
public class GifReaderFactory implements ReaderFactory {
@Override
public Reader getReader() {
return new GifReader();
}
}

客户端使用:

1
2
3
4
5
6
7
8
9
10
11
12
// 读取Jpg
ReaderFactory factory=new JpgReaderFactory();
Reader reader=factory.getReader();
reader.read();
// 读取Png
ReaderFactory factory=new PngReaderFactory();
Reader reader=factory.getReader();
reader.read();
// 读取Gif
ReaderFactory factory=new GifReaderFactory();
Reader reader=factory.getReader();
reader.read();

抽象工厂模式和工厂方法模式区别

工厂模式:定义一个用于创建对象的借口,让子类决定实例化哪一个类

抽象工厂模式:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类

代理模式

代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

优点:职责清晰;高扩展性;智能化。

缺点:由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

实例:将创建一个 Image 接口和实现Image接口的实体类。ProxyImage是一个代理类,减少RealImage对象加载的内存占用。

ProxyPatternDemo,我们的演示类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示。

先创建一个接口:

1
2
3
public interface Image {
void display();
}

创建实现接口的实体类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProxyImage implements Image{
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName){
this.fileName = fileName;
}
@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}

当被请求时,使用ProxyImage来获取RealImage 类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
// 图像将从磁盘加载
image.display();
System.out.println("");
// 图像不需要从磁盘加载
image.display();
}
}
// 运行结果
Loading test_10mb.jpg
Displaying test_10mb.jpg

Displaying test_10mb.jpg