Java设计模式之创建型模式 | 原型模式

52 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 10 天,点击查看活动详情

觉得对你有益的小伙伴记得点个赞+关注

后续完整内容持续更新中

希望一起交流的欢迎发邮件至javalyhn@163.com

1. 原型模式定义

原型模式的简单程度仅次于单例模式,用原型模式指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

image.png

2. 原型模式使用场景

  1. 资源优化场景:类初始化需要消耗大量资源,这个资源包括数据,硬件资源等。
  2. 性能和安全优化的场景:通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  3. 一个对象多个修改者的情况:一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时可以考虑使用原型模式拷贝多个对象供调用者使用。
  4. 深拷贝,浅拷贝

在实际项目中,原型模式通常配合工厂方法模式一起出现,通过clone()方法创建一个对象,然后由工厂方法提供给调用者。

原型模式已经和Java融为一体了,可以随手拿来使用。

3. 原型模式的实现方法

要克隆的对象实现Clonneable接口并且重写clone(),就完成了原型模式

image.png

模拟上述缓存系统

3.1 User类

/**
 * 当前对象是可克隆的
 */
public class User implements Cloneable {

    private String username;
    private Integer age;

    public User(){
        System.out.println("User对象创建");
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + ''' +
                ", age=" + age +
                '}';
    }


    /**
     * 再创建一个人,赋予我的所有属性
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        User user = null;
        try{
             user = (User)user.clone();
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }
}

3.2 MyBtisCache类

public class MyBatisCache {

    //缓存user.序列化和反序列化-深克隆
    private Map<String,User> userCache = new HashMap<>();

    /**
     * 从数据库查数据
     * @return
     */
    public User getUser(String userName) throws CloneNotSupportedException {
        User user = null;
        //如果缓存中没有
        if(!userCache.containsKey(userName)) {
            //去查询数据库
            user = getUserFromDB(userName);
        }else {
            //缓存中查到了
            //原型已经拿到了,但是不能直接给(本人)
            user = userCache.get(userName);
            System.out.println("从缓存中拿到的是:" + user);
            //从这个对象快速得到一个克隆体(克隆人)==原型模式
            user = (User)user.clone();
        }
        return user;
    }
    
    public User getUserFromDB(String userName) throws CloneNotSupportedException {
        System.out.println("从数据库中查到:" + userName);
        User user = new User();
        user.setUsername(userName);
        user.setAge(18);
        //将这个用户放入缓存
        userCache.put(userName,(User)user.clone());
        return user;
    }
}

3.3 MainTest类


/**
 * 是用于创建重复的对象,同时又能保证性能。
 * 1、MyBatisCache:操作数据库,从数据库里面查出很多记录(70%改变很少)
 * 2、每次查数据库,查到以后把所有数据都封装一个对象,返回。
 *    10000 thread:查一个记录: new User("zhangsan",18);每次创建一个对象封装并返回
 *    系统里面就会有10000个User;浪费内存
 * 3、解决:缓存;查过的保存。
 *          如果再查相同的记录,拿到原来的原型对象
 *
 * 4、此时直接拿到缓存中的对象。
 */
public class MainTest {

    public static void main(String[] args) throws Exception {
        MyBatisCache mybatis = new MyBatisCache();

        //十分危险
        //得到的是克隆体
        User zhangsan1 = mybatis.getUser("zhangsan");
        System.out.println("1==>"+zhangsan1);
        zhangsan1.setUsername("李四2.。。");
        System.out.println("zhangsan1自己改了:"+zhangsan1);


        //得到的是克隆体
        User zhangsan2 = mybatis.getUser("zhangsan");

        System.out.println("2-->"+zhangsan2);

        //得到的是克隆体
        User zhangsan3 = mybatis.getUser("zhangsan");
        System.out.println("3-->"+zhangsan3);

        //得到的是克隆体
        User zhangsan4 = mybatis.getUser("zhangsan");
        System.out.println("4-->"+zhangsan4);

        System.out.println(zhangsan1 == zhangsan3);

    }
}

image.png

4. 原型模式的优点以及注意事项

4.1 优点

  1. 性能优点: 原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是在要在一个循环体内创建很多对象。
  2. 逃避构造函数的约束: 这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。这点在使用时需要考虑。

4.2 构造函数不会被执行

一个实现了Cloneable并且重写了clone方法的类A,有一个无参构造或有参构造B,通过new关键字产生了一个对象S,再然后通过s.clone()产生了一个新对象T,那么在对象拷贝时,构造函数B是不会被执行的。

Thing类

public class Thing implements Cloneable{
    public Thing() {
        System.out.println("Thing构造器执行了");
    }

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

Client类

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        //产生一个对象
        Thing thing = new Thing();
        //拷贝一个对象
        Thing clone =(Thing) thing.clone();

    }
}

image.png

可见构造器只被执行一次。

原因很简单,Object的clone()是在内存二进制流的拷贝(具体地说是堆内存),重新分配一个内存块,那构造函数没有执行也是很容易就知道的。

4.3 深拷贝与浅拷贝

这个内容放在小编下一篇文章讲,本文主要讲述设计模式。

5. 总结

原型模式很简单,希望小伙伴们跟着代码敲一遍,相信很快就能掌握!!!!!