23种设计模式4_原型模式

23种设计模式4_原型模式

1 基本介绍

原型模式(Prototype模式):用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。该模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。

原理:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝他们自己来实施创建,即对象.clone()

2 克隆羊问题

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

3 传统方式实现

// 创建羊的类
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;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}
public class Client {

    public static void main(String[] args) {
        // 传统方式
        Sheep tomSheep = new Sheep("tom", 1, "白色");
        // 演示先创建5只羊
        System.out.println(new Sheep(tomSheep.getName(), tomSheep.getAge(), tomSheep.getColor()));
        System.out.println(new Sheep(tomSheep.getName(), tomSheep.getAge(), tomSheep.getColor()));
        System.out.println(new Sheep(tomSheep.getName(), tomSheep.getAge(), tomSheep.getColor()));
        System.out.println(new Sheep(tomSheep.getName(), tomSheep.getAge(), tomSheep.getColor()));
        System.out.println(new Sheep(tomSheep.getName(), tomSheep.getAge(), tomSheep.getColor()));
    }

}

分析:

①优点好理解,简单易操作

②在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低

③总是需要重新初始化对象,而不是动态地获得对象运行时状态,不够灵活

改进思路:java种Object类是所有类的跟类,Object类提供了一个clone()方法,该方法可以将一个java对象复制一份,但是需要实现clone的java类必须要实现接口Cloneable,该接口表示该类能够复制且有复制的能力,这就引申出原型模式。

4 通过原型模式实现

实现步骤:

①Prototype:声明一个原型类,即一个克隆自己的接口。

②ConcretePrototype:创建一个具体的圆形类,实现一个克隆自己的操作。

③CLient:让一个原型对象克隆自己,从而创建一个新的对象(属性)。

首先先对羊这个类进行下修改:

public class Sheep implements Cloneable {

    private String name;
    private int age;
    private String color;

    // 构造器不变,省略。。。
    // get/set方法不变,省略。。。
    // toString()也不变,省略。。。

    // 克隆该实例,使用默认的克隆方法来完成
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return (Sheep) super.clone();
    }

}
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        Sheep tomSheep = new Sheep("tom", 1, "白色");
        // 通过原型模式演示先创建5只羊
        System.out.println("sheep1 = " + tomSheep.clone());
        System.out.println("sheep2 = " + tomSheep.clone());
        System.out.println("sheep3 = " + tomSheep.clone());
        System.out.println("sheep4 = " + tomSheep.clone());
        System.out.println("sheep5 = " + tomSheep.clone());
    }

}

演示结果如下:

在这里插入图片描述

5 实际应用场景演示

案例需求:现在需要给很多用户发送邮件,邮件格式:“标题:XXX公司春节抽奖活动 活动内容:1234567890(纯演示用了) 收件人:{邮件地址} …发送成功!”

// 邮件模板
public class MailTemplate {

    // 标题
    private String mailSubject = "标题:XXX公司春节抽奖活动";

    // 邮件内容
    private String mailContext = "1234567890";

    public String getMailSubject() {
        return this.mailSubject;
    }

    public String getMailContext() {
        return this.mailContext;
    }

}
// 邮件类
public class Mail {

    // 收件人
    private String receiver;

    // 主题
    private String mailSubject;

    // 邮件内容
    private String mailContext;

    public Mail(MailTemplate mailTemplate) {
        this.mailSubject = mailTemplate.getMailSubject();
        this.mailContext = mailTemplate.getMailContext();
    }

    public String getReceiver() {
        return receiver;
    }

    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }

    public String getMailSubject() {
        return mailSubject;
    }

    public void setMailSubject(String mailSubject) {
        this.mailSubject = mailSubject;
    }

    public String getMailContext() {
        return mailContext;
    }

    public void setMailContext(String mailContext) {
        this.mailContext = mailContext;
    }
}
public class Client {

    public static void main(String[] args) {
        // 这里定义假设发给10个人
        int total = 10;
        int count = 0;

        MailTemplate mailTemplate = new MailTemplate();
        Mail mail = new Mail(mailTemplate);
        while (count < total) {
            mail.setReceiver(generateReceiver());
            sendMail(mail);
            count++;
        }
    }

    public static void sendMail(Mail mail) {
        System.out.println(mail.getMailSubject() + "\t收件人:" + mail.getReceiver() + "\t活动内容:" + mail.getMailContext() + "\t" + "...发送成功!");
    }

    public static String randomString(int len) {
        String source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i=0; i<len; i++) {
            sb.append(source.charAt(random.nextInt(source.length())));
        }
        return sb.toString();
    }

    public static String generateReceiver() {
        return randomString(8) + "@" + randomString(3) + ".com";
    }

}

在这里插入图片描述

从上述案例可以看到实现是很简单的,但是有一个问题,我们的这个是单线程的,那么就需要等待上一封邮件发送完毕才能发送下一封邮件,如果邮件数量够多,是不是非常的耗时。那么改成多线程的呢,多线程效率是提高了,但是如果前一封邮件还没有发出去,另一个线程的邮件又进来了,那不是出现问题了。这种情况下我们就可以通过原型模式来进行修改,如下:

// 在mail类实现Cloneable接口,并重写clone()方法
@Override
protected Mail clone() throws CloneNotSupportedException {
    return (Mail) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
    // 这里定义假设发给10个人
    int total = 10;
    int count = 0;

    Mail mail = new Mail(new MailTemplate());
    while (count < total) {
        Mail cloneMail = mail.clone();
        cloneMail.setReceiver(generateReceiver());
        sendMail(cloneMail);
        count++;
    }
}

实现效果是一样的。

分析:

性能优良 原型模式实在内存二进制流中进行拷贝,要比直接new对象性能好很多,特别是在循环体内需要用到大量的对象时,原型模式可以更好的体现优先。

逃避构造函数的约束 这一点既是优点也是缺点,直接在内存中进行拷贝,构造函数是不会执行的,所以需要根据实际试用场景选择使用。

6 使用场景和注意事项

使用场景

资源优化 类初始化过程中需要消耗非常多的资源,包括数据、硬件资源等。

性能和安全要求的场景 通过new方式创建对象需要准备繁琐的数据或者存在访问权限时,可以使用原型模式

一个对象多个修改者的场景 对象提供给其他对象使用时,每个调用者都需要设置不同的属性,可以考虑该模式

注意事项

①clone()方法的原理时从内存中以二进制流的方式进行拷贝,重新分配一块内存区域,是不会执行构造函数的。

②对象的clone与对象内的final关键字是有冲突的。要使用clone方法,就不要在类的成员变量上增加final关键字。

7 深拷贝和浅拷贝

先看一个案例:

public class Thing implements Cloneable {

    private ArrayList<String> list = new ArrayList<>();

    @Override
    public Thing clone() throws CloneNotSupportedException {
        return (Thing) super.clone();
    }

    public void setValue(String str) {
        this.list.add(str);
    }

    public ArrayList<String> getValue() {
        return this.list;
    }

}
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建一个对象,并设置一个值
        Thing thing = new Thing();
        thing.setValue("zhangsan");

        // 拷贝对象
        Thing cloneThing = thing.clone();
        cloneThing.setValue("lisi");

        System.out.println(thing.getValue());
    }
}

运行结果如下:

在这里插入图片描述

思考一下,怎么会有lisi呢?是因为java做了一个偷懒的拷贝动作,Object类提供的clone方法只拷贝对象本身,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝。两个对象公用一个私有变量,你变我也变,非常不安全。那么为什么上面演示的Mail案例中没有问题呢?因为对于基础数据类型,比如int,long,char等都会被拷贝的,String类型比较特殊,它是存储在字符串池中的,当需要使用的时候,就会在内存中创建新的字符串。

注意 使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:一是类的成员变量,而不是方法变量;二是必须是一个可变的引用对象,而不是一个原始类型或者不可变对象。

浅拷贝是有风险的,对上述浅拷贝的案例进行优化:

public class Thing implements Cloneable {

    private ArrayList<String> list = new ArrayList<>();

    @Override
    public Thing clone() throws CloneNotSupportedException {
        Thing cloneThing = (Thing) super.clone();
        // 添加对私有变量进行独立的拷贝
        cloneThing.setList((ArrayList<String>) this.list.clone());
        return cloneThing;
    }

    public void setValue(String str) {
        this.list.add(str);
    }

    public ArrayList<String> getValue() {
        return this.list;
    }

    public ArrayList<String> getList() {
        return list;
    }

    public void setList(ArrayList<String> list) {
        this.list = list;
    }
}

运行结果如下:

在这里插入图片描述

这样就实现了完全拷贝,两个对象之间就没有关系了,你改你的,我改我的,互不影响,这种拷贝就是深拷贝。

深拷贝还有一种是实现方式就是自己写二进制流来操作对象,然后实现对象的深拷贝。

注意 深拷贝和浅拷贝建议不要混合使用,特别是在涉及类的继承时,父类有多个引用的情况下就非常复杂,建议深拷贝和浅拷贝分开来实现。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
原型模式是一种创建设计模式,它允许通过复制现有对象来创建新对象,而不必提供一个显式的构造函数。这种模式用于创建重复的对象,同时保持性能。在 C++ 中,原型模式通常通过实现 clone() 函数来实现。 以下是一个简单的原型模式示例: ```c++ #include <iostream> class Prototype { public: virtual ~Prototype() {} virtual Prototype* clone() const = 0; }; class ConcretePrototype : public Prototype { public: ConcretePrototype(int value) : m_value(value) {} ConcretePrototype(const ConcretePrototype& other) : m_value(other.m_value) {} virtual ConcretePrototype* clone() const override { return new ConcretePrototype(*this); } int getValue() const { return m_value; } private: int m_value; }; int main() { ConcretePrototype prototype(42); ConcretePrototype* clone = prototype.clone(); std::cout << "Original: " << prototype.getValue() << std::endl; std::cout << "Clone: " << clone->getValue() << std::endl; delete clone; return 0; } ``` 在上面的示例中,我们定义了一个抽象基 `Prototype`,它有一个纯虚函数 `clone()`,所有的具体原型都必须实现此函数。我们还定义了一个具体原型 `ConcretePrototype`,它实现了 `clone()` 函数以复制自身。在 `main()` 函数中,我们首先创建一个 `ConcretePrototype` 对象,并通过调用 `clone()` 函数来创建一个新的克隆对象。最后,我们输出了原型对象和克隆对象的值,并在程序结束时释放了克隆对象的内存。 需要注意的是,克隆函数应该返回一个指向新创建对象的指针,因为克隆对象通常需要在堆上分配内存。此外,由于克隆函数通常需要访问私有成员变量,因此它应该是原型的友元函数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔跑吧,高同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值