美文网首页Android进阶Android技术知识Android开发
使程序运行更高效——原型模式

使程序运行更高效——原型模式

作者: 程序员丶星霖 | 来源:发表于2019-01-16 09:42 被阅读12次

    《Android源码设计模式解析与实战》读书笔记(四)
    《Android源码设计模式解析与实战》PDF资料下载

    一、原型模式简介

    原型模式是一个创建型的模式。原型二字表明了该模式应该有一个样板实例,用户从这个样板对象中复制出一个内部属性一致的对象,这就是“克隆”。被复制的实例就是我们所称的“原型”。

    原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效。

    1.1、定义

    用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

    1.2、使用场景

    1. 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
    2. 通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式。
    3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。

    注意:
    通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者说成本较高时,通过clone方法才能获得效率上的提升。

    二、原型模式的简单实现

    /**
     * 文档类型,扮演的是ConcretePrototype角色,而cloneable是代表prototype角色
     */
    public class WordDocument implements Cloneable {
        //文本
        private String mText;
        //图片名列表
        private ArrayList<String> mImages = new ArrayList<>();
    
        public WordDocument() {
            System.out.println("----------------WordDocument构造函数-----------------");
        }
    
        @Override
        protected WordDocument clone() {
            try {
                WordDocument doc = (WordDocument) super.clone();
                doc.mText = this.mText;
                doc.mImages = this.mImages;
                return doc;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public String getText() {
            return mText;
        }
    
        public void setText(String mText) {
            this.mText = mText;
        }
    
        public ArrayList<String> getImages() {
            return mImages;
        }
    
        public void addImages(String img) {
            this.mImages.add(img);
        }
    
        /**
         * 打印文档内容
         */
        public void showDocument() {
            System.out.println("------------Word Content Start-------------");
            System.out.println("Text:" + mText);
            System.out.println("Images List:");
            for (String imgName : mImages) {
                System.out.println("image name:"+imgName);
            }
            System.out.println("------------Word Content End---------------");
        }
    }
    

    Cloneable是一个标识接口,它表明这个类的对象是可拷贝的。如果没有实现Cloneable接口却调用了clone()函数将抛出异常。

    调用代码如下:

            WordDocument originDoc = new WordDocument();
            //2.编辑文档,添加图片等
            originDoc.setText("这是一篇文档");
            originDoc.addImages("图片1");
            originDoc.addImages("图片2");
            originDoc.addImages("图片3");
            originDoc.showDocument();
    
            //以原始文档为原型,拷贝一份副本
            WordDocument doc2 = originDoc.clone();
            doc2.showDocument();
            //修改文档副本,不影响原始文档
            doc2.setText("这是修改过的Doc2文本");
            doc2.showDocument();
    
            originDoc.showDocument();
    

    输出结果:

    原型模式.png

    doc2是originDoc的一份拷贝,它们的内容是一样的,而doc2修改了文本内容以后并不会影响originDoc的文本内容,这就保证了originDoc的安全性。还需要注意,通过clone拷贝对象时并不会执行构造函数

    原型模式的核心问题就是对原始对象进行拷贝,在这个模式的使用过程中需要注意的一点就是:深、浅拷贝的问题

    三、原型模式实战

    这是一个简化版的客户端,在用户登陆之后,通过LoginSession保存用户的登录信息,这些用户信息可能在APP的其他模块被用来做登录校验、用户个人信息显示等。但是,这些信息在客户端程序是不允许修改的,而需要在其他模块被调用,因此,需要开放已登录用户信息的访问接口。

    /**
     * 用户实体类
     */
    public class User {
        public int age;
        public String name;
        public String phoneNum;
        public Address address;
    
        @Override
        public String toString() {
            return "User{" +
                    "age=" + age +
                    ", name='" + name + '\'' +
                    ", phoneNum='" + phoneNum + '\'' +
                    ", addrss=" + address +
                    '}';
        }
    }
    
    /**
     * 用户地址类,存储地址的详细信息
     */
    public class Address {
        //城市
        public String city;
        //区
        public String district;
        //街道
        public String street;
    
        public Address(String city, String district, String street) {
            this.city = city;
            this.district = district;
            this.street = street;
        }
    
        @Override
        public String toString() {
            return "Address{" +
                    "city='" + city + '\'' +
                    ", district='" + district + '\'' +
                    ", street='" + street + '\'' +
                    '}';
        }
    }
    
    //登陆接口
    public interface Login {
        void login();
    }
    
    //登录实现
    public class LoginImpl implements Login {
    
        @Override
        public void login() {
            // 登录到服务器,获取到用户信息
            User loginedUser = new User();
            //将服务器返回的完整信息设置给loginedUser对象
            loginedUser.age = 22;
            loginedUser.name = "Mr.Simple";
            loginedUser.address = new Address("北京市", "海淀区", "花园东路");
            //登录完之后将用户信息设置到Session中LoginSession.getLoginSession()里
            LoginSession.getLoginSession().setLoginedUser(loginedUser);
        }
    }
    
    //登录Session
    public class LoginSession {
        static LoginSession sLoginSession = null;
        //已登录用户
        private User loginedUser;
    
        public LoginSession() {
        }
    
        public static LoginSession getLoginSession() {
            if (sLoginSession == null) {
                sLoginSession = new LoginSession();
            }
            return sLoginSession;
        }
    
        //设置已登录的用户信息,不对外开放
        void setLoginedUser(User user) {
            loginedUser = user;
        }
    
        public User getLoginedUser() {
            return loginedUser;
        }
    }
    

    LoginSession中的setLoginedUser函数是包级私有的,因此外部模块无法调用,这在一定程度上实现了外部客户端程序不能修改已登录的用户信息。

    但是,也会存在类似如下代码:

    //获取已登录的User对象
    User newUser = LoginSession.getLoginSession().getLoginedUser();
    newUser.address = new Address("北京市", "朝阳区", "大望路");
    

    类似的代码也会更新用户的地址。因此,需要使用原型模式来进行保护性拷贝,也就是说在LoginSession的getLoginUser()函数中返回的是已登录用户的一个拷贝,当更新用户地址的网络请求完成时,在通过包级私有的LoginSession中的setLoginedUser更新用户信息。

    于是在User类中覆写了clone方法:

    /**
     * 用户实体类
     */
    public class User implements Cloneable {
        public int age;
        public String name;
        public String phoneNum;
        public Address address;
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            User user = null;
            try {
                user = (User) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return user;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "age=" + age +
                    ", name='" + name + '\'' +
                    ", phoneNum='" + phoneNum + '\'' +
                    ", addrss=" + address +
                    '}';
        }
    }
    

    四、总结

    原型模式本质上就是对象拷贝。使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率。还有一个重要的用途即使保护性拷贝

    优点:

    • 原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地提现其优点。

    缺点:

    • 直接在内存中拷贝,构造函数是不会执行的,在实际开发当中应该注意这个潜在的问题。

    学海无涯苦作舟

    我的微信公众号

    相关文章

      网友评论

        本文标题:使程序运行更高效——原型模式

        本文链接:https://www.haomeiwen.com/subject/qdredqtx.html