牛骨文教育服务平台(让学习变的简单)

前言

面向对象中的另外一个设计模式是原型(prototype)模式,这种模式使用非常简单,使用的场合不是很多,所以不是很常用。

定义:将一个对象实例设为原型,通过clone该原型实例得到新的一个实例,而不是通过new得到原型对象实例,这点java和C++的对象拷贝是一样的。

适用场景:

1、提升对象创建的效率。如果一个对象的创建比较复杂,需要消耗很长的时间才能完成该对象的创建,那么这个时候使用原型模式比较好,即通过一个已有该对象的实例克隆出一个新的实例而不是通过new。

2、保护原型实例的安全。对于一个已经有的原型对象的实例,如果要操作该原型实例(读取数据),又要保证安全性,那么我们可以将该实例复制出一个新的实例给要读取该实例的客户端使用,而不是直接将该实例返回。这样客户端就无法更改原来那个对象的实例的数据了。

原型模式的实现

以java语言为例,原型模式的实现非常简单。下面模拟一个用户登录的过程来实现原型模式。

1、原型对象,User.java

public class User implements Cloneable{

    private String username;
    private String password;
    private Role role;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    //.. -> setter

    @Override
    protected User clone(){
        User user = null;
        try {
            user = (User) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }

    @Override
    public String toString() {
        return "User [username=" + username + ", password=" + password
                + ", role=" + role + "]";
    }
}

需要注意的是clone方法是重写Object对象的,这里重写clone方法没有处理引用类型的复制,为浅复制。

java对象的复制(需要实现Cloneable接口)

  1. 浅复制:没有对引用类型进行处理,这样会导致复制出来的对象,如果存在引用类型,那么复制出来的对象和原来的对象里面的引用类型对象指向的是同一个内存空间。
  2. 深复制:对引用类型也进行复制,这样可以使得克隆对象和原对象完全脱离关系,即怎么操作克隆对象都不会影响原对象。

2、登录的Session,LoginSession.java

public class LoginSession {

    private static User currentUser;

    public static void login(User user) {
        if (currentUser == null) {
             currentUser = user;
            System.out.println("登录用户:"+currentUser);
        }
    }

    public static User getCurrentUser() {
        return currentUser.clone();
    }
}

注意:

  1. login()方法模拟用户实际登录的过程,用户实际登录则会在Session中添加一个登录的用户(原型实例)。
  2. getCurrentUser()不是返回原型实例,而是返回原型实例的克隆实例。这样保证了当前Session中登录的用户信息只能由登录的时候决定,登录后不能修改用户名或者密码了。而这个克隆实例则给程序的其他地方使用(即使改变了里面的数据,原型实例中的数据也不变)。

3、模拟用户从浏览器登录网站和其它地方操作登录的用户信息,Test.java

public class Test {

    public static void main(String[] args) {
        User currentUser = new User("lt","123");
        currentUser.setRole(new Role("程序员"));
        // 模拟登陆
        LoginSession.Login(currentUser);
        // 模拟其他地方操作当前登录用户的情况
        User clone = LoginSession.getCurrentUser();
        clone.setUsername("ydx");
        Role role = clone.getRole();
        role.setName("老板");

        System.out.println("cloneUser:"+clone);
        System.out.println("登录用户:"+currentUser);
    }
}

这是一个模拟用户登录和其它地方操作登录的用户的例子,下面我们运行看输出:

这里写图片描述

其它地方得到当前登录的用户实例(实际上是一个克隆实例),并修改了克隆实例的用户名和角色信息,发现原型实例用户名没变而角色却变了。这里我们明明改的是克隆实例的信息,为什么原型实例角色信息却变了!这里涉及到了浅复制和深复制。我们对User的克隆方法改成下面这样并修重写Role的clone(注意需要实现CloneAble方法,否则克隆的时候会报CloneNotSupportedException异常)。

修改后的User的clone()

@Override
protected User clone(){
    User user = null;
    try {
        user = (User) super.clone();
        user.setRole((Role) user.getRole().clone());
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return user;
}

重写了Role的clone方法,需要实现Cloneable接口(告诉外界这个对象可以被复制),Role无引用类型,浅复制和深复制一样,所以直接给个空的实现即可。

public class Role implements Cloneable{
    private String name;

    public Role(String name) {
        super();
        this.name = name;
    }

    //.. -> setter

    @Override
    public String toString() {
        return "Role [name=" + name + "]";
    }

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

再次运行Test.java:

这里写图片描述

可以看到我们修改了克隆实例的信息后,原型实例什么信息也没有改变,包括引用类型的对象信息也没变,这就是深复制和浅复制的区别。到此这个模拟过程结束,这个模拟过程保证了其它地方可以读取到原型实例的信息但无法影响原型实例信息,满足了我们安全性要求和逻辑需要。

总结:原型模型的实现很简单,就是对象的拷贝(克隆或复制),但我们要注意对象的克隆有深和浅之分。