单例模式
定义
单例设计模式,一般指类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
例子
比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象,SessionFactory并不是轻量级的,一般情况,一个项目只需要一个SessionFactory,这就需要用到单例模式。
饿汉式(静态常量)
实现
1)构造器私有化。
2)类的内部创建对象。
3)向外暴露一个公共方法
代码
class Singleton {
//1)
private Singleton() {};
//2)
private static Singleton instance = new Singleton();
//3)
public static Singleton getInstance() {
return instance;
}
}
优点
在类加载时就完成了实例化,避免了线程同步问题。
缺点
在类装载时就完成了实例化,没有达到懒加载的效果(ps:导致类加载的原因有多种,因此不能确定是否有非getInstance方法外的其他方式导致类加载)。如果一次都未使用这个实例,就造成了内存浪费。
饿汉式(静态代码块)
实现
在静态代码块执行时,创建单例对象。
代码
class Singleton {
private Singleton() {};
private static Singleton instance;
//静态代码块
static {
instance = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
优点
缺点
这种优缺点和前一种一样,不同就是将实例化的过程放在了静态代码块中。
懒汉式(线程不安全)
实现
当调用getInstance方法时,才创建单例对象。
代码
class Singleton {
private Singleton() {};
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点
能达到懒加载的效果。
缺点
在多线程情况下,会出现线程安全问题,一个线程通过if判断但还没执行创建单例,另一个线程也通过了判断,这样会产生多个实例。对于单例模式,这种方法在实际开发基本不会使用。
懒汉式(线程安全,同步方法)
实现
使用synchronized使用getInstance方法同步
代码
class Singleton {
private Singleton() {};
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点
继承了前一种方法的优点,且解决了线程安全问题。
缺点
使用synchronize使得效率低。实际场景中,只有第一次执行实例化,其余都是直接获取返回,而每次getInstance方法都要进行同步,效率太低。
懒汉式(*线程(不)安全,同步代码块)
实现
把同步机制不作用到方法上,而是代码块上。
代码
class Singleton {
private Singleton() {};
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
优点
缺点
这种是一个很容易错误的误区,误以为锁住实例化相关的代码块就能解决前一种方法效率低的问题,但是实际该写法甚至不能保证线程安全,原因和上面提到的懒汉式(线程不安全)相同。
*双重检查
实现
1)使用volatile保证instance的可见性。
2)同步机制作用到代码块上。
3)使用两次if判断检查
代码
class Singleton {
private Singleton() {};
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
优点
支持懒加载,保证线程安全且效率较高
缺点
没有什么明显的缺点,在实际开发中,推荐使用该方式设计单例模式
静态内部类
实现
通过静态内部类进行实例化
代码
class Singleton {
//构造器私有化
private Singleton() {};
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
优点
采用类加载机制保证初始化实例只有一个且线程安全;静态内部类在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载该内部类,从而完成Singleton的实例化,不会因为没使用而浪费内存;效率高。
缺点
无明显缺点,推荐使用
枚举
实现
使用枚举类
代码
enum Singleton {
INSTANCE;
public void op1() {
System.out.println("op1");
}
}
优点
避免多线程同步问题,防止反序列化重新创建新的对象。
缺点
无明显缺点,推荐使用。
推荐使用
双重检查、静态内部类、枚举。如果单例模式一定会至少使用一次,那么饿汉式也是可以使用的。
单例模式应用
很明显jdk源码中的Runtime就是使用了单例模式中的饿汉式(静态常量),不仅线程安全且效率高,又因为Runtime基本会使用,所以不会造成内存浪费。
注意事项
1)单例模式保证系统内存中该类值存在一个对象,节省了系统资源。对于一些需要频繁创建和销毁的对象,使用单例模式可以提高系统性能。
2)当想实例化一个单例类时,使用相应获取对象的方法,而不是使用new。
3)使用场景:需要频繁进行创建和销毁的对象;创建对象时耗时过多或耗费资源过多,但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(如数据源、session工厂等)。
工厂模式
简单工厂模式
定义
1)属于创建型模式,是工厂模式的一种,是工厂模式家族中最简单实用的模式。
2)是由一个工厂对象决定创建出哪一种产品类的实例。
3)定义一个创建对象的类,由这个类来封装实例化对象的行为。
例子
假如有两种披萨类型,BeefPizza和CheesePizza,披萨的制作有prepare、bake、cut、box,我们需要完成披萨的预定功能。
定义抽象类Pizza
public abstract class Pizza {
protected String name;
/**
* 准备原材料
*/
public abstract void prepare();
public void bake() {
System.out.println(name + " baking...");
}
public void cut() {
System.out.println(name + " cutting...");
}
public void box() {
System.out.println(name + " boxing...");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
创建BeefPizza和CheesePizza类继承Pizza抽象类,重写prepare方法
public class BeefPizza extends Pizza{
@Override
public void prepare() {
setName("牛肉披萨");
System.out.println("准备制作牛肉披萨的原材料");
}
}
public class CheesePizza extends Pizza{
@Override
public void prepare() {
setName("芝士披萨");
System.out.println("准备制作芝士酪披萨的原材料");
}
}
在传统模式下的设计:
public class OrderPizza {
public OrderPizza() {
Pizza pizza = null;
String orderType;
do {
orderType = getType();
if ("beef".equals(orderType)) {
pizza = new BeefPizza();
} else if ("cheese".equals(orderType)) {
pizza = new CheesePizza();
} else {
break;
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} while (true);
}
public String getType() {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入订购的披萨类型:beef/cheese,输入-1结束");
do {
String next = scanner.next();
if ("-1".equals(next)) {
break;
}
return next;
} while (true);
return null;
}
}
传统模式下:
1)优点:好理解、易操作。
2)缺点:违反了开闭原则,当需要增加新的种类的披萨时,需要修改OrderPizza代码。
使用简单工厂模式:
把创建披萨对象实例封装到一个类中,这样增加新的种类的披萨时,只需要修改该类即可,其他有创建到Pizza类的代码不用修改。
改进后代码:
新增简单工厂类
public class SimpleFactory {
public Pizza createPizza(String orderType) {
Pizza pizza = null;
System.out.println("使用简单工厂模式");
switch (orderType) {
case "beef":
pizza = new BeefPizza();
break;
case "cheese":
pizza = new CheesePizza();
break;
default:
break;
}
return pizza;
}
}
修改OrderPizza类
public class OrderPizza {
public OrderPizza(SimpleFactory simpleFactory) {
setFactory(simpleFactory);
}
/**
*聚合
*/
SimpleFactory simpleFactory;
Pizza pizza = null;
public void setFactory(SimpleFactory factory) {
String orderType = "";
this.simpleFactory = factory;
do {
orderType = getType();
pizza = this.simpleFactory.createPizza(orderType);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
break;
}
} while (true);
}
public String getType() {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入订购的披萨类型:beef/cheese,输入-1结束");
do {
String next = scanner.next();
if ("-1".equals(next)) {
break;
}
return next;
} while (true);
return null;
}
}
传统vs简单工厂类图
图能更直观看到两种设计的不同
传统:
简单工厂:
补充
简单工厂模式也叫静态工厂模式。即让简单工厂类的生成对象实例的方法变为静态。
SimpleFactory中createPizza方法加上static关键字
public class SimpleFactory {
public static Pizza createPizza(String orderType) {
Pizza pizza = null;
System.out.println("使用简单工厂模式");
switch (orderType) {
case "beef":
pizza = new BeefPizza();
break;
case "cheese":
pizza = new CheesePizza();
break;
default:
break;
}
return pizza;
}
}
修改OrderPizza类,不需要setFactory。
public class OrderPizza {
public OrderPizza() {
String orderType = "";
do {
orderType = getType();
pizza = SimpleFactory.createPizza(orderType);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
break;
}
} while (true);
}
Pizza pizza = null;
public String getType() {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入订购的披萨类型:beef/cheese,输入-1结束");
do {
String next = scanner.next();
if ("-1".equals(next)) {
break;
}
return next;
} while (true);
return null;
}
}
工厂方法模式
定义
定义一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式相对于简单工厂模式将对象的实例化推迟到子类。
例子
在简单工厂模式的例子中扩展新的需求,披萨在种类的情况下还多了一个地域风味,比如北京芝士披萨、北京牛肉披萨、伦敦芝士披萨等等。
这时候如果使用简单工厂模式,就需要创建不同的简单工厂类,比如BJSimpleFactory、LDSimpleFactory。这样的话,可扩展性和维护性不好。
使用工厂方法模式:
将披萨的实例化功能抽象为抽象方法,在不同地域风味的子类中具体实现。
代码,抽象基类Pizza,四个子类均继承Pizza并重写其prepare方法
public class BJCBeefPizza extends Pizza {
@Override
public void prepare() {
setName("北京牛肉披萨");
System.out.println("准备制作北京牛肉披萨的原材料");
}
}
public class BJCheesePizza extends Pizza {
@Override
public void prepare() {
setName("北京芝士披萨");
System.out.println("准备制作北京芝士披萨的原材料");
}
}
public class LDBeefPizza extends Pizza {
@Override
public void prepare() {
setName("伦敦牛肉披萨");
System.out.println("准备制作伦敦牛肉披萨的原材料");
}
}
public class LDCheesePizza extends Pizza {
@Override
public void prepare() {
setName("伦敦芝士披萨");
System.out.println("准备制作伦敦芝士披萨的原材料");
}
}
在OrderPizza类中定义一个抽象方法createPizza
public abstract class OrderPizza {
//定义一个抽象方法
abstract Pizza createPizza(String orderType);
public OrderPizza() {
Pizza pizza = null;
String orderType;
do {
orderType = getType();
pizza = createPizza(orderType);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} while (true);
}
public String getType() {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入订购的披萨类型:beef/cheese,输入-1结束");
do {
String next = scanner.next();
if ("-1".equals(next)) {
break;
}
return next;
} while (true);
return null;
}
}
定义BJOrderPizza和LDOrderPizza继承OrderPizza并实现其抽象方法的细节
public class BJOrderPizza extends OrderPizza{
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if ("beef".equals(orderType)) {
pizza = new BJCBeefPizza();
} else if ("cheese".equals(orderType)) {
pizza = new BJCheesePizza();
}
return pizza;
}
}
public class LDOrderPizza extends OrderPizza{
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if ("beef".equals(orderType)) {
pizza = new LDBeefPizza();
} else if ("cheese".equals(orderType)) {
pizza = new LDCheesePizza();
}
return pizza;
}
}
结构设计类图
抽象工厂模式
定义
1)定义一个接口用于创建相关或有依赖关系的对象簇,而无需指明具体的类。
2)将简单工厂模式与工厂方法模式进行整合。
3)从设计层面看,就是对简单工厂模式的进一步抽象。
4)将工厂抽象为两层,抽象工厂和具体实现的工厂子类。根据创建对象类型使用对应的工厂子类,将单个工厂类变成了工厂簇,更利于代码的维护与扩展。
例子
仍然是披萨例子,在工厂方法模式的基础上改进。
定义一个接口AbsFactory
public interface AbsFactory {
public Pizza createPizza(String orderType);
}
BJFactory和LDFactory分别实现AbsFactory接口
public class BJFactory implements AbsFactory{
@Override
public Pizza createPizza(String orderType) {
Pizza pizza = null;
if ("cheese".equals(orderType)) {
pizza = new BJCheesePizza();
} else if ("beef".equals(orderType)) {
pizza = new BJCBeefPizza();
}
return pizza;
}
}
public class LDFactory implements AbsFactory{
@Override
public Pizza createPizza(String orderType) {
Pizza pizza = null;
if ("cheese".equals(orderType)) {
pizza = new LDCheesePizza();
} else if ("beef".equals(orderType)) {
pizza = new LDBeefPizza();
}
return pizza;
}
}
修改OrderPizza类,去掉了BJOrderPizza和LDOrderPizza类
public class OrderPizza {
/**
* 聚合关系
*/
AbsFactory factory;
public OrderPizza(AbsFactory factory) {
setFactory(factory);
}
private void setFactory(AbsFactory factory) {
Pizza pizza = null;
String orderType = "";
this.factory = factory;
do {
orderType = getType();
pizza = factory.createPizza(orderType);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
break;
}
} while (true);
}
public String getType() {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入订购的披萨类型:beef/cheese,输入-1结束");
do {
String next = scanner.next();
if ("-1".equals(next)) {
break;
}
return next;
} while (true);
return null;
}
}
结构设计类图
工厂模式应用
在java.util.Calendar的源码中有使用到简单工厂模式。
根据创建时候的时区、语言、国家来创建对应的实例。
工厂模式小结
意义
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦,从而提高项目的扩展和维护性。
注意事项
1)创建对象实例时,不要直接new类,而是把这个new类的动作放在一个工厂的方法中并返回。(变量不要直接持有具体类的引用)。
2)不要让类继承具体类,而是继承抽象类或者实现接口。
3)不要覆盖基类中已经实现的方法。(里氏替换原则)