三、原型设计模式

1. 原型设计模式介绍

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。原型设计模式多用于创建复杂的或者构造耗时的实例。因为这种情况下,复制一个已经存在的实例可使程序运行更高效。

2. 原型设计模式使用场景

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

3. 原型设计模式的UML类图

原型设计模型

4. 原型设计模式的简单实现

模拟情形: 用户首先创建了一个文档对象,即WordDocument,这个文档中包含文字和图片。用户经过了长时间的内容编辑后,打算对该文档做进一步的编辑,这个编辑后的文档是否会被采用还不去确定,因此为了安全起见,用户需要将当前文档拷贝一份,然后再在副本上进行修改。

  • WordDocument类:
public class WordDocument implements Cloneable {
            //文本
            private String mText;

            //图片
            private ArrayList<String> mImages = new ArrayList<String>();

            public WordDocument() {
                System.out.println("---------WordDocument构造函数--------");
            }

            @Override
            protected Object clone() throws CloneNotSupportedException {
                try {
                    WordDocument wordDocument = (WordDocument) super.clone();
                    wordDocument.mImages = this.mImages;
                    wordDocument.mText = this.mText;
                    return wordDocument;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }

            public String getmText() {
                return mText;
            }

            public void setmText(String mText) {
                this.mText = mText;
            }

            public ArrayList<String> getmImages() {
                return mImages;
            }
            /**
             * 添加图片
             * @param image
             */
            public void addImage(String image) {
                mImages.add(image);
            }

            /**
             * 打印文档内容
             */
            public void showDocument() {
                System.out.println("-------WordContent Start---- ");
                System.out.println("mText:" + mText);
                System.out.println("Images List:" + mImages);
                for (String imageName : mImages) {
                    System.out.println("image name:" + imageName);
                }
                System.out.println("------WordContent End-----");
            }
        }
  • 测试Client端:
public class Client {
            public static void main(String[] args) {
                //1.构建文档对象
                WordDocument wordDocument = new WordDocument();

                //2. 编辑文档、添加图片等
                wordDocument.setmText("这是一篇文档");
                wordDocument.addImage("图片1");
                wordDocument.addImage("图片2");
                wordDocument.addImage("图片3");
                wordDocument.addImage("图片4");

                //以原始对象为原型,拷贝一份副本
                try {
                    WordDocument cloneWord = (WordDocument) wordDocument.clone();
                    cloneWord.showDocument();

                    //修改文档副本,不会影响原始文档
                    cloneWord.setmText("这是修改后的拷贝文档");
                    cloneWord.showDocument();
                    cloneWord.addImage("哈哈哈.jpg");

                } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
                }
            }
        }
  • WordDocument对象实现了Cloneable接口,表示这个类的对象是可以拷贝的。如果没有实现Cloneable接口却调用了clone()函数将抛出异常。
  • 通过clone拷贝对象时并不会执行构造函数,因此如果在构造函数中需要一些特殊的初始化操作的类型,在使用Coneable实现拷贝时,需要注意构造函数不会执行的问题。

4.1深拷贝和浅拷贝

在上述原型模式的实现中,实际上只是一个浅拷贝,这份拷贝实际上是副本的字段引用原始的字段引用。

原型浅拷贝

A引用B就是说两个对象指向同一个地址,当A修改时,B也会改变,,B修改时,A同时也会改变。
此时修改Client中的代码如下:

public class Client {
        public static void main(String[] args) {
            //1.构建文档对象
            WordDocument wordDocument = new WordDocument();

            //2. 编辑文档、添加图片等
            wordDocument.setmText("这是一篇文档");
            wordDocument.addImage("图片1");
            wordDocument.addImage("图片2");
            wordDocument.addImage("图片3");
            wordDocument.addImage("图片4");

            //以原始对象为原型,拷贝一份副本
            try {
                WordDocument cloneWord = (WordDocument) wordDocument.clone();
                cloneWord.showDocument();

                //修改文档副本,不会影响原始文档
                cloneWord.setmText("这是修改后的拷贝文档");
                cloneWord.addImage("哈哈哈.jpg");
                //此时原始文档已经被修改了,因为副本和原始文档指向同一个地址
                wordDocument.showDocument();

            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
        }
    }

原始文档和副本文档中的mImages指向同一个地址。
解决方式就是采用深度拷贝,即在拷贝对象时,对于引用型的字段也要采用拷贝的形式,而不是单纯引用的形式。
代码如下:

@Override
        protected Object clone() throws CloneNotSupportedException {
            try {
                WordDocument wordDocument = (WordDocument) super.clone();
                wordDocument.mText = this.mText;
                //对mImages对象也调用clone函数,进行深拷贝
    //            wordDocument.mImages = this.mImages;
                wordDocument.mImages = (ArrayList<String>) this.mImages.clone();
                return wordDocument;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
  • 在开发过程中,为了减少错误,,建议采用深拷贝模式,避免操作副本时影响原始对象的问题。

5. Android源码中的原型设计模式

  • ArrayList源码:
//元素数量
        int size;
        //实际存储数据的数组
        transient Object[] array;
        public Object clone() {
            try {
                ArrayList<?> result = (ArrayList<?>) super.clone();
                result.array = array.clone
                return result;
            } catch (CloneNotSupportedException e) {
                throw new AssertionError();
            }
        }

size是整型,并不是一个对象,它是值类型,并不是引用类型,所以不需要进行克隆。

  • 在Android源码中的Intent
     @Override
     public Object clone() {
                    return new Intent(this);
              }

         /**
             * Copy constructor.
             */
            public Intent(Intent o) {
                this.mAction = o.mAction;
                this.mData = o.mData;
                this.mType = o.mType;
                this.mPackage = o.mPackage;
                this.mComponent = o.mComponent;
                this.mFlags = o.mFlags;
                this.mContentUserHint = o.mContentUserHint;
                if (o.mCategories != null) {
                    this.mCategories = new ArraySet<String>(o.mCategories);
                }
                if (o.mExtras != null) {
                    this.mExtras = new Bundle(o.mExtras);
                }
                if (o.mSourceBounds != null) {
                    this.mSourceBounds = new Rect(o.mSourceBounds);
                }
                if (o.mSelector != null) {
                    this.mSelector = new Intent(o.mSelector);
                }
                if (o.mClipData != null) {
                    this.mClipData = new ClipData(o.mClipData);
                }
            }

Intent内部的clone方法,实际上并没有调用super.clone()方法来实现对象拷贝,而是调用了new Intent(this)。使用clone和new需要根据构造对象的成本来决定,如果对象的构造成本比较高或者构造比较麻烦,那么使用clone函数效率较高,否则可以使用new的形式。

6. Android开发中的原型设计模式

情景如下:模块A有修改信息的权利,模块B没有修改信息的权利,只有可读,并在另外一个包中。

  • UserInfo 用户信息类:
public class UserInfo implements Cloneable {
            private String name;
            private int age;

            public UserInfo(String name, int age) {
                this.name = name;
                this.age = age;
            }

            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;
            }

            @Override
            public String toString() {
                return "UserInfo{" +
                        "name='" + name + '\'' +
                        ", age=" + age +
                        '}';
            }

                /**
             * 覆写clone方法
             *
             * @return
             * @throws CloneNotSupportedException
             */
            @Override
            protected Object clone() throws CloneNotSupportedException {
                UserInfo userInfo = null;
                try {
                    userInfo = (UserInfo) super.clone();
                } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
                }
                return userInfo;
            }
        }
  • Session 间接修改用户信息类,setUserInfo()方法为包访问权限,所以只有当前包内的类可以访问。
public class Session implements Cloneable {
            private UserInfo userInfo;

            public UserInfo getUserInfo() {
                try {
                    return (UserInfo) userInfo.clone();
                } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
                }
                return null;
            }

            /**
             * 包访问权限,表示只有当前包下面的类才可修改信息
             *
             * @param userInfo
             */
            void setUserInfo(UserInfo userInfo) {
                this.userInfo = userInfo;
            }

            @Override
            public String toString() {
                return "Session{" +
                        "userInfo=" + userInfo +
                        '}';
            }
        }
  • Client 测试类:
public class Client {
            public static void main(String[] args) {
                //模块A有设置信息的权利
                UserInfo userInfo = new UserInfo("张三", 18);
                Session session = new Session();
                session.setUserInfo(userInfo);
                System.out.println("before:" + session);

                //模块B,另一个包中,另外一个人获取信息,但是不希望有修改信息的权利,只有可读。

                session.getUserInfo().setName("李四");
                System.out.println("after:" + session);

            }
        }
  • 模块A和Session类处于同一个包下,当模块A设置信息后,处于包外的模块B通过getUserInfo获取信息后,拿到了当前UserInfo对象,所以为了防止信息被修改,在Session类的getUserInfo()方法中,返回了UserInfo的副本,达到了只有可读的效果。

7. 总结

  • 使用原型模式可以解决构造复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率。
  • 某个对象可能是只读的,为了防止外部对这个只读对象修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。

优点:

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

缺点:

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值