本博客对Java中常见的设计模式进行简要的介绍汇总.
单例模式
引子
很多场景下我们只需要一个该类型的对象, 比如: 线程池(ThreadPool), 缓存(Cache), 对话框(Dialog), 处理偏好设置和注册表(Registry)的对象, 日志对象, 充当打印机/显卡等设备的驱动程序的对象等等. 事实上, 这类对象也只能 有一个实例, 如果多了就会导致许多问题. 比如: 程序异常, 资源使用过量, 结果不一致等. 那么这种只需要一个实例对象的编程模式在软件工程上被抽象为单例模式.
定义
单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。
基本思路
单例模式的基本思路就是在类中想构造函数私有化, 然后提供一个public的获取实例对象的方法让外部获取, 并且确保外部多次获取返回的都是同一个对象.
实现方式
一. 饿汉式
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.lee.tank.designPatterns.Singleton;
public class Mgr01 { private Mgr01() {}; private static final Mgr01 INSTANCE = new Mgr01(); public static Mgr01 getInstance() { return INSTANCE; } public static void main(String[] args) { Mgr01 m1 = Mgr01.getInstance(); Mgr01 m2 = Mgr01.getInstance(); System.out.println(m1 == m2); } }
|
特点
类加载到内存后,就实例化一个单例,JVM保证线程安全
简单实用,推荐使用!
唯一缺点:不管用到与否,类装载时就完成实例化
Class.forName(“”)
(话说你不用的,你装载它干啥)
二. 饿汉式变体
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.mashibing.dp.singleton;
public class Mgr02 { private static final Mgr02 INSTANCE; static { INSTANCE = new Mgr02(); }
private Mgr02() {};
public static Mgr02 getInstance() { return INSTANCE; } public static void main(String[] args) { Mgr02 m1 = Mgr02.getInstance(); Mgr02 m2 = Mgr02.getInstance(); System.out.println(m1 == m2); } }
|
三. 懒汉式
代码
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 26 27 28 29 30 31 32
| package com.lee.tank.designPatterns.Singleton;
public class Mgr03 { private static Mgr03 INSTANCE;
private Mgr03() { }
public static Mgr03 getInstance() { if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr03(); } return INSTANCE; } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()-> System.out.println(Mgr03.getInstance().hashCode()) ).start(); } } }
|
特点
虽然达到了按需初始化的目的,但却带来线程不安全的问题, 如下图:

测试结果
四. 懒汉式变体(线程安全但低效)
懒汉式的改进, 满足线程安全
代码
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 26 27 28 29 30 31 32 33 34 35 36 37 38
| package com.mashibing.dp.singleton;
public class Mgr04 { private static Mgr04 INSTANCE;
private Mgr04() { }
public static synchronized Mgr04 getInstance() { if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr04(); } return INSTANCE; }
public void m() { System.out.println("m"); }
public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr04.getInstance().hashCode()); }).start(); } } }
|
特点
虽然懒汉式达到了按需初始化的目的,但却带来线程不安全的问题, 该版本就是通过将懒汉式的getInstance方法同步化(synchronized)来解决线程安全的问题的.
但这同时也带来效率下降
五. 懒汉式变体(高效但线程不安全)
代码
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 26 27 28 29 30 31 32 33 34 35 36
| package com.mashibing.dp.singleton;
public class Mgr05 { private static Mgr05 INSTANCE;
private Mgr05() { }
public static Mgr05 getInstance() { if (INSTANCE == null) { synchronized (Mgr05.class) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr05(); } } return INSTANCE; }
public void m() { System.out.println("m"); }
public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr05.getInstance().hashCode()); }).start(); } } }
|
特点
为了尝试优化Mgr04的低效特点, 尝试将同步代码范围缩小以提上效率. 所以将同步函数变为了同步代码块. 但是, 此时 if(INSTANCE==null)的判断不属于同步块之后会造成线程安全隐患. 所以依旧不可取.
六. 懒汉式变体(高效&安全)
代码
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 26 27 28 29 30 31
| package com.lee.tank.designPatterns.Singleton;
public class Mgr06 { private Mgr06() { } private static volatile Mgr06 INSTANCE;
public static Mgr06 getInstance() { if (INSTANCE == null) { synchronized (Mgr06.class) { if (INSTANCE == null) { try { Thread.sleep(100); } catch (InterruptedException e) { } INSTANCE = new Mgr06(); } } } return INSTANCE;
}
public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr06.getInstance().hashCode()); }).start(); } } }
|
特点
V7 静态内部类方式
代码
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 26 27 28 29
| package com.mashibing.dp.singleton;
public class Mgr07 {
private Mgr07() { }
private static class Mgr07Holder { private final static Mgr07 INSTANCE = new Mgr07(); }
public static Mgr07 getInstance() { return Mgr07Holder.INSTANCE; }
public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr07.getInstance().hashCode()); }).start(); } }
}
|
特点
JVM加载内部类时不会加载内部类, 所以 当Mgr07被加载时, Mgr07Holder 就不会被加载. 所以也就实现了懒加载. 另外, JVM加载某个类时只加载一次, 所以只有当外部用户调用Mgr07的getInstance方法时才会用到Mgr07Holder类, 才回去加载Mgr07Holder, 但是有只加载一次, 所以也*保证了线程的安全性. *
V8
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.lee.tank.designPatterns.Singleton;
public enum Mgr08 {
INSTANCE;
public void m() {}
public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr08.INSTANCE.hashCode()); }).start(); } }
}
|
特点
不仅可以解决线程同步,还可以防止反序列化.
单例总结
Strategy策略
Scenario
比如说有这样一个场景:
首先需要对int 类型的一个数组int[] arr进行排序, 比较自然的想法就是定义一个工具类Sorter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.lee.tank.designPatterns.Strategy;
public class Sorter {
public void sort(int[] arr) { for(int i=0; i<arr.length - 1; i++) { int minPos = i;
for(int j=i+1; j<arr.length; j++) { minPos = arr[j]<arr[minPos] ? j : minPos; } swap(arr, i, minPos); } } void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
|
然后在需要对数组进行排序的时候调用sort(int[] arr)方法即可. 如下:
1 2 3 4 5 6 7 8 9 10 11 12
| package com.lee.tank.designPatterns.Strategy;
import java.util.Arrays;
public class Strategy { public static void main(String[] args) { int[] arr={3,5,2,5,1,2,7,9,0,10}; Sorter sorter = new Sorter(); sorter.sort(arr); System.out.println(Arrays.toString(arr)); } }
|
运行上面的代码确实能达到排序的目的

Extension
那么 问题来了, 如果再想对double类型数组排序, 该怎么办呢? 就只能重写Sort类或者重载里面的sort方法:
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 26 27 28 29 30 31 32 33 34 35 36 37 38
| package com.lee.tank.designPatterns.Strategy;
public class Sorter {
public void sort(int[] arr) { for(int i=0; i<arr.length - 1; i++) { int minPos = i;
for(int j=i+1; j<arr.length; j++) { minPos = arr[j]<arr[minPos] ? j : minPos; } swap(arr, i, minPos); } }
public void sort(double[] arr) { for(int i=0; i<arr.length - 1; i++) { int minPos = i;
for(int j=i+1; j<arr.length; j++) { minPos = arr[j]<arr[minPos] ? j : minPos; } swap(arr, i, minPos); } }
void swap(double[] arr, int i, int j) { double temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
|
然后需要对double进行排序时进行调用sort(double[] arr_d)即可, 如下:

Adapter
那么如果是对猫Cat, 狗Dog, 人Person进行排序, 是不是也要进行相应类型的重载sort(Type[] t)才能达到目的呢?
所以, 就有了这样一种想法, 能不能重新定义sort方法里面的比较大小, 不是直接按照大小, 而是可以根据各自类型的特有属性去个性化比较, 比如比较猫时按照年龄比较, 比较狗时按照体重比价, 那就就有了下面的代码
step 1 定义类型的比较方法, 猫按照年龄比较
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 26 27 28 29 30 31 32
| package com.lee.tank.designPatterns.Strategy;
public class Cat { String name; int age;
public Cat(String name, int age) { this.name = name; this.age = age; }
@Override public String toString() { return "Cat{" + "name='" + name + '\'' + ", age=" + age + '}'; }
int compareTo(Cat c){ if(this.age < c.age) { return -1; }else if(this.age > c.age) { return 1; }else { return 0; }
}
}
|
然后在sort(Cat[] c)方法比较方式进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.lee.tank.designPatterns.Strategy;
public class Sorter {
public void sort(Cat[] arr) { for(int i=0; i<arr.length - 1; i++) { int minPos = i;
for(int j=i+1; j<arr.length; j++) { minPos = arr[j].compareTo(arr[minPos])==-1 ? j : minPos; } swap(arr, i, minPos); } }
private void swap(Cat[] arr, int i, int j) { Cat temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
|
Almighty-Adapter
但是似乎又忘了, 一开始, 我们是想让sort方法适应所有类型, 而不是某一种类型. 所以对sort(Cat[] c)方法进行类型适配是必须的.
因为是适配所有类型, 所以很自然的想到适配sort(Object[] obj)的样子, 但是obj并没有compareTo方法. 所以我们的想法是, 自定义一个接口不妨叫Comparable, 里面有一个compareTo方法. 然后有排序需求的实体类必须implements Comparable 实现该接口, 实现其compareTo()方法, sort方法重写成sort(Comparable[] c)的形式, 即可排序.
所以总体需要如下3个步骤:
step 1. 自定义接口
1 2 3 4 5
| package com.lee.tank.designPatterns.Strategy;
public interface Comparable { int compareTo(Object[] obj); }
|
step 2. 实体类实现接口
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 26 27 28 29 30
| package com.lee.tank.designPatterns.Strategy;
public class Dog implements Comparable{ int height ; int weight ; public Dog(int height, int weight) { this.height = height; this.weight = weight; }
@Override public String toString() { return "Dog{" + "height=" + height + ", weight=" + weight + '}'; }
@Override public int compareTo(Object obj) { Dog d=(Dog) obj; if(this.weight<d.weight){ return -1; }else if(this.weight>d.weight){ return 1; }else{ return 0; } } }
|
step 3 重写sort方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.lee.tank.designPatterns.Strategy;
public class Sorter{
public void sort(Comparable[] arr) { for(int i=0; i<arr.length - 1; i++) { int minPos = i;
for(int j=i+1; j<arr.length; j++) { minPos = arr[j].compareTo(arr[minPos])==-1 ? j : minPos; } swap(arr, i, minPos); } }
private void swap(Object[] arr, int i, int j) { Object temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
|
step 4 调用验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.lee.tank.designPatterns.Strategy;
import java.util.Arrays;
public class Strategy { public static void main(String[] args) { Sorter sorter = new Sorter(); Cat[] cats={new Cat("Alex",3), new Cat("Brox",2),new Cat("Joshua",4), new Cat("Ted",1)}; sorter.sort(cats); System.out.println(Arrays.toString(cats));
Dog[] dogs={new Dog(5,10),new Dog(7,8),new Dog(6,9)}; sorter.sort(dogs); System.out.println(Arrays.toString(dogs));
} }
|

Generic
但是这种方式有一个弊端: 因为对Object做的适配是通过Comparable接口里的(int compareTo(Object[] obj); 实现的, 那么每次实体类实现接口compareTo方法时, 第一时间都要做一次类型强转如: Dog d=(Dog) obj; 那么有没有办法能避免这种冗余操作呢? 聪明的你可能已经想到了使用泛型即可完成这种功能.
所以重新调整上面的代码:
step 1. 接口泛型化
1 2 3 4 5
| package com.lee.tank.designPatterns.Strategy;
public interface Comparable<T> { int compareTo(T t); }
|
step 2. Sort (和上一版本保持一致即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.lee.tank.designPatterns.Strategy;
public class Sorter{
public void sort(Comparable[] arr) { for(int i=0; i<arr.length - 1; i++) { int minPos = i;
for(int j=i+1; j<arr.length; j++) { minPos = arr[j].compareTo(arr[minPos])==-1 ? j : minPos; } swap(arr, i, minPos); } }
private void swap(Object[] arr, int i, int j) { Object temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
|
step 3. 实体类泛化
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 26 27 28 29 30
| package com.lee.tank.designPatterns.Strategy;
public class Dog implements Comparable<Dog>{ int height ; int weight ;
public Dog(int height, int weight) { this.height = height; this.weight = weight; }
@Override public String toString() { return "Dog{" + "height=" + height + ", weight=" + weight + '}'; }
@Override public int compareTo(Dog d) { if(this.weight<d.weight){ return -1; }else if(this.weight>d.weight){ return 1; }else{ return 0; } } }
|
step 4 调用验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.lee.tank.designPatterns.Strategy;
import java.util.Arrays;
public class Strategy { public static void main(String[] args) { Sorter sorter = new Sorter(); Cat[] cats={new Cat("Alex",3), new Cat("Brox",2),new Cat("Joshua",4), new Cat("Ted",1)}; sorter.sort(cats); System.out.println(Arrays.toString(cats));
Dog[] dogs={new Dog(5,10),new Dog(7,8),new Dog(6,9)}; sorter.sort(dogs); System.out.println(Arrays.toString(dogs));
} }
|
发现也能实现最终排序功能

策略
截止目前, 虽然对比较类型做了泛化让其适配所有类型, 只要该类型实现了Comparable接口及其compareTo方法即可. 但是灵活性还是不够高, 如果想使用身高height(而不是原来的体重weight)对狗进行比较, 即提高比较本身灵活性, 改变比较的策略, 又该怎么做呢 ? 这才真正引出策略模式.
那么如何才能根据用户需求进行个性化的比较策略的切换以期达到灵活比较的目标呢? 这就需要用户将策略类型作为参数传进比较方法.
Step 1. 定义Comparator接口
1 2 3 4 5
| package com.lee.tank.designPatterns.Strategy;
public interface Comparator<T> { public int compare(T o1, T o2); }
|
Step 2. 定义策略类型
需要什么类型就定义什么类型比如下面定义了两种, DogComparator1 按体重weight比较.DogComparator2 按体重height比较.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.lee.tank.designPatterns.Strategy;
public class DogComparator1 implements Comparator<Dog> { @Override public int compare(Dog dogA, Dog dogB) { if (dogA.weight < dogB.weight) { return -1; } else if (dogA.weight > dogB.weight) { return 1; } else { return 0; } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.lee.tank.designPatterns.Strategy;
public class DogComparator2 implements Comparator<Dog> { @Override public int compare(Dog dogA, Dog dogB) { if (dogA.height < dogB.height) { return -1; } else if (dogA.height > dogB.height) { return 1; } else { return 0; } } }
|
Step 3 Sorter方法应用策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.lee.tank.designPatterns.Strategy;
public class Sorter<T> { public void sort(T[] arr, Comparator<T> ct) { for (int i = 0; i < arr.length - 1; i++) { int minPos = i;
for (int j = i + 1; j < arr.length; j++) { minPos = ct.compare(arr[j],arr[minPos])==-1 ? j : minPos; } swap(arr, i, minPos); } } private void swap(T[] arr, int i, int j) { T temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
|
Step 4. 调用验证
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
| package com.lee.tank.designPatterns.Strategy;
import java.util.Arrays;
public class Strategy { public static void main(String[] args) { Sorter<Dog> dogSorter = new Sorter<>(); Dog[] dogs={new Dog(5,10),new Dog(7,8),new Dog(6,9)}; dogSorter.sort(dogs, new DogComparator1()); System.out.println("按1 weight排序:\n"+Arrays.toString(dogs));
dogSorter.sort(dogs, (dogA,dogB)->{ if (dogA.height < dogB.height) { return -1; } else if (dogA.height > dogB.height) { return 1; } else { return 0; } }); System.out.println("按2 height排序:\n"+Arrays.toString(dogs));
} }
|

总结

工厂模式
门面
装饰器
责任链
观察者
组合模式
享元
代理(静/动)
迭代器
访问者
构建器
适配器
桥接
命令
原型(prototype)
备忘录
模板
状态
解释器
总结和6大设计原则