本文介绍一下设计模式,(暂时介绍常见的设计模式)。
1. 概述
什么是设计模式呢?为什么要采用设计模式呢?
在软件工程中,设计模式是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案,就像类是由万物抽象出来类似,设计模式也是从各种经典的解决方案中抽象出来的,就是某类问题的通用解决方案,这些方案是经过相当长时间的试验和错误总结出来的。比如软件经常新增附加的功能,比如某个系统只能创建一个主体对象等。
框架中经常采用各种各样的设计模式。一共有23种设计模式。23种设计模式可分为三种类型:
- 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
- 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
- 行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。
2. 七大原则
设计模式中常用的七大原则有:
- 单一职责原则
- 接口隔离原则
- 依赖倒转原则
- 里氏替换原则
- 开闭原则
- 迪米特原则
- 合成复用原则
3. 单例模式
单例模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提供系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new。
- 单例模式使用场景:需要频繁使用的进行创建和销毁的对象、创建对象消耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
单例模式在开发中有八种方式:
饿汉式(静态常量)
1
2
3
4
5
6
7
8
9
10
11
12
13class Mouse {
// 私有化构造方法
private Mouse(){}
// 定义常量,创建实例
public static final Mouse instance = new Mouse();
// 提供静态方法,返回实例。
public static Mouse getInstance(){
return instance;
}
}优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading(懒加载)的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
这种方式基于类加载机制,避免了多线程的同步问题,不过,instance在类装载时就实例化。因为导致类装载的方式有很多,在程序中显式调用只是其中一种,有时候不能确定是否有其他方式导致类装载,从而导致无故被装载,导致实例化对象,造成内存浪费。
因为类加载就创建实例,这种方式即需要的时候,保证肯定已经创建好了,成为饿汉式。
饿汉式(静态代码块)
和上面类似,只不过是在静态代码块中创建实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Mouse2 {
// 私有化构造方法
private Mouse2(){}
// 定义常量,创建实例
public static Mouse2 instance;
// 静态代码块
static {
instance = new Mouse2();
}
// 提供静态方法,返回实例。
public static Mouse2 getInstance(){
return instance;
}
}优缺点和上面类似。注意,饿汉式是线程安全的,缺点是会造成内存浪费。
懒汉式(线程不安全)
懒汉式,就是需要的时候,才创建对象,而不是类加载的时候就创建了。但是显然线程不安全,对同一个变量赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Dog {
private static Dog instance;
private Dog(){}
// 提供一个静态的共有方法,当使用到该方法时,才会创建instance
// 即懒汉式,先判断是否为空,如果为空,则创建,不为空则返回已有的对象。
public static Dog getInstance() {
if(instance == null) {
instance = new Dog();
}
return instance;
}
}优点:达到了懒加载的效果。
缺点:在多线程下,如果有一个线程进入到了if判断里面,还没有来得及创建对象并赋值,而此时另一个线程也有可能进入,导致创建多个对象。显然不满足要求,这种方式不能使用。
懒汉式(线程安全,同步方法)
既然线程不安全,加入同步处理机制即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Dog2 {
private static Dog2 instance;
private Dog2(){}
// 提供一个静态的共有方法,当使用到该方法时,才会创建instance
// 即懒汉式,先判断是否为空,如果为空,则创建,不为空则返回已有的对象。
// 加入synchronized同步,保证线程安全
public static synchronized Dog2 getInstance() {
if(instance == null) {
instance = new Dog2();
}
return instance;
}
}虽然此方法解决了线程安全,但是后续如果有多个线程进入同步方法,其实已经创建好对象了,没必要同步串行进行该方法,直接返回return即可。这种方式效率太低,在实际开发中,不建议使用这种方式。
懒汉式(线程安全,同步代码块)
同步代码块在方法里面,if外面进行synchronized包装。
双重检查(Double-Check)
既然上面的懒汉式效率太低,此时可以添加一层非同步代码块if判断,这样,多线程达到的时候就可以并行判断了。但是核心创建对象仍然需要synchronized同步。但是要注意,因为外层非同步代码块用到了共享变量,所以为了保证及时可见,要加volatile修饰。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Dog3 {
private static volatile Dog3 instance;
private Dog3(){}
public static Dog3 getInstance() {
// 首次判断,非同步
if(instance == null) {
// 同步代码块
synchronized (Dog3.class) {
if(instance == null) {
instance = new Dog3();
}
}
}
return instance;
}
}既实现了懒加载,也解决了线程安全问题,并且也保证了效率,推荐使用。
静态内部类
静态内部类在外部类被状态的时候,并不会被装载。只有显式需要该内部类的时候才会被装载,而且只会被装载一次。另外,在类装载的时候是线程安全的。所以静态内部类既实现了懒加载,也解决了线程安全问题,同时也保证了效率。推荐使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Cat {
private Cat(){}
// 静态内部类
// 只有在显式调用内部类的时候,才会被加载,即实现了懒加载
// 同时类加载的时候是线程安全的
// 保证了效率。
public static class CreateCat{
// 常量
private static final Cat instance = new Cat();
}
public static Cat getInstance() {
return CreateCat.instance;
}
}枚举
不太了解枚举,通过枚举,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,推荐使用。代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class SingletonTest04 {
public static void main(String[] args) {
System.out.println();
Animal animal1 = Animal.INSTANCE;
Animal animal2 = Animal.INSTANCE;
System.out.println(animal1.hashCode());
System.out.println(animal2.hashCode());
// 通过枚举对象,也可调用实例方法
animal1.sayOK();
}
}
enum Animal {
// 属性,只有一个,保证了单例
INSTANCE;
public void sayOK() {
System.out.println("ok~");
}
}
总体来看,饿汉式的双重检查、静态内部类以及枚举方式都推荐使用。另外,饿汉式是可以使用的。
在java.lang.Runtime这个类中,就出现了单例模式,是饿汉式。源码如下所示:
1 | public class Runtime { |
4. 工厂模式
工厂模式分为简单工厂模式、工厂方法模式和抽象工厂模式三种。