"数据结构"模式
使用场景
常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用。这时候,将这
些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案。
典型模式
●Composite(组合模式)
●Iterator(迭代器)
●Chain of Resposibility(责任链)
Composite 组合模式
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。
关键代码:树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
应用实例: 1、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作符也可以是操作数、操作符和另一个操作数。 2、在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。
优点: 1、高层模块调用简单。 2、节点自由增加。
缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
使用场景:部分、整体场景,如树形菜单,文件、文件夹的管理。
注意事项:定义时为具体类。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
使用场景
在软件在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引
起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。
模式定义
将对象组合成树形结构以表示"部分-整体"的层次结构。
Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)。
类图结构
Add()和Remove(),GetChild()放在Component抽象类里面是存在争议的。比如下方代码,我并没有把Add()和Remove()放在父类Component中,而是放在Composite树节点里面。放在父类里面会带来一个弊端,叶子节点Leaf类不能实现Add()和Remove(),如果去实现这两个方法,可以抛出异常说明不支持接口,但是这样毫无意义。或者实现之后做空操作,那还不如不实现。放入Composite类实现,也是有弊端的。使用add()的时候要判断对象类型是否是Component。
所以现在普遍的做法就是不在Component实现Add()和Remove(),GetChild()。放到Composite树节点里面实现。
代码举例
//业务需求抽象类
public abstract class Component {
public abstract void process();
}
//树节点
public class Composite extends Component {
String name;
Vector<Component> elements;
public Composite(String s) {
name = s;
}
public void add(Component element) {
elements.add(element);
}
public void remove(Component element) {
elements.remove(element);
}
//递归调用
@Override
public void process() {
//1. 处理当前节点
//2. 处理叶子节点
for (Component e : elements) {
e.process();//多态调用
}
}
}
//叶子节点
public class Leaf extends Component {
String name;
public Leaf(String s) {
name = s;
}
@Override
public void process() {
//处理当前节点
}
}
public class ClientApp{
//如果不用Composite模式 c.process(); 不能这样调用 还需要使用typeOf c.getType 判断 c的类型
//判断c 是 Leaf 还是 Composite 然后做出相应的处理 使用 Composite模式就免去了 这些操作
public static void invoke(Component c){
//...
c.process(); //多态调用
//...
}
public static void main(String args[]) {
Composite root=new Composite("root");
Composite treeNode1=new Composite("treeNode1");
Composite treeNode2=new Composite("treeNode2");
Composite treeNode3=new Composite("treeNode3");
Composite treeNode4=new Composite("treeNode4");
Leaf leat1=new Leaf("left1");
Leaf leat2=new Leaf("left2");
root.add(treeNode1);
treeNode1.add(treeNode2);
treeNode2.add(leaf1);
root.add(treeNode3);
treeNode3.add(treeNode4);
treeNode4.add(leaf2);
invoke(root);
invoke(leaf2);
invoke(treeNode3);
}
}
在Android中 组合模式的体现
View.java 和 ViewGroup.java的举例
View.java 可以看做是 叶子节点 或者是 Component。而 Composite 树节点 就是 ViewGroup。
在ViewGroup 体现了 Composite设计模式
ViewGroup继承View,所以ViewGroup都是树节点 。
ViewGroup 中的 mChildren 数组 对应 上方代码中的 Vector<Component> elements;
hasFocusable() 对应上方代码中的 process(),addFocusables(),findViewsWithText()也是对应process()
@Override
boolean hasFocusable(boolean allowAutoFocus, boolean dispatchExplicit) {
// This should probably be super.hasFocusable, but that would change
// behavior. Historically, we have not checked the ancestor views for
// shouldBlockFocusForTouchscreen() in ViewGroup.hasFocusable.
// Invisible and gone views are never focusable.
相当于先处理当前节点的业务逻辑
if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
// Only use effective focusable value when allowed.
if ((allowAutoFocus || getFocusable() != FOCUSABLE_AUTO) && isFocusable()) {
return true;
}
//相当于处理叶子节点的业务逻辑
// Determine whether we have a focused descendant.
final int descendantFocusability = getDescendantFocusability();
if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
return hasFocusableChild(dispatchExplicit); //使用递归
}
return false;
}
要点总结
Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
将“客户代码与复杂的对象容器结构’'解耦是Composite的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的内部实现结构——发生依赖,从而更能‘应对变化”。
Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。
Iterator 迭代器
迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。
迭代器模式属于行为型模式。
介绍
意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
主要解决:不同的方式来遍历整个整合对象。
何时使用:遍历一个聚合对象。
如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。
关键代码:定义接口:hasNext, next。
应用实例:JAVA 中的 iterator。
优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
使用场景: 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。
注意事项:迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
使用场景
在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种‘透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。
使用面向对象技术将这种遍历机制抽象为‘迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方式。
模式定义
提供一种方法顺序访问一个聚合对象(容器)中的各个元素,而又不暴露(稳定)该对象的内部表示。
类图结构
代码举例
迭代器的使用
package File;
import java.util.Iterator;
import java.util.Vector;
public class ClientApp{
public static void main(String args[]) {
Vector<String> mc=new Vector<>();
mc.add("Beijing");
mc.add("Shanghai");
mc.add("New York");
mc.add("Paris");
mc.add("London");
Iterator<String> iter = mc.iterator();
while (iter.hasNext())
{
String item = iter.next();
System.out.println(item);
}
// : 后面的类型 一定要支持迭代器的类型才行 也就是实现Iterabale接口
for(String data: mc){ //还是要干迭代器干的事
System.out.println(data);
}
//如果使用反汇编工具 这两个循环生成的代码是一样的
}
}
AbstractList内部实现 iterator() 返回一个真正的类型对象
AbstractList实现了List接口
找到了迭代器最原始的接口
回到AbstractList类中查看iterator() 中的 return 语句
Itr类是封装在AbstractList类中的内部嵌套类,实现内部类原因是对外界是没必要知道的, 设计模式的原则内部实现尽量不要暴露给外界,除非有绝对的必要性。
在类图结构中 Iterator对应代码中的是 ,First() 在 Iterator 接口中被省略了。调用了一次hasNext(),然后再调用next(),直接就能得到First。Next() 对应的 是 。IsDone() 对应代码中的 hasNext()。CurrentItem() 已经被合并在 ,直接next()就返回对象。
Aggregate 是聚合对象 对应代码中的 。可迭代的对象就是聚合对象。CreateIterator() 对应代码中的 。
Concretelterator 对应代码中的 。
ConcreteAggregate 对应的是 的子类。在java库中几乎所有的集合类型,比如List,ArrayList,Set,都实现了 Iterable 接口。
Aggregate 和 Iterator 是稳定的。ConcreteAggregate 和 Concretelterator 是变化的。
要点总结
迭代抽象:访问一个聚合对象的内容而无需暴露它的内部表示。
迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
迭代器的健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。(不要在迭代的过程中删除元素)