前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第22次文章:建造者模式+原型模式

第22次文章:建造者模式+原型模式

作者头像
鹏-程-万-里
发布2019-09-28 18:01:04
3940
发布2019-09-28 18:01:04
举报
文章被收录于专栏:Java小白成长之路

这周我们就可以把GOFO23设计模式中的创建型模式全部介绍完了!后面在项目里面可以试一下啦!

一、建造者模式

1、场景

(1)我们需要建造一个复杂的产品。比如:神州飞船,iPhone。这个复杂的产品的创建。有这样一个问题需要处理:装配这些子组件是不是有个步骤问题?

(2)实际开发中,我们所需要的对象构建有时也非常复杂,有许多步骤需要处理时。

2、本质

(1)分离了对象子组件的单独构造(由Builder来负责)和装配(由director负责)。从而可以构造出复杂的对象。这个模式适用于:某个对象的构造过程复杂的情况下使用。

(2)由于实现了构建和装配的解耦。不同的构造器,相同的装配,可以做出不同的对象;相同的构造器,不同的装配顺序也可以做出不同的对象,也就是实现了构建算法、装配算法的解耦,实现了更好的复用。

3、建造者模式实例化

(1)背景

现在我们假设一个背景,我们需要建造一个一艘飞船,里面需要用到3个零部件,分别是发动机,轨道舱和逃逸塔。首先我们需要构造一个飞船类,以及相应的发动机,轨道舱和逃逸塔类。对于一艘飞船,我们需要3个零部件。主要的实现思路如下:

代码语言:javascript
复制
public class AirShip {  private Engine engine;//发动机  private OrbitalModule orbitalModule;//轨道舱  private EscapeTower escapeTower;//逃逸塔    public Engine getEngine() {    return engine;  }  public void setEngine(Engine engine) {    this.engine = engine;  }  public OrbitalModule getOrbitalModule() {    return orbitalModule;  }  public void setOrbitalModule(OrbitalModule orbitalModule) {    this.orbitalModule = orbitalModule;  }  public EscapeTower getEscapeTower() {    return escapeTower;  }  public void setEscapeTower(EscapeTower escapeTower) {    this.escapeTower = escapeTower;  }}
class Engine{  private String name;  public Engine(String name) {    super();    this.name = name;  }  public String getName() {    return name;  }  public void setName(String name) {    this.name = name;  }}
class OrbitalModule{  ......}class EscapeTower{    ......}

tips:OrbitalModule和EscapeTower类中实现的代码与Engine中的代码类似,所以在上面的代码块中省略。

(2)builder和director的构建

在建造者模式中,最核心的部分就是将零部件的创造和组装进行分离,使得整个创建飞船的每一个步骤都相对简洁。所以我们根据建造飞船的流程,分别构建两个接口AirShipBuilder(负责零部件的构建)和AirShipDirector(负责零部件的组装)。

代码语言:javascript
复制
public interface AirShipBuilder {  OrbitalModule builderOrbitalModule();  Engine builderEngine();  EscapeTower builderEscapeTower();}
代码语言:javascript
复制
public interface AirShipDirector {  AirShip directAirShip();}

(3)实体化构建

经过上述的基本准备,已经将一个飞船类的全部流程节点规划好了,接下来我们通过上述流程,实现一个具体的飞船对象PengAirShip,依次构建PengAirShipBuilder和PengAirShipDirector。

代码语言:javascript
复制
public class PengAirShipBuilder implements AirShipBuilder{    @Override  public OrbitalModule builderOrbitalModule() {    System.out.println("建造轨道舱");    return new OrbitalModule("pengAirShip轨道舱");  }
  @Override  public Engine builderEngine() {    System.out.println("建造发动机");    return new Engine("pengAirShip发动机");  }
  @Override  public EscapeTower builderEscapeTower() {    System.out.println("建造逃逸塔");    return new EscapeTower("pengAirShip逃逸塔");  }}
代码语言:javascript
复制
public class PengAirShipDirector implements AirShipDirector {  private PengAirShipBuilder builder;  public PengAirShipDirector(PengAirShipBuilder builder) {    super();    this.builder = builder;  }      @Override  public AirShip directAirShip() {    AirShip airShip = new AirShip();    airShip.setEngine(builder.builderEngine());    airShip.setEscapeTower(builder.builderEscapeTower());    airShip.setOrbitalModule(builder.builderOrbitalModule());    return airShip;  }}

(4)完成构建

最后我们通过一个简单的测试,来实现PengAirShip构建的全部操作。

代码语言:javascript
复制
public class Client {  public static void main(String[] args) {    PengAirShipDirector a = new PengAirShipDirector(new PengAirShipBuilder());    AirShip airship = a.directAirShip();    System.out.println(airship.getEngine().getName());    System.out.println(airship.getEscapeTower().getName());    System.out.println(airship.getOrbitalModule().getName());  }}

测试结果:

tips:我们可以在结果中看出建造的过程信息,以及最后的各个零部件的信息。

4、总结

上周我们一起聊了聊工厂模式,我们现在回过来将两者进行一个对比。会感到两者甚至有点相似,都是将在满足客户端要求的前提下,尽可能的通过构建一系列的流程方法,简化客户端的操作步骤。在工厂模式中,我们通过构建一系列的工厂来实现生产一个实体类对象,相当于一个工厂同时兼任了零部件的构造以及零部件的组装两个工作。而建造者模式的优点就在于将构建和组装进行分离,互相解耦。建造者模式的核心思想在于将工厂的作用再次进行细化。一个工厂仅仅负责一个职能,使得对每个工厂的管理更加方便。相互的关联也更少。

二、原型模式prototype

1、场景

通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

2、原型模式

(1)就是java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点。

(2)优势:效率高(直接克隆,避免了重新执行构造过程步骤)。

(3)克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的对象的属性值完全和原型对象相同。并且克隆出的新对象改变不会影响原型对象。然后,再修改克隆对象的值。

3、原型模式实现

在原型模式中,我们需要在需要实现克隆的类上实现Cloneable接口,并且重写clone方法。所以我们首先构建一个Sheep类。

代码语言:javascript
复制
public class Sheep implements Cloneable{  private String name;  private Date birthday;    @Override  protected Object clone() throws CloneNotSupportedException {    return super.clone();  }
  public Sheep(String name, Date birthday) {    super();    this.name = name;    this.birthday = birthday;  }.....}

tips:省略号的地方是两个属性name 和 birthday的set与get方法。需要注意的是,方法clone()属于类Object的方法,而不是Cloneable接口中的方法。我们打开Cloneable接口的源码可以发现,此接口是一个空接口,里面没有任何方法。这也就代表着Cloneable接口仅仅属于一个标识符。

(1)浅复制

使用clone方法的时候,在JVM的内部,使用的并不是值的复制,而是将原生对象的属性地址传递给了新对象。稍微思考,这样的机制就会产生一个问题:假如我们将一个原生对象属性值的地址传递给一个新对象,然后该属性的值假如有所变化,那么紧接着,新对象该属性的值也会跟着变化,这样就无法和原对象的值保持一致了。这就是所谓的浅复制,我们结合下面的例子来说明。

代码语言:javascript
复制
public class Client {  public static void main(String[] args) throws CloneNotSupportedException {        Date date = new Date(351454652132654312L);    Sheep s1 = new Sheep("多利",date);    Sheep s2 = (Sheep) s1.clone();        System.out.println(s1);    System.out.println(s1.getName());    System.out.println(s1.getBirthday());
    date.setTime(546565454654635L);        System.out.println("多利羊的新出生日期:"+s1.getBirthday());    System.out.println("\n"+"###########################################"+"\n");    System.out.println(s2);    System.out.println(s2.getName());    System.out.println(s2.getBirthday());    System.out.println("\n"+"可以修改s2中的值");    s2.setName("邵莉");    System.out.println(s2.getName());  }}

我们查看一下结果:

tips:

1.首先我们看一下两个红色方框,我们在程序中打印出了两个类s1和s2,s2是使用clone()方法得到新对象。可以在红色方框中可以看出,两个对象s1和s2属于两个不同的对象,由此证明,clone()方法产生的是新对象,而不是对原对象的引用。

2.我们关注一下蓝色方框中的日期,在程序的最开始,我们随机设置了一个日期date,打印出来的日期为蓝色方框中的日期。并将其设置为原对象s1的birthday属性值。在完成克隆操作之后,我们对date值进行更改,可以发现,s2的birthday也同时改变为新的date值,与s1对象最开始的birthday值不同(橘色方框)。这种结果的原因就是克隆方法的基本原理是基于地址的复制,而并非真实值的复制。所以这就是浅复制。

(2)深复制

面对上面的浅复制,在很多情况下是不满足我们的需求的,既然是克隆,那么我们的需求应该是克隆出的对象,与原对象的所有值都是相同的,而不会随着外界属性值的改变而有所改变,这样才更加符合我们的需求。面对这样需求,我们需要在源头上进行克服。主要是在clone()方法的内部,对birthday进行再次克隆。所以我们在Sheep的基础上加以修改clone()方法,对Sheep的birthday属性进行克隆,由此就可以解决浅克隆问题。修改如下:

代码语言:javascript
复制
public class Sheep2 implements Cloneable,Serializable{  private String name;  private Date birthday;    @Override  protected Object clone() throws CloneNotSupportedException {    Object obj = super.clone();    Sheep2 s = (Sheep2) obj;    s.birthday = (Date) this.birthday.clone();//把属性也进行克隆    return s;  }    ......}

tips:Sheep2与Sheep的主要区别就在于clone()方法的修改,从Sheep仅仅克隆对象,到Sheep2中克隆属性,从而避免了浅克隆问题。

然后我们继续使用上面测试代码,检查一下修正过后的Sheep2类型。结果如下:

tips:我们观察上图中的红色方框,第一个红色方框表示s1最开始的birthday日期,s2表示date经过修改过后,s2的birthday日期。橘色方框表示birthday修改后的日期。可以发现,虽然date经过修改,但是s2的birthday并没有改变。这就是由于在date修改之前,s2克隆了s1中birthday的属性值,此时s2的birthday属性值就不再依赖于date中的值了。这就是深克隆的思想。

4、使用序列化和反序列化技术实现克隆

在前面的多次文章中,我们提到过序列化和反序列化的内容。就属于将二进制值的输入与输出。通过这种机制,我们也可以完成一个对象的克隆。具体的实现方案如下;

代码语言:javascript
复制
public class Client3 {  public static void main(String[] args) throws Exception {        Date date = new Date(351454652132654312L);    Sheep2 s1 = new Sheep2("多利",date);        System.out.println(s1);    System.out.println(s1.getName());    System.out.println(s1.getBirthday());    //    利用序列化和反序列化进行深复制    ByteArrayOutputStream bos = new ByteArrayOutputStream();    ObjectOutputStream  oos = new ObjectOutputStream(bos);        oos.writeObject(s1);    byte[] bytes = bos.toByteArray();        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);    ObjectInputStream ois = new ObjectInputStream(bis);    Sheep2 s2 = (Sheep2) ois.readObject();//通过序列化把s1的属性值都进行复制
//    重新设置时间    date.setTime(5465654546635L);    System.out.println("修改后的日期:"+s1.getBirthday());        System.out.println("\n"+"###########################################"+"\n");
    System.out.println(s2);    System.out.println(s2.getName());    System.out.println(s2.getBirthday());
    System.out.println("\n"+"可以修改s2中的值");    s2.setName("邵莉");    System.out.println(s2.getName());  }}

tips:序列化与反序列化实现的克隆,属于深克隆,所得结果与上面的深克隆结果一致,此处不再粘贴结果。仅仅将实现方法粘贴出来,作为一种对于克隆模式的思考和拓展。

5、原型模式中几个注意点

(1)原型模式产生新对象的方法的效率大大高于new方法产生新对象的效率,所以当需要产生大量类似对象时,优先考虑原型模式。

(2)原型模式主要是用来产生新对象,克隆之后得到的新对象,只是获得了原始对象全部特性,我们依旧可以根据自己的需求更改新对象的一些属性值。

(3)原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。

三、创建型模式的总结

创建型模式:都是用来帮助我们创建对象的

1、单例模式

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

2、工厂模式

(1)简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)

(2)工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)

(3)抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)

3、建造者模式

分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。从而可以构造出复杂的对象。

4、原型模式

通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java小白成长之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、建造者模式
    • 1、场景
      • 2、本质
        • 3、建造者模式实例化
          • 4、总结
          • 二、原型模式prototype
            • 1、场景
              • 2、原型模式
                • 3、原型模式实现
                  • 4、使用序列化和反序列化技术实现克隆
                    • 5、原型模式中几个注意点
                    • 三、创建型模式的总结
                      • 1、单例模式
                        • 2、工厂模式
                          • 3、建造者模式
                            • 4、原型模式
                            相关产品与服务
                            文件存储
                            文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档