【程序员内功】Java设计模式并不难(三)---原型模式

Prototype Pattern

1、概述

原型模式其实不难,它主要做的事情就是对象的拷贝,即使不说原型模式我们也可以想到在对象中提供一个方法创建当前对象的实例,然后将属性一个一个set进去。而原型模式其实就是将"拷贝"这个事情提取了一下,我们可以书写一个抽象原型类(抽象类和接口即可),其中包含clone()方法,然后使用不同的子类(具体原型类)去实现该接口覆盖clone方法(原型方法)即可,而clone()的实现还是我们自己书写(也可以使用java自带的方法),核心思路如下。

class ConcretePrototype implements Prototype
{
private String  attr; //成员属性
public void  setAttr(String attr)
{
    this.attr = attr;
}
public String  getAttr()
{
    return this.attr;
}
public Prototype  clone() //克隆方法
{
    Prototype  prototype = new ConcretePrototype(); //创建新对象
    prototype.setAttr(this.attr);
    return prototype;
}
}

思考:能否将上述代码中的clone()方法写成:public Prototype clone() { return this;}

答案是不能,因为这样我们返回的对象就是同一个了,也就算不上clone,可以打印原对象==新对象发现返回true。

2、Java自带clone

我们现在使用原型模式完成一个工作周报的创建,周报可能有多种类型,只需要在每种类型的周报entity中书写clone()方法即可。这里使用Java自带的方法进行实现,将Cloneable视作抽象原型类,实现Cloneable接口1

//工作周报WeeklyLog:具体原型类,考虑到代码的可读性和易理解性,只列出部分与模式相关的核心代码
class WeeklyLog implements Cloneable
{
       private  String name;
       private  String date;
       private  String content;
       public  void setName(String name) {
              this.name  = name;
       }
       public  void setDate(String date) {
              this.date  = date;
       }
       public  void setContent(String content) {
              this.content  = content;
       }
       public  String getName() {
              return  (this.name);
       }
       public  String getDate() {
              return  (this.date);
       }
       public  String getContent() {
              return  (this.content);
       }
     //克隆方法clone(),此处使用Java语言提供的克隆机制
       public WeeklyLog clone()
       {
              Object obj = null;
              try
              {
                     obj = super.clone();
                     return (WeeklyLog)obj;     
              }
              catch(CloneNotSupportedException e)
              {
                     System.out.println("不支持复制!");
                     return null;
              }
       }
}

可以使用client测试如下:

class Client
{
       public  static void main(String args[])
       {
              WeeklyLog log_previous = new WeeklyLog();  //创建原型对象
              log_previous.setName("张无忌");
              log_previous.setDate("第12周");
              log_previous.setContent("这周工作很忙,每天加班!");
              System.out.println("****周报****");
              System.out.println("周次:" +  log_previous.getDate());
              System.out.println("姓名:" +  log_previous.getName());
              System.out.println("内容:" +  log_previous.getContent());
              System.out.println("--------------------------------");
              WeeklyLog  log_new;
              log_new  = log_previous.clone(); //调用克隆方法创建克隆对象
              log_new.setDate("第13周");
              System.out.println("****周报****");
              System.out.println("周次:" + log_new.getDate());
              System.out.println("姓名:" + log_new.getName());
              System.out.println("内容:" + log_new.getContent());
       }
}

输出结果如下:

****周报****
周次:第12周
姓名:张无忌
内容:这周工作很忙,每天加班!
--------------------------------
****周报****
周次:第13周
姓名:张无忌
内容:这周工作很忙,每天加班!

思考:以下语句的输出结果是什么,为什么?

System.out.println(log_previous == log_new);
System.out.println(log_previous.getDate() == log_new.getDate());
System.out.println(log_previous.getName() == log_new.getName());
System.out.println(log_previous.getContent() == log_new.getContent());

结果如下:

false
false
true
true

1、因为返回的是不同对象(引用)所以为false(注:引用中存储的是指向对象的地址)

2、因为data被重新set了一下

3、4是因为属性拷贝后不变

3、深拷贝与浅拷贝
①浅拷贝

我们上面所说的都是浅拷贝,浅拷贝会将WeeklyLog中的属性全部clone一份到新的对象中,我们的WeeklyLog属性还比较简单,如果其中包含另一个对象会如何?我们现在在WeeklyLog中添加一个Attachment属性,是否会影响我们的拷贝结果。

//附件类
class Attachment
{
       private  String name; //附件名
       public  void setName(String name)
       {
              this.name  = name;
       }
       public  String getName()
       {
              return  this.name;
       }
     public void download()
     {
            System.out.println("下载附件,文件名为" + name);
     }
}
//工作周报WeeklyLog
class WeeklyLog implements Cloneable
{
     //为了简化设计和实现,假设一份工作周报中只有一个附件对象,实际情况中可以包含多个附件,可以通过List等集合对象来实现
       private Attachment attachment;
private String name;
       private  String date;
       private  String content;
    public void setAttachment(Attachment  attachment) {
              this.attachment = attachment;
       }
       public  void setName(String name) {
              this.name  = name;
       }
       public  void setDate(String date) {
              this.date  = date;
       }
       public  void setContent(String content) {
              this.content  = content;
       }
public Attachment  getAttachment(){
              return (this.attachment);
       }
       public  String getName() {
              return  (this.name);
       }
       public  String getDate() {
              return  (this.date);
       }
       public  String getContent() {
              return  (this.content);
       }
     //使用clone()方法实现浅克隆
       public WeeklyLog clone()
       {
              Object obj = null;
              try
              {
                     obj = super.clone();
                     return (WeeklyLog)obj;
              }
              catch(CloneNotSupportedException  e)
              {
                     System.out.println("不支持复制!");
                     return null;
              }
       }
}
class Client
{
       public  static void main(String args[])
       {
              WeeklyLog  log_previous, log_new;
              log_previous  = new WeeklyLog(); //创建原型对象
              Attachment  attachment = new Attachment(); //创建附件对象
              log_previous.setAttachment(attachment);  //将附件添加到周报中
              log_new  = log_previous.clone(); //调用克隆方法创建克隆对象
              //比较周报
              System.out.println("周报是否相同? " + (log_previous ==  log_new));
              //比较附件
              System.out.println("附件是否相同? " +  (log_previous.getAttachment() == log_new.getAttachment()));
       }
}

此时的打印结果为:

周报是否相同?  false
附件是否相同? true

可以看到浅拷贝只是将属性的值或者引用地址拷贝了出来,如果只是简单的基本类型并没有关系,如果是引用类型,而我们又需要对引用对象的属性进行操作,那么及时是拷贝出来的attachment也会影响原对象的attachment,因为他们在WeeklyLog中的值是一样的,是指向同一个attachment。

注:虽然String也是引用类型,但是一般我们不会去动其中的属性,最多就是把这个引用的地址重新赋值,所以可以认为无影响。

②深拷贝

相对于浅拷贝,深拷贝就是可以将复杂对象的属性完全clone一份,及时是引用类型可以将引用类型里的属性clone一份。那么我们该如何将深层属性clone呢,有两种方式:将对象写到流中、使用json转换。

  • IO流
    将对象写到io流中需要实现序列化接口,而且WeeklyLog和Attachment都需要实现。

    import  java.io.*;
    //附件类
    class  Attachment implements Serializable
    {
           private  String name; //附件名
           ……
    }
    
    import  java.io.*;
    //工作周报类
    class  WeeklyLog implements Serializable
    {
           private  Attachment attachment;
           private  String name;
           ……
        
       //使用序列化技术实现深克隆
           public WeeklyLog deepClone() throws  IOException, ClassNotFoundException, OptionalDataException
           {
                  //将对象写入流中
                  ByteArrayOutputStream bao=new  ByteArrayOutputStream();
                  ObjectOutputStream oos=new  ObjectOutputStream(bao);
                  oos.writeObject(this);
                  //将对象从流中取出
                  ByteArrayInputStream bis=new  ByteArrayInputStream(bao.toByteArray());
                  ObjectInputStream ois=new  ObjectInputStream(bis);
                  return  (WeeklyLog)ois.readObject();
           }
    }
    
  • 使用json转换(推荐)

    使用json转换比较简单方便,也可以达到同样的效果,而且不用实现序列化接口。

    public WeeklyLog clone() {
            String json = JSON.toJSONString(this);
            WeeklyLog parse = (WeeklyLog) JSON.parseObject(json, WeeklyLog.class);
            return parse;
        }
    
4、原型管理器

其实原型模式到这里基本就结束了,但是我们还可以学习一个"原型管理器"的概念(也就是工厂),上面我们已经说过对象的clone其实是编码在对象中的,我们已经有了对象的方法能力,但是缺少对象的创建者,如果有个工厂能够帮助我们进行对象创建使用起来想必是十分方便的。因为原型模式其实是一种模板模式,它可以返回同一对象的clone,实际业务可能存在这样的情况:我们需要快速生成一批文档,这些文档都是一样的,我们很自然的想到使用原型模式生成,我们这里可以引入一个原型管理器的概念来帮助我们管理文档,直接从管理器中即可获取到clone对象,符合"单一职责原则"。

import java.util.*;
//抽象公文接口,也可定义为抽象类,提供clone()方法的实现,将业务方法声明为抽象方法
interface OfficialDocument extends  Cloneable
{
       public  OfficialDocument clone();
       public  void display();
}
//可行性分析报告(Feasibility Analysis Report)类
class FAR implements OfficialDocument
{
       public  OfficialDocument clone()
      {
              OfficialDocument  far = null;
              try
              {
                     far  = (OfficialDocument)super.clone();
              }
              catch(CloneNotSupportedException  e)
              {
                      System.out.println("不支持复制!");
              }
              return  far;
       }
       public  void display()
       {
              System.out.println("《可行性分析报告》");
       }
}
//软件需求规格说明书(Software Requirements Specification)类
class SRS implements OfficialDocument
{
       public  OfficialDocument clone()
       {
              OfficialDocument  srs = null;
              try
              {
                     srs  = (OfficialDocument)super.clone();
              }
              catch(CloneNotSupportedException  e)
              { 
                     System.out.println("不支持复制!");
              }
              return  srs;
       }
       public  void display()
       {
              System.out.println("《软件需求规格说明书》");
       }
}
//原型管理器(使用饿汉式单例实现)
class  PrototypeManager
{
       //定义一个Hashtable,用于存储原型对象
       private Hashtable ht=new Hashtable();
       private static PrototypeManager pm =  new PrototypeManager();
       //为Hashtable增加公文对象   
     private  PrototypeManager()
     {
              ht.put("far",new  FAR());
              ht.put("srs",new  SRS());               
     }
     //增加新的公文对象
       public void addOfficialDocument(String  key,OfficialDocument doc)
       {
              ht.put(key,doc);
       }
       //通过浅克隆获取新的公文对象
       public OfficialDocument  getOfficialDocument(String key)
       {
              return  ((OfficialDocument)ht.get(key)).clone();
       }
       public static PrototypeManager  getPrototypeManager()
       {
              return pm;
       }
}
5、总结
①优点
  • 如果实例的创建比较复杂,使用原型模式复制一个可以提高创建效率。
  • 扩展性较好,由于使用了抽象原型类,所以我们完全可以使用配置文件的方式切换具体原型类,并且增加、减少具体类对代码几乎没有影响。
  • 原型模式的工厂更加简化,像工厂方法模式必须提供一个对产品对应的工厂类。
  • 可以将对象以深克隆的方式保存,以便需要时使用(如:撤销操作)。
②缺点
  • 每一个类一个clone方法,修改需要改动源代码,违反了“开闭原则”。
  • 深克隆代码较为复杂,如果对象很复杂,每一个还要去实现序列化接口。(当然使用json的方式就不是问题)
③使用场景
  • 创建新对象成本较大时,如初始化需要占用较长的时间,占用太多的CPU资源或网络资源。
  • 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。

  1. Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作为一名 Java 程序员,了解硬件层面的内存知识是非常重要的。在 NUMA 架构下,内存的访问速度会受到影响,因此了解 NUMA 架构的内存分布和访问方式,可以帮助程序员优化程序的性能。 NUMA 架构是一种多处理器架构,其中每个处理器都有自己的本地内存和一组本地 I/O 设备。这些处理器通过一个快速互联网络连接在一起,共享全局内存和 I/O 设备。 在 NUMA 架构下,内存被分割成多个本地内存区域和全局内存区域。本地内存区域是指与处理器直接相连的内存区域,访问速度最快。全局内存区域是指被所有处理器共享的内存区域,访问速度相对较慢。 程序员需要了解的是,当一个线程在处理器上执行时,它会优先访问本地内存区域,如果需要访问全局内存区域,则会通过互联网络访问,这会导致访问速度变慢。因此,在程序设计时,需要尽可能地减少线程之间的共享内存,避免出现频繁的跨处理器访问全局内存的情况。 另外,程序员还需要了解内存对齐的概念。在 NUMA 架构下,由于不同处理器的内存访问速度不同,如果内存没有对齐,则会导致不同处理器之间的数据传输速度不一致,进而影响程序的性能。因此,在程序设计时,需要注意内存对齐的问题,尽可能地将数据放在相邻的内存位置,避免出现不必要的跨处理器数据传输。 总之,了解 NUMA 架构的内存分布和访问方式是非常重要的,可以帮助程序员优化程序的性能。程序员需要注意线程之间的共享内存和内存对齐的问题,尽可能地减少跨处理器访问全局内存的情况,提高程序的执行效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值