创建型模式之单例模式与工厂模式(一)

一、基本介绍

  创建型模式,就是创建对象的模式,抽象了实例化的过程。它帮助一个系统独立于如何创建、组合和表示它的那些对象。关注的时对象的创建,创建型模式将创建对象的过程进行了抽象,也可以理解为将创建对象的过程进行了封装,作为客户程序仅仅需要去使用对象,而不再关心创建过程中的逻辑。

  具体的创建型模式可分为:

  • 单例模式(Singleton)
  • 简单工厂模式(Simple Factory)
  • 工厂方法模式(Factory Method)
  • 抽象工厂模式(Abstract Factory)
  • 原型模式(Prototype)
  • 建造者模式(Builder)

二、单例模式

1,基本介绍

  所谓的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。例如:Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这时就需要使用单例模式。

2,实现方式

  单例设计模式有八种方式(推荐使用1、2、6、7、8):

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程不安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

3,饿汉式(静态常量)

a)应用实例

class Singleton{
    private static final Singleton singleton = new Singleton();
    private Singleton() {
    }
    public static Singleton getInstant() {
        return singleton;
    }
}

b)优缺点

  优点:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题

  缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费

  结论:这种单例模式可用,但是可能造成内存浪费

4,饿汉式(静态代码块)

a)应用实例

class Singleton1{
    private static Singleton1 singleton;
    private Singleton1(){}
    static {
        singleton = new Singleton1();
    }
    public static Singleton1 getInstance(){
        return singleton;
    }
}

b)优缺点

  优缺点:这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

  结论:这种单例模式可用,但是可能造成内存浪费。

5,懒汉式(线程不安全)

a)应用实例

class Singleton2{
    private static Singleton2 singleton;
    private Singleton2(){}
    public static Singleton2 getSingleton(){
        if (singleton == null) {
            singleton = new Singleton2();
        }
        return singleton;
    }
}

b)优缺点

  • 起到了LazingLoading的效果(使用时才创建),但是只能在单线程下使用。
  • 如果在多线程下,多个线程同时判断singleton为null,会创建多个实例
  • 结论:在实际开发中,不要使用这种方式创建

6,懒汉式(线程安全,同步方法)

a)应用实例

class Singleton3{
    private static Singleton3 singleton;
    private Singleton3(){}
    public static synchronized Singleton3 getSingleton(){
        if (singleton == null) {
            singleton = new Singleton3();
        }
        return singleton;
    }
}

b)优缺点

  • 采用同步方法关键字synchronized解决线程安全问题
  • 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。
  • 结论:实际开发中,不推荐使用这种方式

7,懒汉式(线程不安全,同步代码块)

a)应用实例

class Singleton4{
    private static Singleton4 singleton4;
    private Singleton4(){}
    public static Singleton4 getInstance() {
        if (singleton4 == null) {
            synchronized (Singleton4.class) {
                singleton4 = new Singleton4();
            }
        }
        return singleton4;
    }
}

b)优缺点

  • 这种方式本意是想对6方法进行改进,因为前面同步方法效率太低了,改为同步产生实例化的代码块
  • 但是这种同步并不能起到线程同步的作用解决不了线程安全的问题)。因为同样如果多个线程进行判断singleton为null时,虽然会在synchronized方法外被阻塞,但是阻塞完成之后还是会继续执行产生多个不同实例对象
  • 结论:在实际开发中,不能使用这种方式

8,双重检查

a)应用实例

class Singleton5{
    private static volatile Singleton5 singleton;
    private Singleton5(){}
    public static Singleton5 getSingleton(){
        if (singleton == null) {
            synchronized (Singleton5.class) {
                if (singleton == null) {
                    singleton = new Singleton5();
                }
            }
        }
        return singleton;
    }
}

b)优缺点

  • 第一个if判断能提升创建效率,如果去掉,多线程会阻塞;
  • 第二个if判断能解决线程安全问题,如果去掉会有多个线程进入synchronized代码块中创建;
  • synchronized方法能保顺序执行,如果去掉也会创建多个实例;
  • volatile关键字能保证可见性和禁止指令重排,详细点击Volatile的应用DCL单例模式(四)
  • 结论:实现了线程安全;延迟加载;效率较高。推荐使用

9,静态内部类

a)应用实例

class Singleton6 {
    private Singleton6(){}
    private static class StaticClass {
        private static final Singleton6 INSTANCE = new Singleton6();
    }
    public static Singleton6 getInstance() {
        return StaticClass.INSTANCE;
    }
}

b)分析

  • 当外部类Singleton进行类转载时,静态内部是不会被装载的
  • 当调用Singleton的getInstance()方法,用到INSTANCE静态常量时,静态类才会被装载,且只会装载一次。在装载时,线程是安全的

c)优缺点

  • 这种方式采用了类装载的机制来保证初始化实例时只会有个一个线程
  • 静态内部类方式在Singleton类被装载时并不会立即实例化,而是需要实例化时,调用getInstance()方式,才会装载类StaticClass类,从而完成Singleton的实例化
  • 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的
  • 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
  • 结论:推荐使用

10,枚举

a)应用实例

enum Singleton7{
    INSTANCE;
}

b)优缺点

  • 这是借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
  • 结论:推荐使用

11,单例模式在JDK中的使用

  Runtime源码

  

12,注意事项

  • 单例模式保证了系统内存中改类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  • 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
  • 单例模式使用的场景:需要频繁创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

三、工厂模式

需求

  • 1) 披萨的种类很多(比如 GreekPizza、CheesePizza 等)
  • 2) 披萨的制作有 prepare,bake, cut, box
  • 3) 完成披萨店订购功能。

1,传统方式

a)代码

源码:详情

public class OrderPizza {
    public OrderPizza() {
        Pizza pizza = null;
        String orderType;
        do {
            orderType = getOrderType();
            if ("chess".equals(orderType)) {
                pizza = new ChessPizza();
                pizza.name = "奶酪";
            }else if ("greek".equals(orderType)) {
                pizza = new GreekPizza();
                pizza.name = "希腊";
            }else if ("china".equals(orderType)) {
                pizza = new ChinaPizza();
                pizza.name = "中国";
            } else {
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }
}

b)uml图

  

c)优缺点

  • 优点是比较好理解,简单易操作
  • 缺点是违反了ocp原则,对扩展开发对修改关闭。当需要添加新的pizza种类时需要修改调用方OrderPizza(如果有多个调用方OrderPizza1,OrderPizza2…)。

d)优化

  把创建Pizza对象封装到一个类(工厂类)中,在订购时只需要根据不同的orderType就能获得对应的Pizza类。

2,简单工厂模式

a)代码

源码:详情

public class SimpleFactory {
    public Pizza getPizza(String type) {
        Pizza pizza = null;
        if ("chess".equals(type)) {
            pizza = new ChessPizza();
            pizza.setName("奶酪");
        }else if ("greek".equals(type)) {
            pizza = new GreekPizza();
            pizza.setName("希腊");
        }else if ("china".equals(type)) {
            pizza = new ChinaPizza();
            pizza.setName("中国");
        }
        return pizza;
    }
}

public class OrderPizza {
    public OrderPizza(SimpleFactory simpleFactory) {
        do {
            String type = getOrderType();
            Pizza pizza = simpleFactory.getPizza(type);
            if (pizza == null) {
                System.out.println("当前pizza的种类没有。");
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }
}

b)uml图

  

c)分析

  如果需要重新添加一个pizza的种类,需要在添加一个类继承Pizza类(可选择重写方法),在简单工厂中添加对应的创建逻辑。不需要在每个OrderPizza类中修改代码。

3,工厂方法

  需求变动:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪 pizza、北京的胡椒 pizza 或者是伦敦的奶酪 pizza、伦敦的胡椒 pizza。

a)代码

源码:详情

//创建一个抽象的订购披萨类,根据地区不同都需要继承这个类,然后再根据口味确定最终的成品
public abstract class OrderPizza {

    abstract Pizza createPizza(String type);

    public OrderPizza() {
        do {
            String type = getOrderType();
            Pizza pizza = createPizza(type);
            if (pizza == null) {
                System.out.println("当前pizza的种类没有。");
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }

    private String getOrderType() {
        System.out.println("输入订单类型:chess,pepper");
        Scanner scanner = new Scanner(System.in);
        String next = scanner.next();
        System.out.println(next);
        return next;
    }
}
//北京pizza的订购类
public class BJOrderPizza extends OrderPizza{
    @Override
    Pizza createPizza(String type) {
        Pizza pizza = null;
        if ("chess".equals(type)) {
            pizza = new BJChessPizza();
            pizza.setName("北京chesses");
        } else if ("pepper".equals(type)) {
            pizza = new BJPepperPizza();
            pizza.setName("北京pepper");
        }
        return pizza;
    }
}
//伦敦pizza的订购类
public class LDOrderPizza extends OrderPizza{
    @Override
    Pizza createPizza(String type) {
        Pizza pizza = null;
        if ("pepper".equals(type)) {
            pizza = new LDPepperPizza();
            pizza.setName("伦敦pepper");
        } else if ("chess".equals(type)) {
            pizza = new LDChessPizza();
            pizza.setName("伦敦chesses");
        }
        return pizza;
    }
}

b)uml图

   

c)分析

  • 如果这种情况还是使用简单工厂模式,创建不同的工厂类,比如BJPizzaSimpleFactory、LDPizzaSimpleFactory等,从目前的需求分析是可以的,但是会导致有很多的工厂类,考虑到软件的可维护性、可扩展性,所以选择工厂方法模式
  • 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类

4,抽象工厂

a)基本介绍

  抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类。抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。

  从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。

  将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。

  如果产品的种类很多的话,使用抽象工厂模式的灵活性会很高;如果产品的种类不多的话,使用简单工厂模式就足够了

b)代码

源码:详情

  Pizza、BJChessPizza、BJPepperPizza、LDChessPizza、LDPepperPizza与工厂方法基本相同。

//定义抽象工厂类
public interface AbsFactory {
    public Pizza createPizza(String type);
}

//定义伦敦工厂类
public class LDFactory implements AbsFactory{
    @Override
    public Pizza createPizza(String type) {
        if ("chess".equals(type)) {
            return new LDChessPizza();
        } else if ("pepper".equals(type)) {
            return new LDPepperPizza();
        }
        return null;
    }
}

//定义北京工厂类
public class BJFactory implements AbsFactory{
    @Override
    public Pizza createPizza(String type) {
        if ("chess".equals(type)) {
            return new BJChessPizza();
        } else if ("pepper".equals(type)) {
            return new BJPepperPizza();
        }
        return null;
    }
}

//定义订购类
public class OrderPizza {
    AbsFactory absFactory;

    public OrderPizza(AbsFactory absFactory) {
        setFactory(absFactory);
    }

    private void setFactory(AbsFactory absFactory) {
        this.absFactory = absFactory;
        do {
            String orderType = getOrderType();
            Pizza pizza = absFactory.createPizza(orderType);
            if (pizza == null) {
                System.out.println("当前pizza的种类没有。");
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }

    private String getOrderType() {
        System.out.println("输入订单类型:chess,pepper");
        Scanner scanner = new Scanner(System.in);
        String next = scanner.next();
        System.out.println(next);
        return next;
    }
}

//定义client用户
public class Test {
    public static void main(String[] args) {
        new OrderPizza(new LDFactory());
    }
}

c)uml图

  

5,工厂模式在JDK中的应用

    

Calendar中createCalendar源码:
private static Calendar createCalendar(TimeZone zone,
                                           Locale aLocale){
        CalendarProvider provider =
            LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                                 .getCalendarProvider();
        if (provider != null) {
            try {
                return provider.getInstance(zone, aLocale);
            } catch (IllegalArgumentException iae) {
                // fall back to the default instantiation
            }
        }

        Calendar cal = null;

        if (aLocale.hasExtensions()) {
            String caltype = aLocale.getUnicodeLocaleType("ca");
       //根据不同的caltype类型匹配对应的类
            if (caltype != null) {
                switch (caltype) {
                case "buddhist":
                cal = new BuddhistCalendar(zone, aLocale);
                    break;
                case "japanese":
                    cal = new JapaneseImperialCalendar(zone, aLocale);
                    break;
                case "gregory":
                    cal = new GregorianCalendar(zone, aLocale);
                    break;
                }
            }
        }
        if (cal == null) {
            if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
                cal = new BuddhistCalendar(zone, aLocale);
            } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                       && aLocale.getCountry() == "JP") {
            } else {
                cal = new GregorianCalendar(zone, aLocale);
            }
        }
        return cal;
}