【设计模式深度剖析】【5】【创建型】【原型模式】| 类比群发邮件,加深理解

👈️上一篇:建造者模式    |   下一篇:创建型设计模式对比👉️

原型模式(Prototype Pattern)

>>本文源码<<

概览

定义

英文原话

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

直译

指定要使用原型实例创建的对象类型,并通过复制该原型创建新对象。

3个角色

类图

Prototype

1. 抽象原型(Prototype)角色

为具体原型角色定义方法,指定统一标准

2. 具体原型(Concrete Prototype)角色

该角色是被复制的对象,必须实现抽象原型接口

Java中内置了克隆机制,Object类具有一个clone()方法,能够实现对象的克隆,使一个类支持克隆需要以下两步。

  • 实现Cloneable接口
  • 覆盖Object的clone()方法,完成对象的克隆操作,通常只需要调用Object的clone()方法(“浅克隆”-即只复制关联对象的引用)即可

3. 客户(Client)角色

该角色提出创建对象的请求

代码示例

>>示例源码<<

1. 抽象原型

抽象原型Prototype接口继承 Cloneable 接口,以标明该接口的实现类可以被复制,并声明一个 clone()方法,该clone()方法是对Object类的clone()方法的重写

package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;

/** 实现本接口,表明实现类可以被复制*/
public interface Prototype extends Cloneable {
    // 克隆方法
    Prototype clone();
}
2. 具体原型

具体原型ConcretePrototype实现clone()方法。这里调用的父类Object的clone()方法

package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;

/** 具体原型ConcretePrototype实现clone()方法*/
public class ConcretePrototype implements Prototype {

    @Override
    public Prototype clone() {
        try {
            // 此处调用的Object类的clone()方法,并向下转型为抽象原型类型。
            // Java中Object提供的clone()方法采用的是“浅”克隆,即只复制关联对象的引用;
            // 而不复制关联对象的数据。如果需要“深”克隆,则需要在覆盖clone()方法时手动控制克隆的深度。
            return (Prototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}
3. 被复制的对象的类

提供一个被复制的类,用于测试使用

package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;

import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;

/** 被复制的对象的类*/
@Data
@Builder
@EqualsAndHashCode(callSuper = true)
public class User extends ConcretePrototype {
    private String name;
    private Integer age;
}

这里的@Builder是lombok的注解,加上该注解编译出的class文件中,通过内部类的方式实现了建造者模式。便于用户方便地创建对象。

(关于建造者模式看上一篇 建造者模式


@Builder的原理,参考补充知识点Lombok的@Builder注解原理:建造者模式

4. 客户端

使用原型类的客户类

public class Client {
    // 传参传入具体原型类示例,具体原型类实现了抽象原型的clone方法
    public Prototype operation(Prototype example) {
        // 得到example的副本
        Prototype p = example.clone();
        return p;
    }
}
5. 测试类

新建了一个用于被复制的user对象,

调用客户类进行复制,传入原型类型的对象(User类实现了原型类,通过继承具体原型类实现的),然后返回一个原型实例,

之后测试了复制对象与原始对象属性是同一对象,

然后对复制对象的属性值通过反射进行了获取与打印

package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;

import java.lang.reflect.Field;

/**
 * @author Polaris 2024/5/17
 */
public class DemoTest {
    public static void main(String[] args) {
        User user = User.builder().name("历史").age(5000).build();

        Client client = new Client();
        Prototype clone = client.operation(user);

        System.out.println("name属性是同一个:"+((User)clone).getName().equals(user.getName()));
        System.out.println("age属性(>127,非读缓存值)是同一个:"+((User)clone).getAge().equals(user.getAge()));
        System.out.println("-----------------");
        Field[] declaredFields = clone.getClass().getDeclaredFields();
        for (Field declaredField : declaredFields) {
            Object o;
            try {
                String name = declaredField.getName();
                declaredField.setAccessible(true);
                o = declaredField.get(clone);
                System.out.println(name + ":" + o);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

/* Output:

name属性是同一个:true
age属性(>127,非读缓存值)是同一个:true
-----------------
name:历史
age:5000

*///~

输出中:克隆前后的对象的属性指向的是同一个对象。

也印证了:

Java中Object提供的clone()方法采用的是“浅”克隆,即只复制关联对象的引用

应用

优点

原型模式的优点有以下几个方面。

  1. 性能优良:原型模式是在内存二进制流的复制,要比直接new一个对象性能好,特别是在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
  2. 逃避构造函数的约束:这既 是优点也是缺点,直接在内存中复制,构造函数是不会执行的,因此减少了约束,需要在实际应用时进行权衡考虑。

使用场景

原型模式的使用场景如下。

  1. 资源优化场景,类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
  2. 性能和安全要求的场景,通过new产生一个对象需要非常烦琐的数据准备或访问权限,可以使用原型模式。
  3. 一个对象多个修改者的场景,一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式复制多个对象供调用者使用。
  4. [注]:在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone()方法创建一个对象,然后由工厂方法提供给调用者。
    • 注意Java中Object提供的clone()方法采用的是“浅”克隆,即只复制关联对象的引用,而不复制关联对象的数据。如果需要“深”克隆,则需要在覆盖clone()方法时手动控制克隆的深度。

原型模式示例解析:邮件群发

>>示例源码<<<

本案例是一个对象多个修改者情况:

  • 每次发送邮件都是对原始邮件对象克隆然后进行属性修改
  • 原始邮件对象实现了抽象原型接口
  • 抽象原型接口继承Cloneable接口,声明提供抽象原型类型的clone方法声明
  • 原始邮件类型实现抽象原型类型的clone()方法,用于克隆对象
  • 使用Object的克隆方法来克隆邮件类型对象,浅克隆,复制邮件类对象的引用

类图

在这里插入图片描述

1. 抽象原型角色:Prototype.java(接口定义克隆方法)

package com.polaris.designpattern.list1.creational.pattern5.prototype;

public interface Prototype extends Cloneable {
    //克隆方法
    Prototype clone();
}

2. 具体原型类:Mail.java

创建一个邮件类Mail:

Mail类实现Cloneable接口,并实现了clone()方法,

该方法是实现原型模式的关键,只有实现clone()方法,

在应用中才能对Mail进行复制克隆

package com.polaris.designpattern.list1.creational.pattern5.prototype;

import lombok.Getter;
import lombok.Setter;

public class Mail implements Prototype {
    //收件人
    @Getter
    @Setter
    private String recerver;
    //邮件标题
    @Getter
    @Setter
    private String subject;
    //称谓
    @Getter
    @Setter
    private String appellation;
    //邮件内容
    @Getter
    @Setter
    private String contxt;
    //邮件尾部,一般是加上“xxx版权所有”等信息
    @Getter
    @Setter
    private String tail;

    //构造函数
    public Mail(String subject, String contxt) {
        this.subject = subject;
        this.contxt = contxt;
    }

    //克隆方法
    @Override
    public Prototype clone() {
        Mail mail = null;
        try {
            mail = (Mail) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return mail;
    }
}

3. 测试类

通过邮件的克隆群发演示原型模式

package com.polaris.designpattern.list1.creational.pattern5.prototype;

import java.util.Random;

public class DemoTest {
    //发送账单的数量,这个值从数据库中获得的
    private static int MAX_COUNT = 6;

    public static void main(String[] args) {
        //模拟发送邮件
        int i = 0;
        //定义一个邮件对象
        Prototype prototype = new Mail("某商场五一抽奖活动",
                "五一抽奖活动通知:" +
                        "凡在五一期间在本商场购物满100元的客户都有货的抽奖机会!...");
        ((Mail)prototype).setTail("解释权归某商场所有");
        while (i < MAX_COUNT) {
            //克隆邮件
            Mail cloneMail = (Mail) prototype.clone();
            //以下是每封邮件不同的地方
            cloneMail.setAppellation(getRandomString(5) + " 先生(女士)");
            cloneMail.setRecerver(getRandomString(5) + "@" + getRandomString(8) + ".com");
            //发送邮件
            sendMail(cloneMail);
            i++;
        }
    }

    //发送邮件
    public static void sendMail(Mail mail) {
        System.out.println("标题:" + mail.getSubject() +
                "\t收件人:" + mail.getRecerver() + "\t...发送成功!");
    }

    //获取指定长度的随机字符串
    public static String getRandomString(int maxLength) {
        String souce = "abcdefghijklmnopqrstuvwxyz" +
                "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < maxLength; i++) {
            sb.append(souce.charAt(random.nextInt(souce.length())));
        }
        return sb.toString();
    }
}

/* Output:
标题:某商场五一抽奖活动	收件人:TEDiM@LXFeyNdU.com	...发送成功!
标题:某商场五一抽奖活动	收件人:qyOWv@wieXPRga.com	...发送成功!
标题:某商场五一抽奖活动	收件人:wMloC@IgFOzjBh.com	...发送成功!
标题:某商场五一抽奖活动	收件人:hrDGv@HAjpARpN.com	...发送成功!
标题:某商场五一抽奖活动	收件人:lyWwt@cLdWntpC.com	...发送成功!
标题:某商场五一抽奖活动	收件人:ZVeap@HZlYxaCe.com	...发送成功!
*///~

补充知识点

Lombok的@Builder注解原理:建造者模式

1. 先说下用法

通过下面的链式调用方式,可以非常方便的得到一个需要的user对像

User user = User.builder().name("历史").age(5000).build();
2. 原理分析

以下是标记了@Builder注解后,编译出的类文件中看到增加了以下代码:

public class User extends ConcretePrototype {
    private String name;
    private Integer age;
    
    User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public static User.UserBuilder builder() {
        return new User.UserBuilder();
    }

    public static class UserBuilder {
        private String name;
        private Integer age;

        UserBuilder() {
        }

        public User.UserBuilder name(String name) {
            this.name = name;
            return this;
        }

        public User.UserBuilder age(Integer age) {
            this.age = age;
            return this;
        }

        public User build() {
            return new User(this.name, this.age);
        }

        public String toString() {
            return "User.UserBuilder(name=" + this.name + ", age=" + this.age + ")";
        }
    }
}

通过标记了@Builder注解的类调用类方法builder()方法,这里创建了一个内部类UserBuilder对象

接下来就是构造者模式的实现

内部类UserBuilder就是建造者角色,

User类的内部类UserBuilder的属性和User类的属性完全一样,然后提供了对这些属性配置的方法,且每个方法都会将该实例返回,这样就可以进行链式调用。

最后提供了一个build()方法,将内部类的属性赋值给User类的构造函数,来构造User类实例,返回给客户端。

这就是建造者模式的具体应用。

👈️上一篇:建造者模式    |   下一篇:创建型设计模式对比👉️

  • 23
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值