Java设计模式设计模式

《设计模式》组合模式

2019-08-25  本文已影响2人  敏捷Studio

基本介绍

定义

将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

介绍

文件夹树形结构

UML类图

组合模式UML类图

角色说明:

具体实现(透明的组合模式)

下面以网站页面为例子,一个页面有多个栏目以及内容,栏目又可以包含子栏目以及具体内容,这就是一个树形结构。如下图:

网站页面结构图

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

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

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、客户端测试:

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
   ──广告

5、其他说明:

上面的例子可以看到叶子节点其实并不需要添加删除等方法,但由于叶子节点实际上是依赖了抽象组件角色。一方面,这遵循了依赖倒置原则——依赖抽象,而不依赖具体实现;同时,也保证了叶子节点跟树枝节点具体相同的结构,即他们具有同样的方法接口,能够让客户端以一致的方式去处理单个对象和组合对象。但另一方,这违反了单一职责原则接口隔离原则,让 叶子节点继承了它本不应该有的方法,并且不太优雅的抛出了 UnsupportedOperationException 。这实际叫透明的组合模式

安全的组合模式

另外一种组合模式叫安全的组合模式。这种模式客户端在使用的时候必须依赖具体的实现,这违反了依赖倒置原则,但遵循了单一职责原则接口隔离原则

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("");
}

2、对比

模式总结

应用场景

优点

缺点

Android中的源码分析

Android源码中,ViewGroupView就是典型的组合模式。

1、View
View相当与叶子节点,里面没有添加删除View等操作。

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
  // 具体代码略
}

2、ViewGroup
ViewGroup实际上是View的子类,同时ViewGroup中实现了添加删除View等操作,因此可以作为容器存放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) {
    // 具体实现代码略
  }

  // 其他代码略
}

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

4、其他
可以看出ViewGroupView 使用的是安全的组合模式,而不是透明的组合模式。

上一篇 下一篇

猜你喜欢

热点阅读