Android的设计模式-组合模式
前言
Android的设计模式系列文章介绍,欢迎关注,持续更新中:
Android的设计模式-设计模式的六大原则
创建型模式:
Android的设计模式-单例模式
Android的设计模式-建造者模式
Android的设计模式-工厂方法模式
Android的设计模式-简单工厂模式
Android的设计模式-抽象工厂模式
Android的设计模式-原型模式
行为型模式:
Android的设计模式-策略模式
Android的设计模式-状态模式
Android的设计模式-责任链模式
Android的设计模式-观察者模式
Android的设计模式-模板方法模式
Android的设计模式-迭代器模式
Android的设计模式-备忘录模式
Android的设计模式-访问者模式
Android的设计模式-中介者模式
Android的设计模式-解释器模式
Android的设计模式-命令模式
结构型模式:
Android的设计模式-代理模式
Android的设计模式-组合模式
Android的设计模式-适配器模式
Android的设计模式-装饰者模式
Android的设计模式-享元模式
1.定义
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
2.介绍
- 组合模式属于结构型模式。
- 组合模式有时叫做部分—整体模式,主要是描述部分与整体的关系。
- 组合模式实际上就是个树形结构,一棵树的节点如果没有分支,就是叶子节点;如果存在分支,则是树枝节点。
- 我们平时遇到的最典型的组合结构就是文件和文件夹了,具体的文件就是叶子节点,而文件夹下还可以存在文件和文件夹,所以文件夹一般是树枝节点。
3.UML类图
角色说明:
- Component(抽象组件角色):定义参加组合对象的共有方法和属性,可以定义一些默认的函数或属性。
- Leaf(叶子节点):叶子没有子节点,因此是组合树的最小构建单元。
- Composite(树枝节点):定义所有枝干节点的行为,存储子节点,实现相关操作。
4.实现(透明的组合模式)
下面以网站页面为例子,一个页面有多个栏目以及内容,栏目又可以包含子栏目以及具体内容,这就是一个树形结构。如下图:
4.1 创建抽象组件角色
这里就是一个网站的抽象页面元素:
public abstract class PageElement {//页面 protected List<PageElement> mPageElements = new ArrayList<>();//用来保存页面元素 private String name; public PageElement(String name) { this.name = name; } public abstract void addPageElement(PageElement pageElement);//添加栏目或者具体内容 public abstract void rmPageElement(PageElement pageElement);//删除栏目或者具体内容 public abstract void clear();//清空所有元素 public abstract void print(String placeholder);//打印页面结构 public String getName() { return name; } }
4.2 创建叶子节点
叶子节点继承了抽象组件角色,但是由于没有分支,所以一些添加删除操作是实现不了的。叶子节点都是一些具体的内容,比如具体的音乐内容、视屏内容等等。
public class Content extends PageElement {//具体内容 public Content(String name) { super(name); } @Override public void addPageElement(PageElement pageElement) { throw new UnsupportedOperationException("不支持此操作"); } @Override public void rmPageElement(PageElement pageElement) { throw new UnsupportedOperationException("不支持此操作"); } @Override public void clear() { throw new UnsupportedOperationException("不支持此操作"); } @Override public void print(String placeholder) { System.out.println(placeholder + "──" + getName()); } }
4.3 创建树枝节点
树枝节点能够删除添加叶子或树枝。
public class Column extends PageElement {//栏目 public Column(String name) { super(name); } @Override public void addPageElement(PageElement pageElement) { mPageElements.add(pageElement); } @Override public void rmPageElement(PageElement pageElement) { mPageElements.remove(pageElement); } @Override public void clear() { mPageElements.clear(); } /** * @param placeholder 占位符 */ @Override public void print(String placeholder) { //利用递归来打印文件夹结构 System.out.println(placeholder + "└──" + getName()); Iterator<PageElement> i = mPageElements.iterator(); while (i.hasNext()) { PageElement pageElement = i.next(); pageElement.print(placeholder + " "); } } }
4.4 客户端测试:
public void test() { //创建网站根页面 root PageElement root = new Column("网站页面"); //网站页面添加两个栏目:音乐,视屏;以及一个广告内容。 PageElement music = new Column("音乐"); PageElement video = new Column("视屏"); PageElement ad = new Content("广告"); root.addPageElement(music); root.addPageElement(video); root.addPageElement(ad); //音乐栏目添加两个子栏目:国语,粤语 PageElement chineseMusic = new Column("国语"); PageElement cantoneseMusic = new Column("粤语"); music.addPageElement(chineseMusic); music.addPageElement(cantoneseMusic); //国语,粤语栏目添加具体内容 chineseMusic.addPageElement(new Content("十年.mp3")); cantoneseMusic.addPageElement(new Content("明年今日.mp3")); //视频栏目添加具体内容 video.addPageElement(new Content("唐伯虎点秋香.avi")); //打印整个页面的内容 root.print(""); }
输出结果:
└──网站页面 └──音乐 └──国语 ──十年.mp3 └──粤语 ──明年今日.mp3 └──视屏 ──唐伯虎点秋香.avi ──广告
4.5 其他说明:
上面的例子可以看到叶子节点其实并不需要添加删除等方法,但由于叶子节点实际上是依赖了抽象组件角色。一方面,这遵循了依赖倒置原则——依赖抽象,而不依赖具体实现;同时,也保证了叶子节点跟树枝节点具体相同的结构,即他们具有同样的方法接口,能够让客户端以一致的方式去处理单个对象和组合对象。但另一方,这违反了单一职责原则与接口隔离原则,让 叶子节点继承了它本不应该有的方法,并且不太优雅的抛出了 UnsupportedOperationException
。这实际叫透明的组合模式。
5. 安全的组合模式
另外一种组合模式叫安全的组合模式。这种模式客户端在使用的时候必须依赖具体的实现,这违反了依赖倒置原则,但遵循了单一职责原则与接口隔离原则。
5.1 实现
public abstract class PageElement {//页面 private String name; public PageElement(String name) { this.name = name; } //抽象组件角色去掉增删等接口 public abstract void print(String placeholder); public String getName() { return name; } } public class Content extends PageElement {//具体内容,只专注自己的职责 public Content(String name) { super(name); } @Override public void print(String placeholder) { System.out.println(placeholder + "──" + getName()); } } public class Column extends PageElement {//栏目 private List<PageElement> mPageElements = new ArrayList<>();//用来保存页面元素 public Column(String name) { super(name); } public void addPageElement(PageElement pageElement) { mPageElements.add(pageElement); } public void rmPageElement(PageElement pageElement) { mPageElements.remove(pageElement); } public void clear() { mPageElements.clear(); } @Override public void print(String placeholder) { System.out.println(placeholder + "└──" + getName()); Iterator<PageElement> i = mPageElements.iterator(); while (i.hasNext()) { PageElement pageElement = i.next(); pageElement.print(placeholder + " "); } } } public void test() {//客户端测试方法 //依赖具体的实现类Column Column root = new Column("网站页面"); Column music = new Column("音乐"); Column video = new Column("视屏"); PageElement ad = new Content("广告"); root.addPageElement(music); root.addPageElement(video); root.addPageElement(ad); Column chineseMusic = new Column("国语"); Column cantoneseMusic = new Column("粤语"); music.addPageElement(chineseMusic); music.addPageElement(cantoneseMusic); chineseMusic.addPageElement(new Content("十年.mp3")); cantoneseMusic.addPageElement(new Content("明年今日.mp3")); video.addPageElement(new Content("唐伯虎点秋香.avi")); root.print(""); }
5.2 对比
安全的组合模式将职责区分开来放在不同的接口中,这样一来,设计上就比较安全,也遵循了单一职责原则和接口隔离原则,但是也让客户端必须依赖于具体的实现;透明的组合模式,以违反单一职责原则和接口隔离原则来换取透明性,但遵循依赖倒置原则,客户端可以直接依赖于抽象组件即可,将叶子和树枝一视同仁,也就是说,一个元素究竟是枝干节点还是叶子节点,对客户端是透明的。
一方面,我们写代码时应该遵循各种设计原则,但实际上,有些设计模式原则在使用时会发生冲突,这就需要我们根据实际情况去衡量做出取舍,适合自己的才是最好的。
6. 应用场景
- 表示对象的部分-整体层次结构时。
- 从一个整体中能够独立出部分模块或功能时。
7. 优点
- 高层模块(客户端)调用简单。局部和整体都是同样的结构,客户端无需关心是局部还是整体。
- 新增节点容易。无需对现有类库进行任何修改,符合开闭原则。
8. 缺点
- 不同的组合模式实现有不同的缺点,具体看上面的分析。
9. Android中的源码分析
Android源码中,ViewGroup
和 View
就是典型的组合模式。
9.1 View类
View相当与叶子节点,里面没有添加删除View等操作。
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { //具体代码略 }
9.2 ViewGroup类
ViewGroup
实际上是View的子类,同时ViewGroup
中实现了添加删除View等操作,因此可以作为容器存放view。
public abstract class ViewGroup extends android.view.View implements ViewParent, ViewManager {//继承View @Override public void addView(View child, android.view.ViewGroup.LayoutParams params) {//添加view //具体实现代码略 } @Override public void updateViewLayout(View view, android.view.ViewGroup.LayoutParams params) {//更新view //具体实现代码略 } @Override public void removeView(View view) {//移除view //具体实现代码略 } //其他代码略 }
9.3 ViewManager接口
实际上ViewGroup
中的了添加删除View是实现了ViewManager
接口中的方法:
public interface ViewManager{ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view);}
9.4 其他
可以看出ViewGroup 和 View 使用的是安全的组合模式,而不是透明的组合模式。
相关文章阅读
Android的设计模式-设计模式的六大原则
创建型模式:
Android的设计模式-单例模式
Android的设计模式-建造者模式
Android的设计模式-工厂方法模式
Android的设计模式-简单工厂模式
Android的设计模式-抽象工厂模式
Android的设计模式-原型模式
行为型模式:
Android的设计模式-策略模式
Android的设计模式-状态模式
Android的设计模式-责任链模式
Android的设计模式-观察者模式
Android的设计模式-模板方法模式
Android的设计模式-迭代器模式
Android的设计模式-备忘录模式
Android的设计模式-访问者模式
Android的设计模式-中介者模式
Android的设计模式-解释器模式
Android的设计模式-命令模式
结构型模式:
Android的设计模式-代理模式
Android的设计模式-组合模式
Android的设计模式-适配器模式
Android的设计模式-装饰者模式
Android的设计模式-享元模式
更多相关文章
- Android的设计模式-状态模式
- Android的设计模式-适配器模式
- Android的设计模式-享元模式
- Android(安卓)23种设计模式
- element ui级联选择器--懒加载数据
- 标签切换、轮播图及购物车小案例相关知识总结
- 9种设计模式在 Spring 中的运用,记住!
- 想要成为一名优秀的Java程序员,你需要这8个锦囊
- 云计算模式:Docker正掀起个性化商业革命