设计模式(三)-正篇-单例模式、工厂模式

单例模式

定义

单例设计模式,一般指类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

例子

比如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)不要覆盖基类中已经实现的方法。(里氏替换原则)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值