熊猫儿
发表于 2016-9-2 14:40
注释很清楚了,是用来赋予一个类的 clone 功能的,继续看看 Object 类的 clone 函数:protected Object clone() throws CloneNotSupportedException { if (!(this instanceof Cloneable)) { throw new CloneNotSupportedException("Class " + getClass().getName() + " doesn't implement Cloneable"); } return internalClone();}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
这也是为什么需要继承 Cloneable 接口的原因,不继承该接口这里就会直接抛出 CloneNotSupportedException 异常,internalClone 是一个 native 函数:private native Object internalClone();
[*]1
[*]1
它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
这里需要注意的是浅拷贝(影子拷贝)与深拷贝的问题,学过 C++ 对拷贝都应该印象很深,在上面的例子中用的是一个 String 和一个 ArrayList 的对象,对于这两个对象,clone 函数里的拷贝写法是不一样的,一个是直接 set ,另一个需要继续调用 ArrayList 的 clone 方法生成一个 ArrayList 的拷贝对象 set 进 copy 对象中,如果直接将源对象中的 ArrayList 对象 set 进 copy 对象中,就会造成客户端获取到 copy 对象之后,可以通过 copy 对象修改源对象的数据,这在保护原型模式中是绝对不允许的,所以这里不能使用浅拷贝,必须要使用深拷贝。Object 类的 clone 方法只会拷贝对象中的 8 种基本数据类型 ,byte 、char、short、int、long、float、double、boolean,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝,如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝,如上面的 ArrayList 一样。String 这个类型必须要单独拿出来说一下,这个类型实际上算是一个浅拷贝,因为 src 与 拷贝后的 copy 对象指向的是同一个内存区域,但是由于 Java 中 String 的特殊不可变性(除去反射之外,不能修改一个 String 对象所指向的字符串内容),具体可以参考这篇博客:Java中的String为什么是不可变的? – String源码分析,所以从实际表现效果来看,String 和 8 种基础类型一样,可以认为是深拷贝。
http://img.blog.csdn.net/20160625204330086示例与源码 原型模式的代码结构很简单,我们这就以 Android 中的 Intent 类为例,来简单了解一下 Intent 类的原型模式:
Intent.class@Overridepublic Object clone() { return new Intent(this);}.../** * Copy constructor. */
熊猫儿
发表于 2016-9-2 14:40
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); }}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
[*]12
[*]13
[*]14
[*]15
[*]16
[*]17
[*]18
[*]19
[*]20
[*]21
[*]22
[*]23
[*]24
[*]25
[*]26
[*]27
[*]28
[*]29
[*]30
[*]31
[*]32
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
[*]12
[*]13
[*]14
[*]15
[*]16
[*]17
[*]18
[*]19
[*]20
[*]21
[*]22
[*]23
[*]24
[*]25
[*]26
[*]27
[*]28
[*]29
[*]30
[*]31
[*]32
和我们平时使用的 clone 函数不一样,Intent 类并没有调用 super.clone ,而是专门写了一个拷贝构造函数用来克隆对象,我们在上文提到过,使用 clone 和 new 需要根据构造函数的成本来决定,如果对象的构造成本比较高或者构造较为麻烦,那么使用 clone 函数效率较高,否则可以使用 new 的形式。这就和 C++ 中的拷贝构造函数完全一致,将原始对象作为构造函数的参数,然后在构造函数中奖原始对象的数据逐个拷贝一遍,这样,整个克隆过程就完成了。总结 原型模式是非常简单的一个设计模式,它的核心问题就是对原始对象进行拷贝,在这个模式的使用过程中需要注意的一点就是:深、浅拷贝的问题。在开发过程中,为了减少错误,应该尽量使用深拷贝,避免操作副本时影响原始对象的问题。
同时需要特别注意的是使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用 Object 类的 clone 方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。
原型模式的优点和缺点基本也就明了了:
[*]优点
原型模式是在内存中二进制流的拷贝,要比直接 new 一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好的体现其优点,而且可以向客户端隐藏制造新实例的复杂性;[*]缺点
直接在内存中拷贝,构造函数是不会执行的,在实际开发中应该注意这个潜在的问题,另外,对象的复制有时候相当复杂,特别需要注意的是不彻底深拷贝的问题。
熊猫儿
发表于 2016-9-3 22:30
创建型模式 Rules of thumb 有些时候创建型模式是可以重叠使用的,有一些抽象工厂模式和原型模式都可以使用的场景,这个时候使用任一设计模式都是合理的;在其他情况下,他们各自作为彼此的补充:抽象工厂模式可能会使用一些原型类来克隆并且返回产品对象。
抽象工厂模式,建造者模式和原型模式都能使用单例模式来实现他们自己;抽象工厂模式经常也是通过工厂方法模式实现的,但是他们都能够使用原型模式来实现;
通常情况下,设计模式刚开始会使用工厂方法模式(结构清晰,更容易定制化,子类的数量爆炸),如果设计者发现需要更多的灵活性时,就会慢慢地发展为抽象工厂模式,原型模式或者建造者模式(结构更加复杂,使用灵活);
原型模式并不一定需要继承,但是它确实需要一个初始化的操作,工厂方法模式一定需要继承,但是不一定需要初始化操作;
使用装饰者模式或者组合模式的情况通常也可以使用原型模式来获得益处;
单例模式中,只要将构造方法的访问权限设置为 private 型,就可以实现单例。但是原型模式的 clone 方法直接无视构造方法的权限来生成新的对象,所以,单例模式与原型模式是冲突的,在使用时要特别注意。源码下载 https://github.com/zhaozepeng/Design-Patterns/tree/master/PrototypePattern引用https://en.wikipedia.org/wiki/Prototype_pattern
http://blog.csdn.net/jason0539/article/details/23158081
http://blog.csdn.net/zhangjg_blog/article/details/18369201
http://blog.csdn.net/zhangjg_blog/article/details/18319521
熊猫儿
发表于 2016-9-3 22:31
java/android 设计模式学习笔记(12)---组合模式
标签: 设计模式androidjava
2016-06-26 18:03 3041人阅读 评论(0) 收藏 举报
http://static.blog.csdn.net/images/category_icon.jpg 分类:
Android/Java 设计模式(18) http://static.blog.csdn.net/images/arrow_triangle%20_down.jpg Java(23) http://static.blog.csdn.net/images/arrow_triangle%20_down.jpg Android(68) http://static.blog.csdn.net/images/arrow_triangle%20_down.jpg
版权声明:转载请标明出处http://blog.csdn.net/self_study,对技术感兴趣的同鞋加群544645972一起交流
目录(?)[+]
这篇我们来介绍一下组合模式(Composite Pattern),它也称为部分整体模式(Part-Whole Pattern),结构型模式之一。组合模式比较简单,它将一组相似的对象看作一个对象处理,并根据一个树状结构来组合对象,然后提供一个统一的方法去访问相应的对象,以此忽略掉对象与对象集合之间的差别。这个最典型的例子就是数据结构中的树了,如果一个节点有子节点,那么它就是枝干节点,如果没有子节点,那么它就是叶子节点,那么怎么把枝干节点和叶子节点统一当作一种对象处理呢?这就需要用到组合模式了。
http://img.blog.csdn.net/20160626121352749
转载请注明出处:http://blog.csdn.net/self_study/article/details/51761709。
PS:对技术感兴趣的同鞋加群544645972一起交流。设计模式总目录 java/android 设计模式学习笔记目录特点 组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构,并且能够让客户端以一致的方式处理个别对象以及组合对象。
组合模式让我们能用树形方式创建对象的结构,树里面包含了组合构件以及叶子构件的对象,而且能够把相同的操作应用在组合构件和叶子构件上,换句话说,在大多数情况下我们可以忽略组合对象和叶子对象之间的差别。组合模式使用的场景:
[*]表示对象的部分-整体结构层次时;
[*]从一个整体中能够独立出部分模块或功能的场景。
熊猫儿
发表于 2016-9-3 22:33
UML类图 组合模式在实际使用中会有两种情况:安全的组合模式与透明的组合模式。安全的组合模式 我们先来看看安全组合模式的 uml 类图:
http://img.blog.csdn.net/20160626123324414
可以看到组合模式有 4 个角色:
[*]Component:抽象根节点,为组合中的对象声明接口行为,是所有节点的抽象。在适当的情况下,实现所有类共有接口的缺省行为。声明一个接口用于访问和管理 Component 的子节点。可在递归结构中定义一个接口,用于访问一个父节点,并在合适的情况下实现它;
[*]Composite:增加定义枝干节点的行为,存储子节点,实现 Component 接口中的有关的操作;
[*]Leaf:在组合中表示叶子结点对象,叶子节点没有子节点,实现 Component 接口中的全部操作;
[*]Client:通过 Component,Composite 和 Leaf 类操纵组合节点对象。
据此我们可以写出安全组合模式的通用代码:
Component.class
public abstract class Component { public abstract void operation();}
[*]1
[*]2
[*]3
Composite.classpublic class Composite extends Component{ private ArrayList<Component> componentList = new ArrayList<>(); @Override public void operation() { Log.e("shawn", "this is composite " + this + " -------start"); for (Component component : componentList) { component.operation(); } Log.e("shawn", "this is composite " + this + " -------end"); } public void add(Component child) { componentList.add(child); }
熊猫儿
发表于 2016-9-3 22:34
public void remove(Component child) { componentList.remove(child); } public Component getChild(int position) { return componentList.get(position); }}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
[*]12
[*]13
[*]14
[*]15
[*]16
[*]17
[*]18
[*]19
[*]20
[*]21
[*]22
[*]23
[*]24
[*]25
Leaf.classpublic class Leaf extends Component{ @Override public void operation() { Log.e("shawn", "this if leaf " + this); }}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
Client 测试代码:Composite root = new Composite();Leaf leaf1 = new Leaf();Composite branch = new Composite();root.add(leaf1);root.add(branch);Leaf leaf2 = new Leaf();branch.add(leaf2);root.operation();break;
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
[*]12
最后输出结果:com.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@a37f4d8 -------startcom.android.compositepattern E/shawn: this if leaf com.android.compositepattern.composite.Leaf@1d7d4031com.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@ec97316 -------startcom.android.compositepattern E/shawn: this if leaf com.android.compositepattern.composite.Leaf@5dae497com.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@ec97316 -------endcom.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@a37f4d8 -------end
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
代码很简单,结果就是一个简单的树形结构,但是仔细看看客户端代码,就能发现它违反了 6 个设计模式原则中依赖倒置原则,客户端不应该直接依赖于具体实现,而应该依赖于抽象,既然是面向接口编程,就应该把更多的焦点放在接口的设计上,于是这样就产生了透明的组合模式。透明的组合模式 来看看透明的组合模式 uml 类图:
http://img.blog.csdn.net/20160626160823732
熊猫儿
发表于 2016-9-3 22:36
和安全的组合模式差异就是在将 Composite 的操作放到了 Component 中,这就造成 Leaf 角色也要实现 Component 中的所有方法。实现的代码做出相应改变:
Component.classpublic interface Component { void operation(); void add(Component child); void remove(Component child); Component getChild(int position);}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
Composite.classpublic class Composite implements Component{ private ArrayList<Component> componentList = new ArrayList<>(); @Override public void operation() { Log.e("shawn", "this is composite " + this + " -------start"); for (Component component : componentList) { component.operation(); } Log.e("shawn", "this is composite " + this + " -------end"); } @Override public void add(Component child) { componentList.add(child); } @Override public void remove(Component child) { componentList.remove(child); } @Override public Component getChild(int position) { return componentList.get(position); }}
http://blog.csdn.net/self_study/article/details/51761709
熊猫儿
发表于 2016-9-4 22:04
Leaf.classpublic class Leaf implements Component { @Override public void operation() { Log.e("shawn", "this if leaf " + this); } @Override public void add(Component child) { throw new UnsupportedOperationException("leaf can't add child"); } @Override public void remove(Component child) { throw new UnsupportedOperationException("leaf can't remove child"); } @Override public Component getChild(int position) { throw new UnsupportedOperationException("leaf doesn't have any child"); }}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
[*]12
[*]13
[*]14
[*]15
[*]16
[*]17
[*]18
[*]19
[*]20
[*]21
Client 测试代码Component root = new Composite();Component leaf1 = new Leaf();Component branch = new Composite();root.add(leaf1);root.add(branch);Component leaf2 = new Leaf();branch.add(leaf2);root.operation();
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
最后产生的结果是一样的,由于是在 Component 类中定义了所有的行为,所以客户端就不用直接依赖于具体 Composite 和 Leaf 类的实现,遵循了依赖倒置原则——依赖抽象,而不依赖具体实现。但是也违反了单一职责原则与接口隔离原则,让 Leaf 类继承了它本不应该有的方法,并且不太优雅的抛出了 UnsupportedOperationException ,这样做的目的就是为了客户端可以透明的去调用对应组件的方法,将枝干节点和子节点一视同仁。
另外,将 Component 写成一个虚基类,并且实现所有的 Composite 方法,而且默认都抛出异常,只让 Composite 去覆盖重写父类的方法,而 Leaf 类就不需要去实现 Composite 的相关方法,这么去实现当然也是可以的。
熊猫儿
发表于 2016-9-4 22:05
对比 安全的组合模式将责任区分开来放在不同的接口中,这样一来,设计上就比较安全,也遵循了单一职责原则和接口隔离原则,但是也让客户端必须依赖于具体的实现;透明的组合模式,以单一职责原则和接口隔离原则原则换取透明性,遵循依赖倒置原则,客户端就直接依赖于 Component 抽象即可,将 Composite 和 Leaf 一视同仁,也就是说,一个元素究竟是枝干节点还是叶子节点,对客户端是透明的。
所以这是一个很典型的折衷案例,尽管我们受到设计原则的指导,但是我们总是需要观察某原则对我们的设计所造成的影响。有时候这个需要去根据实际案例去分析,毕竟有些时候 6 种设计模式原则在实际使用过程中是会冲突的,是让客户端每次使用的时候都去先检查类型还是赋予子节点不应该有的行为,这都取决于设计者的观点,总体而言,这两种方案都是可行的。示例与源码 组合模式在实际生活过程中的例子就数不胜数了,比如菜单、文件夹等等。我们这就以 Android 中非常经典的实现为例来分析一下。View 和 ViewGroup 想必应该都非常熟悉,其实他们用到的就是组合模式,我们先来看看他们之间的 uml 类图:
http://img.blog.csdn.net/20160626165308858
ViewManager 这个类在java/android 设计模式学习笔记(8)—桥接模式中提到过,WindowManager 也继承了该类:
熊猫儿
发表于 2016-9-4 22:07
/** Interface to let you add and remove child views to an Activity. To get an instance* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.*/public interface ViewManager{ /** * Assign the passed LayoutParams to the passed View and add the view to the window. * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a * secondary {@link Display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view);}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
[*]12
[*]13
[*]14
[*]15
[*]16
[*]17
[*]18
[*]19
只定义了关于 View 操作的三个方法。ViewParent 类是用来定义一个 父 View 角色所具有的职责,在 Android 中,一般能成为父 View 的也只有 ViewGroup:/** * Defines the responsibilities for a class that will be a parent of a View. * This is the API that a view sees when it wants to interact with its parent. **/public interface ViewParent { /** * Called when something has changed which has invalidated the layout of a * child of this view parent. This will schedule a layout pass of the view * tree. */ public void requestLayout(); /** * Indicates whether layout was requested on this view parent. * * @return true if layout was requested, false otherwise */ public boolean isLayoutRequested(); ....}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
[*]12
[*]13
[*]14
[*]15
[*]16
[*]17
[*]18
[*]19
[*]20
[*]21
从 uml 类图中可以注意到一点,ViewGroup 和 View 使用的安全的组合模式,而不是透明的组合模式,怪不得有时候使用前需要将 View 强转成 ViewGroup 。