设计模式
设计模式(Design Pattern)是解决特点问题特定结构的代码实现,这些解决方案是众多软件开发人员经过相当长的一段时间的实践和错误总结出来的,它是处理指定问题的最佳实践。
分类
java的设计模式,分为3类23种:
- 创建型:单例、抽象工厂、建造者、工厂、原型
- 结构型:适配器、桥接、装饰、组合、外观、享元、代理
- 行为型:模板方法、命令、迭代器、观察者、中介者、备忘录、解释器、状态、策略、责任链、访问者
1 单例模式
所谓单例模式,简单来说,保证一个趔在内存中的对象就一个。
六种实现
饿汉式
public class Singleton{
//私有构造方法,外部无法访问
private Singleton(){}
//创建私有静态对象
private static Singleton uniqueInstance = new Singleton();
//定义公有静态方法访问
public static Singleton getInstance(){
return uniqueInstance;
}
}
懒汉式
public class Singleton{
private static Singleton uniqueInstance = null;
public static Singleton getInstance(){
if(uniqueInstance==null){
uniqueInstance = new Singleton2();
}
return uniqueInstance;
}
//测试
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Singleton2 s = Singleton2.getInstance();
System.out.println(s);
}
}
}
双重锁检查式
懒汉式非线程安全,多线程高并发时会出问题,通过双重检验(Double Check),加锁进行处理。
三个特征:
- 两次判断
- 加锁 synchronized,
- 静态变量使用volatile,JVM优化未创建完成会提前返回对象,为了追求性能,但就会造成风险,如nullpoint异常。加上volatile则JVM在写没有完成时不许读。
public class Singleton3 {
private volatile static Singleton3 uniqueInstance = null;
private Singleton3() {}
public static Singleton3 getInstance() {
if(uniqueInstance == null) {
synchronized (Singleton3.class) {
if(uniqueInstance == null) {
uniqueInstance = new Singleton3();
}
}
}
return uniqueInstance;
}
}
静态块
静态代码块方式实现,这种方式和第一种类似,也是一种饿汉模式。
public class Singleton4 {
private static Singleton4 uniqueInstance;
private Singleton4() {}
//通过静态代码块初始化
static {
uniqueInstance = new Singleton4();
}
public static Singleton4 getInstance() {
return uniqueInstance;
}
}
内部类
上面我们通过双重检查加锁实现线程安全,但这样性能会下降,无法并行。
下面使用内部类方式,它无需显式的进行任何同步加锁操作,那它怎么保证线程安全呢?和饿汉模式一样,是靠JVM保证类的静态成员只能被加载一次的特点,这样就从JVM层面保证了只会有一个实例对象。那么问题来了,这种方式和饿汉模式又有什么区别呢?不也是立即加载么?实则不然,加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。可以说这种方式是实现单例模式的最优解。
public class Singleton5 {
//静态内部类,staitc修饰类时,只在内部类使用
private static class SingletonHolder{
private static final Singleton5 instance = new Singleton5();
}
private Singleton5() {}
public static final Singleton5 getInstance() {
//延时加载,外部类被加载时,内部类不会进行加载,当调用时才进行加载
return SingletonHolder.instance;
}
}
枚举
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化,这个是JVM保证的。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏。
public class Singleton6 {
private Singleton6() {}
public static Singleton6 getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private Singleton6 singleton;
Singleton() {
singleton = new Singleton6();
}
public Singleton6 getInstance() {
return singleton;
}
}
}
小结
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度。
懒汉模式是一种偷懒的模式,在程序初始化时不会创建实例,只有在使用实例的时候才会创建实例,所以懒汉模式解决了饿汉模式带来的空间浪费问题。
饿汉式由于使用了static关键字,保证了在引用这个变量时,关于这个变量的所以写入操作都完成,所以保证了JVM层面的线程安全。
if(uniqueInstance == null) {
uniqueInstance = new Singleton2();
}
懒汉式是线程非安全,假设有两个线程同时进入到上面这段代码,因为没有任何资源保护措施,所以两个线程可以同时判断的 instance 都为空,都将去初始化实例,所以就会出现多份实例的情况。
双重检查锁模式解决了单例、性能、线程安全问题,但是这种写法同样存在问题:在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile关键字,volatile关键字严格遵循happens-before原则,即:在读操作前,写操作必须全部完成。
静态内部类模式也称单例持有者模式,实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由static修饰,保证只被实例化一次,并且严格保证实例化顺序。
名称 | 饿汉模式 | 懒汉模式 | 双重检查锁模式 | 静态内部类 | 枚举类 |
---|---|---|---|---|---|
可用性 | 可用 | 不推荐使用 | 推荐使用 | 推荐使用 | 推荐使用 |
特点 | 不能实现懒加载,可能造成空间浪费 | 不加锁线程不安全;加锁性能差 | 线程安全;延迟加载;效率较高 | 避免了线程不安全,延迟加载,效率高。 | 写法简单;线程安全;只装载一次 |
2 模板设计模式
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板,它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方法进行。这种类型的设计模式属于行为型模式。
介绍
**意图:**定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
**主要解决:**一些方法通用,却在每一个子类都重新写了这一方法。
**何时使用:**有一些通用的方法。
**如何解决:**将这些通用算法抽象出来。
**关键代码:**在抽象类实现,其他步骤在子类实现。
应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
**缺点:**每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
实现
我们将创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。Cricket 和 Football 是扩展了 Game 的实体类,它们重写了抽象类的方法。
TemplatePatternDemo,我们的演示类使用 Game 来演示模板模式的用法。
步骤1
创建一个抽象类,它的模板方法被设置为final
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
//模板
public final void play(){
//初始化游戏
initialize();
//开始游戏
startPlay();
//结束游戏
endPlay();
}
}
步骤 2
创建扩展了上述类的实体类。
Cricket.java
public class Cricket extends Game {
@Override
void endPlay() {
System.out.println("Cricket Game Finished!");
}
@Override
void initialize() {
System.out.println("Cricket Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Cricket Game Started. Enjoy the game!");
}
}
Football.java
public class Football extends Game {
@Override
void endPlay() {
System.out.println("Football Game Finished!");
}
@Override
void initialize() {
System.out.println("Football Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Football Game Started. Enjoy the game!");
}
}
步骤 3
使用 Game 的模板方法 play() 来演示游戏的定义方式。
TemplatePatternDemo.java
public class TemplatePatternDemo {
public static void main(String[] args) {
Game game = new Cricket();
game.play();
System.out.println();
game = new Football();
game.play();
}
}
步骤 4
执行程序,输出结果:
Cricket Game Initialized! Start playing.
Cricket Game Started. Enjoy the game!
Cricket Game Finished!
Football Game Initialized! Start playing.
Football Game Started. Enjoy the game!
Football Game Finished!