跟萌新一起学设计模式(三)之原型模式

**

设计模式(三)之原型模式

**

  • 案例说明

    本文以获取多个属性相同但hashCode不同的市场实体类(内含水果实体类的引用类型属性)的实例对象的例子来体现原型模式的写法和优缺点,并对深拷贝和浅拷贝进行区分,结合UML类图和代码一起说明。

  • 不用工厂模式的传统写法

  我们在创建一个市场对象之后,想要再创建一个属性相同的市场对象,但两个对象所指向的内存地址不一样,传统写法可以直接new一个对象,并把原对象的属性set到新对象中去。
在这里插入图片描述

  市场实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Market {

    private String name;

    private String address;

}

  客户端

public class Client {
    /**
     * 模拟客户端
     */
    public static void main(String[] args) {
        // 原对象
        Market market = new Market("东方市场", "东方一路");
        // 新对象
        Market market2 = new Market(market.getName(), market.getAddress());
        System.out.println("market=" + market + " market2=" + market2);
        System.out.println(market == market2);
    }
}

  测试结果显而易见,属性相同且内存地址不同,传统写法是可以满足我们的需求的。

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=54665:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.prototype.none.Client
market=Market(name=东方市场, address=东方一路) market2=Market(name=东方市场, address=东方一路)
false

Process finished with exit code 0
  • 一般的原型模式

  我们让Market市场类实现Clonable接口,并重写clone方法。
在这里插入图片描述

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Market implements Cloneable{

    private String name;

    private String address;

    @Override
    protected Object clone(){
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

  客户端直接调用clone方法即可创建新的对象。

public class Client {
    /**
     * 模拟客户端
     */
    public static void main(String[] args){
        // 原对象
        Market market = new Market("东方市场", "东方一路");
        // 新对象
        Market market2 = (Market)market.clone();
        System.out.println("market=" + market + " market2=" + market2);
        System.out.println(market == market2);
    }
}

  测试结果也满足属性相同,内存地址不同的需求。

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=54944:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.prototype.normal.Client
market=Market(name=东方市场, address=东方一路) market2=Market(name=东方市场, address=东方一路)
false

Process finished with exit code 0
  • 一般的原型模式,类中属性有引用数据类型。

  市场类中新增了水果类的属性。
在这里插入图片描述

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Market implements Cloneable{

    private String name;

    private String address;

    private Fruit fruit;

    @Override
    protected Object clone(){
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

  水果类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Fruit implements Cloneable{

    private String name;

    private String color;

    @Override
    protected Object clone(){
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

  客户端新增了水果的属性以及水果属性的内存地址比较。

public class Client {
    /**
     * 模拟客户端
     */
    public static void main(String[] args){
        // 原对象
        Market market = new Market("东方市场", "东方一路",new Fruit("苹果","红色"));
        // 新对象
        Market market2 = (Market)market.clone();
        System.out.println("market=" + market + " market2=" + market2);
        System.out.println(market == market2);
        System.out.println(market.getFruit() == market2.getFruit());
    }
}

  测试发现,市场对象内存地址不一致而水果属性的内存地址一致,说明市场对象在调用clone方法时只是浅拷贝,属性中的引用数据表类型并没有另外开辟内存空间来拷贝,只是单纯拷贝了内存地址的指向位置。

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=55072:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.prototype.normal2.Client
market=Market(name=东方市场, address=东方一路, fruit=Fruit(name=苹果, color=红色)) market2=Market(name=东方市场, address=东方一路, fruit=Fruit(name=苹果, color=红色))
false
true

Process finished with exit code 0

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

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

  • 实现了深拷贝的原型模式,类中属性有引用数据类型。

      我们将Market市场类中的clone方法加上一句,克隆引用类型Fruit即可实现。类图跟上面一样:
    在这里插入图片描述
      改动的代码如下:

@Override
    protected Object clone() throws CloneNotSupportedException {
        Market market = (Market)super.clone();
        market.fruit = (Fruit)market.fruit.clone();
        return market;
    }

  测试发现,水果实例也分配了不同的内存空间:

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=58284:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.prototype.deep.Client
market=Market(name=东方市场, address=东方一路, fruit=Fruit(name=苹果, color=红色)) market2=Market(name=东方市场, address=东方一路, fruit=Fruit(name=苹果, color=红色))
false
false

Process finished with exit code 0

  有朋友会问,如果引用类型的属性很多,那clone方法写起来也太麻烦和累赘了,那我们也可以用序列化和反序列化的方式来实现,让Market实现Serializable,添加一个deepClone方法:
在这里插入图片描述

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Market implements Cloneable,Serializable{

    private static final long serialVersionUID = 1L;

    private String name;

    private String address;

    private Fruit fruit;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Market market = (Market)super.clone();
        market.fruit = (Fruit)market.fruit.clone();
        return market;
    }

    public Market 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);
            Market market = (Market)ois.readObject();
            return market;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  客户端

public class Client {
    /**
     * 模拟客户端
     */
    public static void main(String[] args) throws CloneNotSupportedException {
        // 原对象
        Market market = new Market("东方市场", "东方一路",new Fruit("苹果","红色"));
        // 新对象
        Market market2 = (Market)market.deepClone();
        System.out.println("market=" + market + " market2=" + market2);
        System.out.println(market == market2);
        System.out.println(market.getFruit() == market2.getFruit());
    }
}

  测试可看出,序列化反序列化的方法也能实现:

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=58518:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.prototype.deep.Client
market=Market(name=东方市场, address=东方一路, fruit=Fruit(name=苹果, color=红色)) market2=Market(name=东方市场, address=东方一路, fruit=Fruit(name=苹果, color=红色))
false
false

Process finished with exit code 0
  • 总结

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值