创建型模式之原型模式与建造者模式(二)

一、原型模式

  原型模式是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式。它提供了一种创建对象的最佳方式。

  这种模式是实现一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如:当一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求返回它的克隆,在需要的时候更新数据库,以此来减少数据库的调用。

1,需求分析

  现在有一只羊,姓名:tom,年龄:1,颜色:白。请编写程序创建和tom羊属性完全相同的10只羊。

2,传统方式解决克隆羊

a)思路分析

  

b)代码

  源码:详情

public class Sheep {

    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;
    }

    //...get set方法  
    
}

public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom", 1, "白色");
        Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
    }
}

c)优缺点

  • 优点:比较好理解,简单易操作
  • 确定:在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率比较低;总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活

d)改进

  Java中Object类时所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力 => 原型模式

3,原型模式

a)基本介绍

  • 原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象(自我复制)
  • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如果创建的细节
  • 工作原理:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()

b)原理结构图

  

  原理结构图说明:

  • Proptype:原型类,声明一个克隆自己的接口
  • ConcreteProptype:具体的原型类,实现一个克隆自己的操作
  • Client:让一个原型创建对象克隆自己,从而创建一个新的对象(成员属性一样)

c)采用原型模式解决克隆羊问题

   源码:详情

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 Sheep clone() {
        try {
            return (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    //...get set
}

public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom", 1, "白色");
        Sheep sheep1 = sheep.clone();
        Sheep sheep2 = sheep.clone();
        Sheep sheep3 = sheep.clone();
        System.out.println("sheep = " + sheep);
        System.out.println("sheep1 = " + sheep1);
        System.out.println("sheep2 = " + sheep2);
        System.out.println("sheep3 = " + sheep3);
    }
}

4,原型模式在Spring框架中的源码分析

  

public class ProtoType {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取monster[通过id获取monster]
        Object bean = applicationContext.getBean("id01");
        System.out.println("bean="+bean);
        
        Object bean2 = applicationContext.getBean("id01");
        System.out.println("bean="+bean);
        
        System.out.println(bean == bean2);//false:并不是同一个对象,只是两个bean的属性相同
    }
}

5,深拷贝与浅拷贝

a)浅拷贝

  • 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
  • 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的成员变量值
  • 前面克隆羊就是浅拷贝,使用默认的clone()方法来实现:sheep=(Sheep)super.clone();

b)深拷贝

  • 复制对象的所有基本数据类型的成员变量值
  • 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
  • 深拷贝的实现方式:1.重写clone方法来实现深拷贝;2.通过对象序列化实现深拷贝(推荐)

c)深拷贝应用案例

  源码:详情

public class DeepPrototype implements Serializable,Cloneable {

    String name;
    DeepCloneTarget deepCloneTarget;

    //方案1:重写clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object deep = null;
        //完成对基本类型和String的克隆
        deep = super.clone();
        //对引用类型的属性进行单独处理
        DeepPrototype deepPrototype = (DeepPrototype) deep;
        deepPrototype.deepCloneTarget = (DeepCloneTarget) deepCloneTarget.clone();
        return deepPrototype;
    }

    //方案2:通过对昂的序列化实现(推荐)
    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);
            return (DeepPrototype)ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                ois.close();
                bis.close();
                oos.close();
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @Override
    public String toString() {
        return "DeepPrototype{" +
                "hascode=" + this.hashCode() +
                " , name='" + name + '\'' +
                ", deepCloneTarget=" + deepCloneTarget +
                '}';
    }
}

class DeepCloneTarget implements Cloneable,Serializable {
    private static final long serialVersionUID = 1L;

    private String name1;
    private String name2;

    public DeepCloneTarget(String name1, String name2) {
        this.name1 = name1;
        this.name2 = name2;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "DeepCloneTarget{" +
                "hashcode= " + this.hashCode() +
                ", name1='" + name1 + '\'' +
                ", name2='" + name2 + '\'' +
                '}';
    }
}
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        DeepPrototype p = new DeepPrototype();
        p.name = "宋江";
        p.deepCloneTarget = new DeepCloneTarget("大牛", "小牛");

        //方式 1  完成深拷贝
        DeepPrototype p1 = (DeepPrototype) p.clone();
        System.out.println("p..." +p);
        System.out.println("p1..." +p1);

        //方式 2  完成深拷贝
        DeepPrototype p2 = (DeepPrototype) p.deepClone();
        System.out.println("p2..." +p2);
    }
}

6,原型模式注意事项

  • 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  • 并不用重复初始化对象,而是动态地或得对象运行时的状态
  • 如果原始对象发生变化(增加或者减少属性),其他克隆对象也会发生相应的变化,无需修改代码
  • 在实现深克隆的时候推荐使用对象序列化
  • 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则。

 二、建造者模式

1,基本介绍

  建造者模式(Builder Pattern)又叫生成器模式,时一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。

  建造者模式是一步一步创建一个复杂的对象,它不允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

a)uml类图

   

b)角色分析

  • Product(产品角色):一个具体的产品对象
  • Builder(抽象建造者):创建一个Product对象的各个部件指定的接口/抽象类。(接口和抽象类都可以做抽象层)抽象构造者只需要指定建造一个产品的流程,并不管具体的建造细节。
  • ConcreteBuilder(具体建造者):实现接口/抽象类,构建和装配各个部件。负责具体的建造细节。
  • Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

2,案例分析

a)需求

  • 需要建房子:这一过程为打桩、砌墙、封顶
  • 房子有各种各样的,比如普通房、高楼、别墅,各种房子的过程虽然一样,但是要求使不同的

b)uml分析类图

  

c)代码实现

  源码:详情

//产品 
public class House {
    private String basic;
    private String wall;
    private String roof;
    
    ...get set

}

//抽象的构建者
public abstract class HouseBuilder {
    protected House house = new House();
    abstract void builderBasic();
    abstract void builderWall();
    abstract void roofed();
    public House build() {
        return house;
    }
}

//具体的构建者实现(省略HighBuilding..)
public class CommonHouse extends HouseBuilder {
    @Override
    public void builderBasic() {
        System.out.println("普通房子打地基");
    }
    @Override
    public void builderWall() {
        System.out.println("普通房子砌墙");
    }
    @Override
    public void roofed() {
        System.out.println("普通房子封顶");
    }
}

//指挥者,这里会去指定制作流程,返回产品
public class HouseDirector {

    HouseBuilder houseBuilder = null;

    public HouseDirector(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
    }

    public House houseBuilder() {
        houseBuilder.builderBasic();
        houseBuilder.builderWall();
        houseBuilder.roofed();
        return houseBuilder.build();
    }
}

//客户端调用者
public class Client {
    public static void main(String[] args) {
        HouseDirector houseDirector = new HouseDirector(new CommonHouse());
        House house = houseDirector.houseBuilder();
        System.out.println(house + "构建完毕");
    }
}

3,建造者模式在JDK应用(StringBuilder)

  

  • Appendable接口中定义了多个append方法(抽象方法),即Appendable为抽象制造者,定义了抽象方法
  • AbstractStringBuilder虽然只是抽象类,但是它实现了Appendable接口的方法,因此这里的AbstractStringBuilder已经是建造者,只是不能实例化(因为它是抽象类)
  • StringBuilder既充当了指挥者角色,同时也充当了具体的制造者,建造方法的实现是由AbstractStringBuilder完成,而StringBuilder继承了AbstractStringBuilder(它重写了AbstractStringBuilder的append方法)

4,注意事项

  • 客户端(使用程序)不必知道产品内部组成的细节(直接调用方法即可),将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  • 每一个具体建造者都相互独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
  • 可以更加精细地控制产品的创建过程。(在指挥者里可以自行组织建造流程)将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”(例如:实例中只需增加新的构造者OtherHouse集成HouseBuilder,Client就可以直接使用)
  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式。
  • 抽象工厂VS 建造者模式:抽象工厂模式实现对产品家族的创建:产品家族,具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关系什么产品由什么工厂生产即可。建造者模式是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而生产一个新产品。