常见7种设计模式总结(上)

目录

前言

一、单例模式(Singleton)

1.饿汉式(静态常量)

2.饿汉式(静态代码块)

3.懒汉式(线程不安全)

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

5.懒汉式(同步代码块,线程安全)

6.双重检查

7.静态内部类

8.枚举

二、工厂模式(Factory)

1.简单工厂

2.工厂方法

3.抽象工厂

三、原型模式(Prototype)

1.浅拷贝

2.深拷贝


前言

设计模式有很多,网上能搜到的教程也有很多,在学习了设计模式相关教程后,总是觉得每种设计模式都很类似,不能很好分辨出各设计模式的侧重点。在众多设计模式中,有7种设计模式在开发中应用频次较高,也非常重要,这里是对这7种设计模式进行总结,分析各设计模式的设计思想和侧重点。


一、单例模式(Singleton)

单例模式:就是采取一定措施使得整个程序中对于某个类,只存在一个对象实例。该类的所有元素和构造器对外界封闭,只提供一个静态方法来获取对象实例。

在设计单例模式时,需要考虑如何使得程序只存在一个对象实例,即当已经存在一个实例时如何避免创建新的实例,按照这一设计思想,联想到静态量、条件判断、线程安全,就可以按照不同方式实现单例模式,也能理解单例模式的八种方式。

1.饿汉式(静态常量)

为了避免重复创建新的对象实例,在类内部对声明静态常量的对象实例,在类装载时就完成了实例化,代码如下:

public class SingletonHungry1 {
    // 1.构造器私有化,外部不能new
    private SingletonHungry1() {}
    // 2.本类内部定义属性时直接创建对象实例
    private final static SingletonHungry1 instance = new SingletonHungry1();
    // 3.对外提供一个公有的静态方法,返回实例对象
    public static SingletonHungry1 getInstance() {
        return instance;
    }
}

优点

这种方式比较简单,在类装载的时候就完成了对象实例化操作,避免了线程安全问题。

缺点

静态常量的方式创建的实例对象会一直存在在内存中,如果整个程序从头到尾都未使用过这个实例,则会造成内存的浪费。

2.饿汉式(静态代码块)

可以在静态常量中实例化对象,也可以在静态代码块中实例化对象,这两种方式类似,代码如下:

public class SingletonHungry2 {
    private static SingletonHungry2 instance;
    // 1.构造器私有化,外部不能new
    private SingletonHungry2() {}
    // 2.在静态代码块中创建单例对象
    static {
        instance = new SingletonHungry2();
    }
    // 3.对外提供一个公有的静态方法,返回实例对象
    public static SingletonHungry2 getInstance() {
        return instance;
    }
}

这种方式的优缺点与静态常量一致。

3.懒汉式(线程不安全)

除静态方式,还可以通过条件判断的方式来获取对象实例,且保证只存在一种实例,代码如下:

public class SingletonLazy1 {
    private static SingletonLazy1 instance = null;
    // 1.构造器私有化,外部不能new
    private SingletonLazy1() {
    }
    // 2.提供一个静态的公有方法,当使用到该方法时,才去创建单例对象
    public SingletonLazy1 getInstance() {
        if (instance == null) {
            instance = new SingletonLazy1();
        }
        return instance;
    }
}

优点

这种方式起到了懒加载的效果,即只有当有需要对象实例时,才去创建。

缺点

在多线程下,都去获取对象实例时,在if判断语句块中,可能会产生多个实例对象,违背了只存在一个对象的原则,是线程不安全的。

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

同步方法可以避免线程安全问题,因此只要在上面的基础上将获取对象实例的方法改为同步方法,就能避免线程安全问题,代码如下:

public class SingletonLazy2 {
    private static SingletonLazy2 instance = null;
    // 1.构造器私有化,外部不能new
    private SingletonLazy2() {
    }
    // 2.提供一个静态的公有方法,当使用到该方法时,才去创建单例对象
    // 同时利用Synchronized方法实现线程安全
    public static synchronized SingletonLazy2 getInstance() {
        if (instance == null) {
            instance = new SingletonLazy2();
        }
        return instance;
    }
}

优点

解决了线程安全问题。

缺点

每个线程获取实例时,都要进行同步,即便对象已经实例化了,还要在外进行等待,这大大降低了代码的执行效率。

5.懒汉式(同步代码块,线程安全)

同步代码块也可以实现线程安全问题,代码如下:

public class SingletonLazy3 {
    private static SingletonLazy3 instance = null;
    // 1.构造器私有化,外部不能new
    private SingletonLazy3() {
    }
    // 2.提供一个静态的公有方法,当使用到该方法时,才去创建单例对象
    // 同时利用同步代码块实现线程安全
    public static SingletonLazy3 getInstance() {
        synchronized (SingletonLazy3.class) {
            if (instance == null) {
                instance = new SingletonLazy3();
            }
        }
        return instance;
    }
}

值得注意的是,上述方式同样效率很低,和同步方法的方式没有区别,而为了提高效率将同步代码块放入if条件判断中,则会存在线程安全问题。

6.双重检查

方式5的弊端就是将同步代码块放入条件判断中就存在线程安全问题,而放在条件判断外效率低,因此可以多加一层条件判断,通过双重检查来解决这些问题,代码如下:

public class SingletonDoubleCheck {
    private static SingletonDoubleCheck instance = null;
    // 1.构造器私有化,外部不能new
    private SingletonDoubleCheck() {}
    // 2.提供一个静态的公有方法,当使用到该方法时,才去创建单例对象
    // 利用同步代码块,同时通过两层判断语句实现线程安全
    public static SingletonDoubleCheck getInstance() {
        if (instance == null) {
            synchronized (SingletonDoubleCheck.class) {
                if (instance == null) {
                    instance = new SingletonDoubleCheck();
                }
            }
        }
        return instance;
    }
}

这种方法是多线程开发中经常用到的,线程安全,效率较高。

7.静态内部类

当类被装载时是线程安全的,利用这一特性可以通过静态内部类实现单例模式,当外部类装载时,内部类不会装载,当调用获取对象方法时,内部类才会装载,代码如下:

public class SingletonInnerClass {
    // 1.构造器私有化,外部不能new
    private SingletonInnerClass() {}
    // 2.写一个静态内部类,该类中有一个静态属性Singleton
    private static class SingletonInstance {
        private static final SingletonInnerClass INSTANCE = new SingletonInnerClass();
    }
    // 3.对外提供一个公有的静态方法,返回实例对象
    public static SingletonInnerClass getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

这种方式通过类转载的机制保证了线程安全,效率高,推荐使用,具体原因可以参照JVM相关内容。

8.枚举

通过JDK1.5中添加的枚举来实现单例模式,同样可以避免线程安全问题,还能防止反序列化重新创建新的对象,代码如下:

public enum SingletonEnum {
    INSTANCE;
}

二、工厂模式(Factory)

工厂模式的设计思想:将产品对象的创建过程分离到具体工厂类中,即产品类内部自己写相应的属性和方法,而创建该产品对象由具体工厂来完成。

1.简单工厂

简单工厂是针对一类产品有与之对应的一个具体工厂类,用来创建具体产品对象,代码如下:

public class SimpleFactory {
    // 创建Pizza产品具体的对象实例的方法
    private static Pizza createPizza(String orderType) {
        Pizza pizza = null;
        if ("greek".equals(orderType)) {
            pizza = new GreekPizza();
        }else if ("cheese".equals(orderType)) {
            pizza = new CheessPizza();
        }else if ("pepper".equals(orderType)) {
            pizza = new PepperPizza();
        }
        return pizza;
    }
    // 根据传进来的产品类型获取相应的产品对象实例
    public static Pizza getPizza(String orderType) {
        return createPizza(orderType);
    }
}

上述代码就是一个简单工厂,而相应的产品代码如下:

public abstract class Pizza {
    protected String name; // 名字
    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 void setName(String name) { this.name = name; }
}

public class CheesePizza extends Pizza {}
public class GreekPizza extends Pizza {}
public class PepperPizza extends Pizza {}

这里限于篇幅,只是简单介绍了产品类,为了方便工程获取具体子产品,这里定义了一个公共的抽象父类Pizza,也可以用接口来替换,效果一样。

在抽象父类或接口中可以定义公共的抽象方法,由子类或实现类根据情况具体实现。重点在于简单工厂的思想是通过工厂来创建相应产品的对象。

优点

客户端避免了直接创建产品对象的操作,由工厂来完成;客户端不需要知道具体产品的类名,只需要传入类型参数,由工厂来判断需要的产品类型,并创建对应的产品实例。

缺点

工厂类单一,负责具体某一类产品的创建工作(如Pizza抽象类的所有子类或者Pizza接口的所有实现类);当产品类过于复杂,存在几百上千个不同产品时,需要修改工厂的条件判断逻辑,容易造成逻辑混乱;当增加新的产品类型时(如增加Phone产品),需要专门针对这类产品建造新的工厂,系统扩展困难。

应用场景

当产品种类较少时,可以使用简单工厂模式。只需要传入工厂类的参数,不需要关注如何创建对象的逻辑。

2.工厂方法

工厂方法是对简单工厂的进一步抽象化,其定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化操作推迟到子类中。代码如下:

public abstract class OrderPizza {
    // 抽象方法获取Pizza对象,让各子类具体实现
    public abstract void getPizza();
    // 获取客户希望订购的Pizza种类
    public String getType() {
        try {
            BufferedReader string = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("Input pizza type: ");
            return string.readLine();
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }
}

public class BJOrderPizza extends OrderPizza {
    Pizza pizza = null;
    @Override
    public void getPizza() {
        do {
            String orderType = getType();
            if ("cheess".equals(orderType)) {
                this.pizza = new BJCheessPizza();
            }else if ("pepper".equals(orderType)) {
                this.pizza = new BJPepperPizza();
            }
            if (orderType != null) {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else {
                break;
            }
        }while (true);
    }
}

public class LDOrderPizza extends OrderPizza {
    Pizza pizza = null;
    @Override
    public void getPizza() {
        do {
            String orderType = getType();
            if ("cheess".equals(orderType)) {
                this.pizza = new LDCheessPizza();
            }else if ("pepper".equals(orderType)) {
                this.pizza = new LDPepperPizza();
            }
            if (orderType != null) {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else {
                break;
            }
        }while (true);
    }
}

3.抽象工厂

抽象工厂定义了一个借口用于创建相关的或者无关的其他产品对象,相当于简单工厂和工厂方法的组合。抽象工厂分为两层,通用的AbsFactory借口用来定义获取产品对象的通用操作,具体的实现子类用来获取某一类产品的具体实例。代码如下:

// 通用的抽象工厂借口
public interface ABSFactory {
    public Pizza getPizza(String orderType);
}
// 具体实现子工厂,实现对北京Pizza产品对象的创建
public class BJFactory implements ABSFactory {
    @Override
    public Pizza getPizza(String orderType) {
        Pizza pizza = null;
        if ("cheese".equals(orderType.toLowerCase())) {
            pizza = new BJCheessPizza();
        }else if ("pepper".equals(orderType.toLowerCase())) {
            pizza = new BJPepperPizza();
        }
        return pizza;
    }
}
// 具体实现子工厂,实现对伦敦Pizza产品对象的创建
public class LDFactory implements ABSFactory {
    @Override
    public Pizza getPizza(String orderType) {
        Pizza pizza = null;
        if ("cheese".equals(orderType.toLowerCase())) {
            pizza = new LDCheessPizza();
        }else if ("pepper".equals(orderType.toLowerCase())) {
            pizza = new LDPepperPizza();
        }
        return pizza;
    }
}
// 点Pizza类,通过某个工厂获取相应的产品对象实例
public class OrderPizza {
    ABSFactory factory;

    public OrderPizza(ABSFactory factory) { this.factory = factory; }
    public void setFactory(ABSFactory factory) { this.factory = factory; }

    public void getPizza() { getPizzaByFactory(); }
    // 通过工厂获取产品实例
    private void getPizzaByFactory() {
        Pizza pizza = null;
        do {
            pizza = factory.getPizza(getType());
            if (pizza != null) {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else {
                break;
            }
        }while (true);
    }
    // 传入产品类型参数
    private String getType() {
        try {
            BufferedReader string = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("Input pizza type: ");
            return string.readLine();
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }
}

 抽象工厂增强了程序的可拓展性,当新增一个产品簇时,不需要修改原代码,只需要增加相应的产品类和创建该产品对象的工厂即可。

三、原型模式(Prototype)

原型模式设计思想:用一个已经创建好的实例作为原型,通过复制该原型对象来创建一个和原型相同的新对象。根据拷贝方式的不同,原型模式分为浅拷贝和深拷贝。

1.浅拷贝

通俗来讲,浅拷贝就是拷贝地址,即拷贝对象的成员变量是某个数组、某个类的对象时,新得到的复制对象的这些成员变量只是获得了相同的地址,指向内存中一个同一个成员变量。代码如下:

public class Sheep implements Cloneable {
    private String name;
    private int age;
    private String color;
    // 构造器
    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }
    // 浅拷贝,对于属性是基本数据类型或字符串,进行值传递;对于属性是引用数据类型,进行地址传递
    @Override
    protected Object clone() {
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return sheep;
    }
}

通过实现Cloneable接口,使用默认的clone()方法来实现浅拷贝。

2.深拷贝

深拷贝是对拷贝对象的所有成员变量进行拷贝,对于数组、类这些引用数据类型进行复制,创建新的对象,而非拷贝地址。代码如下:

// 用于测试深拷贝的类对象
public class DeepCloneableTarget implements Serializable, Cloneable {

    private static final long serialVersionUID = 1L;
    private String cloneName;
    private String cloneClass;
    public DeepCloneableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
// 拷贝原型
public class DeepPrototype implements Serializable, Cloneable {
    public String name;
    public DeepCloneableTarget deepCloneableTarget;
    public DeepPrototype() {}
    // 构造器
    public DeepPrototype(String name, DeepCloneableTarget deepCloneableTarget) {
        this.name = name;
        this.deepCloneableTarget = deepCloneableTarget;
    }
    // 深拷贝:通过对象的序列化实现(推荐),
    // 本质上是利用内存二进制流的复制,性能上比new一个对象更好。
    public Object deepClone() {
        // 创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        try {
            // 序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this); // 当前这个对象以对象流的方式输出
            // 反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            Object object = ois.readObject();
            return object;
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            // 资源关闭,限于篇幅,这里简写了
            ois.close();
            bis.close();
            oos.close();
            bos.close();
        }
        return null;
    }
}

优点:

利用原型模式拷贝对象时,不需要知道对象的内部细节,有哪些成员变量,可以直接将原有对象复制一份,简化了创建对象的过程。

缺点:

原型模式中拷贝对象的类需要实现Cloneable接口,配置一个clone方法,而如果采用深拷贝的方式,其成员变量中的类或数组成员变量,同样需要实现Cloneable接口,配置相应的clone方法,违反了开闭原则。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值