软件开发设计模式之一(创建型)

在武侠世界里,什么样的招式最厉害,通常都会说无招胜有招,手中无剑,心中也无剑,设计模式就是一把剑,极致的境界就是心中无设计模式,代码亦无设计模式—设计模式随处可见,俯拾皆是,已经融入软件设计的灵魂中,这才是高手中的高手,简称高高手。

引言

工作多年,深感还是不能融会贯通地使用这些模式,只会常用的几个招式,这使得我不得不回头再学习和整理。作为一位技术人员,知识点不懂并不丢人,没有关系,去学,学无止境(学习、学习、再学习)。

模式设计分类

设计模式的原则就是要让写出来的程序具有这几个特点:可复用性高、可扩展性强、模块之间独立。经过先辈们的努力和奋斗,积累了23个设计模式,但是它们各具特色,每个模式都为某一个可重复的设计问题提供了一套解决方案。根据它们的用途,设计模式可分为创建型(Creational),结构型(Structural)和行为型(Behavioral)三种:

  • 其中创建型模式主要用于描述如何创建对象;
  • 结构型模式主要用于描述如何实现类或对象的组合;
  • 行为型模式主要用于描述类或对象怎样交互以及怎样分配职责。

因此,可将23个模式归类如下:

  • 创建型模式(共五种):单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
  • 结构型模式(共七种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  • 行为型模式(共十一种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

创建型模式

创建型模式抽象了实例化过程。它们帮助一个系统独立于如何创建、组合和表示它的那些对象。一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象。

它们都将关于该系统使用哪些具体的类的信息封装起来;
它们隐藏了这些类的实例是如何被创建和放在一起的。整个系统关于这些对象所知道方法的是由抽象类所定义的接口。

单例模式(Singleton)

单例对象(Singleton)是一种常用的设计模式。在APP应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:

  • 某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
  • 省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
  • 有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

单例懒汉模式:

public class Singleton{
    private static Singleton mInstance;
    private Singleton(){};

    //// 添加 synchronized 关键字,即 getInstance() 是一个同步方法
    public static synchronized Singleton getInstance(){
        if(mInstance === null){
            mInstance = new Singleton();
        }else{
            return mInstance;
        }
    }
}

声明一静态对象,调用 getInstance() 方法初始化 ( 用时才初始化,既惰性处理机制 )。

静态内部类单例模式:

public class Singleton {
    private Singleton() {}
    public static Singleton getInstance() {
        return SingletonHolder.sInstance;
    }
    private static class SingletonHolder { // 静态内部类
        private static final Singleton sInstance = new Singleton();
    }
}

第一次加载 Singleton 的 getInstance() 方法才会使 sInstance 被初始化。因此,第一次调用 getInstance() 方法会导致虚拟机加载 SingletonHolder 类.

不管以哪种形式实现单例模式,它们的核心原理都是将 构造函数私有化 ,并通过 静态方法获取一个唯一的实例 。获取实例的过程须保证线程安全,防止反序列化导致重新生成实例对象等。
选择哪种实现形式取决项目本身,如是否是复杂的并发环境、JDK 版本是否过低、单例对象的资源消耗等。

单例模式的优缺点
优点

  • 只生成一个实例,减少系统的性能开销;
  • 当一对象的产生需要较多资源时,如读取配置、产生其他依赖对象时,可通过应用启动时直接产生一个单例对象,永久驻留内存。

缺点

  • 单例模式一般没有接口,扩展性难;
  • 单例对象若持有 Context,那么很容易引发内存泄漏,此时需注意传递给单例对象的 Context 应该是 Application.Context 。

工厂方法模式(Factory Method)

创建一个工厂接口和创建多个工厂实现类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。

public abstract class Product {
    public abstract void method();
}
public class ConcreteProductA extends Product { // 具体产品 A
    @Override
    public void method() {
        System.out.println("我是具体的产品A.");
    }
}
public class ConcreteProductB extends Product { // 具体产品 B
    @Override
    public void method() {
        System.out.println("我是具体的产品B.");
    }
}

public abstract class Factory { // 抽象工厂类
    /*
     * @return 具体的产品对象
     */
     public abstract Product createProduct();
}

public class ConcreteFactory extends Factory {
    @Override
    public Product createProduct() {
        // 返回具体产品 A 或者具体产品 B
        // return new ConcreteProductA();
        // return new ConcreteProductB();
    }
}

// 客户端实现
public class Client {
    public static void main(String[] args) {
        Factory factory = new ConcreteFactory();
        Product p = factory.createProduct();
        p.method();
    }
}

其实这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Product接口,同时做一个工厂类,实现Factory接口,就可以了,无需去改动现成的代码。这样做,拓展性较好!

抽象工厂模式(Abstract Factory)

工厂方法模式和抽象工厂模式不好分清楚,他们的区别如下:

工厂方法模式:

  • 一个抽象产品类,可以派生出多个具体产品类。
  • 一个抽象工厂类,可以派生出多个具体工厂类。
  • 每个具体工厂类只能创建一个具体产品类的实例。

抽象工厂模式:

  • 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
  • 一个抽象工厂类,可以派生出多个具体工厂类。
  • 每个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。

区别:

  • 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
  • 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。

抽象工厂就像工厂,而工厂方法则像是工厂的一种产品生产线。

建造者模式(Builder)

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
适用:当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时

class Person{
    private String name;
    private String address;
    private int age;
    private int sex;
    private int height;
    private int weight;
    public void setName(String name) {this.name = name;}
    public String getName() {return name;}
    public void setAddress(String address) {this.address = address;}
    public String getAddress() {return address;}
    public void setAge(int age) {this.age = age;}
    public int getAge() {return age;}
    public void setSex(int sex) {this.sex = sex;}
    public int getSex() {return sex;}
    public void setHeight(int height) {this.height = height;}
    public int getHeight() {return height;}
    public void setWeight(int weight) {this.weight = weight;}
    public int getWeight() {return weight;}
}

class PersonBuilder{
    private Person person;
    public PersonBuilder(){
        this.person = new Person();
    }
    public PersonBuilder name(String name){
        this.person.setName(name);
        return this;
    }
    public PersonBuilder address(String address){
        this.person.setAddress(address);
        return this;
    }
    public PersonBuilder age(int age){
        this.person.setAge(age);
        return this;
    }
    public PersonBuilder sex(int sex){
        this.person.setSex(sex);
        return this;
    }
    public PersonBuilder height(int height){
        this.person.setHeight(height);
        return this;
    }
    public PersonBuilder weight(int weight){
        this.person.setWeight(weight);
        return this;
    }
}

public class TestBuilder {
    public test(){
        PersonBuilder builder = new PersonBuilder();
        Person person = builder.name("lion")
                .address("america")
                .age(18)
                .sex(2)
                .height(180)
                .weight(150);
    }
}

原型模式(Prototype)

原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

适用于:

  • 当一个系统应该独立于它的产品创建、构成和表示时;
  • 当要实例化的类是在运行时刻指定时,例如,通过动态装载;
  • 为了避免创建一个与产品类层次平行的工厂类层次时;
  • 当一个类的实例只能有几个不同状态组合中的一种时。
class Car implements Cloneable{
    private int id;
    public int getId() {return id;}
    public void setId(int id) {this.id = id;}

    public Car clone(){
        try {
            return (Car)super.clone();
        }catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

class Prototype implements Cloneable{
    private int id;
    private Car car;
    public Car getCar() {return car;}
    public void setCar(Car car) {this.car = car;}
    public int getId() {return id;}
    public void setId(int id) {this.id = id;}
    public Object clone(){
        try {
            boolean deep = true;
            if (deep){
                /**
                 * 深复制,复制出了两辆车
                 * */
                Prototype prototype = (Prototype)super.clone();
                prototype.setCar((Car)this.car.clone());
                // 继续复制其他引用对象
                return prototype;

            }else{
                /**
                 * 浅复制 ,是同一辆车
                 * */
                return super.clone();
            }
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

public class TestPrototype {
    public void test(){
        Prototype p1 = new Prototype();
        p1.setCar(new Car());
        p1.setId(1);
        // 复制
        Prototype p2 = (Prototype)p1.clone();
        p2.setId(2);
    }
} 

复制的概念:

  • 浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
  • 深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。

参考书箱:

  • 《设计模式之禅》