设计模式(5)—— 创建型 —— 原型(Prototype)

版权声明:如有任何疑问,欢迎@我:[email protected] https://blog.csdn.net/qq_37206105/article/details/84112104

导航

介绍原型模式的基本特点,对象拷贝的运用 。要理解 浅度拷贝深度拷贝 的区别和使用。

原型设计模式介绍

  • 定义:指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
  • 特点:不需要知道任何创建细节,不调用构造函数
  • 类型:创建型
  • 适用场景:
    • 类初始化消耗较多资源
    • new产生的一个对象需要非常频繁的过程,例如数据准备,访问权限等。
    • 构造函数较为复杂
    • 循环体中生产大量对象时
  • 优点
    • 性能比直接new一个对象性能高
    • 简化创建过程
  • 缺点
    • 必须配备克隆方法,克隆方法是这个模式的核心。(Java提供cloneable接口表示对象是可拷贝的;必须重写Object的clone方法)
    • 对克隆复杂对象或克隆出对象进行复杂改造时,容易引入风险。
    • 深拷贝,浅拷贝要运用得恰当。

一句话来说,就是实现类的克隆,其中包含深度拷贝,浅度拷贝。

代码实现

实现代码的业务场景:及时通讯app,某个人在特定时间内发送很多消息给好友。

首先,定义简单消息类,以及发送消息的工具类

/**
 * 定义消息类。
 * 注意点:implement Cloneable ; @Override clone
 */
public class Message implements Cloneable {

    private String content;
    private String senderName;
    private String receiverName;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getSenderName() {
        return senderName;
    }

    public void setSenderName(String senderName) {
        this.senderName = senderName;
    }

    public String getReceiverName() {
        return receiverName;
    }

    public void setReceiverName(String receiverName) {
        this.receiverName = receiverName;
    }

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

/**
 * 定义发送消息类,简单的print一下。以代表相关的发送逻辑
 */
public class MessageUtil {

    public static void sendMessage(Message msg){
        System.out.println( msg.getSenderName() +
                            " is Sending a message : {" +
                            msg.getContent() +
                            "} to " +
                            msg.getReceiverName() );
    }

}

下面是测试类:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {

        Message msg = new Message();
        msg.setSenderName("Me");
        msg.setContent("默认内容");
        msg.setReceiverName("默认接收者");

        // 模拟业务场景,此时一个人在某个时间点需要发送多条消息。
        for( int i= 0 ; i < 5 ; i ++ ){
            Message clonedMsg =  (Message) msg.clone();
            clonedMsg.setContent("第" + (i+1) + "条消息");
            clonedMsg.setReceiverName("第" + (i+1) + "个人");
            MessageUtil.sendMessage(clonedMsg);
        }

    }
}

从测试代码容易看出,业务频繁要创建对象实例。并且这个实例我们已知。那么我们就利用现成的已知对象来克隆新的对象。

让克隆抽象化

// 抽象可克隆的类。
public class AbstractCloneableClass implements Cloneable {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
// 具体的实体类
public class Entity extends AbstractCloneableClass {
    
    private String content;
    private String senderName;
    private String receiverName;

    /**
     * 下面是getter和setter,我们省略。仅仅为了展示克隆抽象化的体现
     */
     
     //由于继承抽象类的关系,我们在实体类中不再需要重写clone和实现cloneable接口了
}

代码通过注释很容易理解。

浅度拷贝出现的问题

前面我们的成员变量都为String。其实对于成员变量只有基本数据类型和String类型的类,我们在重写clone函数时,直接使用super.clone()就足以解决问题。但是当我们的成员变量是一个类的时候,此时的拷贝只能拷贝我们前面所说的几种常见的数据类型。这就要求我们在重写clone函数时,进行深度拷贝。具体的代码实现如下:

现在对于一个User类,我们有它的基本信息namebirthday
按照前面的实现思路,实现下面的拷贝:

public class User implements Cloneable {
    //User类,注意到成员变量birthday的类型为Birthday自定义Class类型。
    private String name;
    private BirthDay birthday;

    public User(String name, BirthDay time){
        this.name = name;
        this.birthday = time;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BirthDay getBirthday() {
        return birthday;
    }

    public void setBirthday(BirthDay birthday) {
        this.birthday = birthday;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
/**
* 生日类。这个类在User类中作为成员变量
*/
public class BirthDay {
    private String year;
    private String month;
    private String day;

    public BirthDay(String year, String month, String day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public String getMonth() {
        return month;
    }

    public void setMonth(String month) {
        this.month = month;
    }

    public String getDay() {
        return day;
    }

    public void setDay(String day) {
        this.day = day;
    }
}

下面是测试代码用例:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        User user = new User("XiaoMing", new BirthDay( "1998", "1", "1" ) );
        User clonedUser = (User) user.clone();

        /**测试两个整体User实例,看是否是指向同一地址。运行结果:
             user hashCode@1304836502
             cloned user hashCode@225534817
             两者是否指向同一内存地址?false
         */
        System.out.println( "user hashCode@" + user.hashCode() +
                            "\ncloned user hashCode@" + clonedUser.hashCode() +
                            "\n两者是否指向同一内存地址?" + (user==clonedUser) );


        /**
         * 下面是对两个对象内部的birthday对象进行测试。下面是输出结果
             User : XiaoMing @1878246837
             Cloned User : XiaoMing @1878246837
             生日是否指向同一内存地址:true
         */
        System.out.println( "\nUser : " + user.getName() +
                            " @" + user.getBirthday().hashCode() );

        System.out.println( "Cloned User : " + clonedUser.getName() +
                            " @" + clonedUser.getBirthday().hashCode()  );

        System.out.println( "生日是否指向同一内存地址:" + ( user.getBirthday() == clonedUser.getBirthday() ) );


    }

}

从结果我们可以看到,两个User对象内部的birthday对象的hashCode相等,并且指向同一内存地址。这样的拷贝并不完全。

深度拷贝解决方案

下面是深拷贝的方法:

@Override
    protected Object clone() throws CloneNotSupportedException {

        User clonedUser = (User) super.clone();

        clonedUser.birthday = new BirthDay( clonedUser.getBirthday().getYear(),
                                            clonedUser.getBirthday().getMonth(),
                                            clonedUser.getBirthday().getDay() );
        return clonedUser;
    }

测试用例:

// 用例生成的hashCode的具体值不必在乎。
// 在乎的是比较hashCode的相等性。
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        User user = new User("XiaoMing", new BirthDay( "1998", "1", "1" ) );
        User clonedUser = (User) user.clone();

        /**测试两个整体User实例,看是否是指向同一地址。运行结果:
             user hashCode@1304836502
             cloned user hashCode@225534817
             两者是否指向同一内存地址?false
         */
        System.out.println( "user hashCode@" + user.hashCode() +
                            "\ncloned user hashCode@" + clonedUser.hashCode() +
                            "\n两者是否指向同一内存地址?" + (user==clonedUser) );


        /**
         * 下面是对两个对象内部的birthday对象进行测试。下面是输出结果
                 User : XiaoMing @1878246837
                 Cloned User : XiaoMing @929338653
                 生日是否指向同一内存地址:false
         */
        System.out.println( "\nUser : " + user.getName() +
                            " @" + user.getBirthday().hashCode() );

        System.out.println( "Cloned User : " + clonedUser.getName() +
                            " @" + clonedUser.getBirthday().hashCode()  );

        System.out.println( "生日是否指向同一内存地址:" + ( user.getBirthday() == clonedUser.getBirthday() ) );
    }
}

猜你喜欢

转载自blog.csdn.net/qq_37206105/article/details/84112104