jtahstu的博客

root@jtahstu.com   Github   英文博客  

最新碎语:以后没事写写小的知识点吧

您的位置:jtahstu的博客 >笔记> 设计模式从入门到放弃 - 组合模式

设计模式从入门到放弃 - 组合模式

组合模式定义

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

    组合模式对单个对象(叶子对象)和组合对象(组合对象)具有一致性,它将对象组织到树结构中,可以用来描述整体与部分的关 系。同时它也模糊了简单元素(叶子对象)和复杂元素(容器对象)的概念,使得客户能够像处理简单元素一样来处理复杂元素,从而使客户程序能够与复杂元素的 内部结构解耦。

    在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。

组合模式结构图如下:


     结构图说明:

         (1)Component:组合中的对象声明接口,在适当情况下实现所有类共有的默认行为,声明一个接口用于访问和管理Component的子组件。在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。(可选)

         (2)Leaf:在组合中表示叶节点,叶节点没有子节点,定义对象的基本行为。

         (3)Composite:定义有子部件的那些部件的行为,存储子部件并在Component接口实现与子部件有关的操作。

         (4)Client:通过Component接口操作组合部件的对象。

     组合模式基本代码:

public abstract class Component {
	public abstract void add(Component c);

	public abstract void remove(Component c);

	public abstract Component getChild(int i);

	public abstract void operation();
}

public class Leaf extends Component {
	public void add(Component c) {
	}

	public void remove(Component c) {
	}

	public Component getChild(int i) {
	}

	public void operation() {
		// 实现代码
	}
}

public class Composite extends Component {
	private ArrayList list = new ArrayList();

	public void add(Component c) {
		list.add(c);
	}

	public void remove(Component c) {
		list.remove(c);
	}

	public Component getChild(int i)
	{
		(Component)list.get(i);
	}

	public void operation() {
		for (Object obj : list) {
			((Component) obj).operation();
		}
	}
}


     可以看出,Composite类型的对象可以包含其它Component类型的对象。换而言之,Composite类型对象可以含有其它的树枝(Composite)类型或树叶(Leaf)类型的对象

     组合模式的实现根据所实现接口的区别分为两种形式,分别称为安全模式和透明模式。组合模式可以不提供父对象的管理方法,但组合模式必须在 合适的地方提供子对象的管理方法(诸如:add、remove等)。安全式的组合模式要求管理聚集的方法只出现在树枝构件类中,而不出现在树叶构件中。与 安全式的组合模式不同的是,透明式的组合模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定的接口。

    通过引入组合模式,Sunny 公司设计的杀毒软件具有良好的可扩展性,在增加新的文件类型时,无须修改现有类库代码,只需增加一个新的文件类作为 AbstractFile 类的子类即可,但是由于在 AbstractFile 中声明了大量用于管理和访问成员构件的方法,例如 add()、remove() 等方法,我们不得不在新增的文件类中实现这些方法,提供对应的错误提示和异常处理。为了简化代码,我们有以下两个解决方案:

解决方案一:将叶子构件的 add()、remove() 等方法的实现代码移至 AbstractFile 类中,由 AbstractFile 提供统一的默认实现,代码如下所示:

//提供默认实现的抽象构件类
abstract class AbstractFile {
    public void add(AbstractFile file) {
        System.out.println("对不起,不支持该方法!");
    }

    public void remove(AbstractFile file) {
        System.out.println("对不起,不支持该方法!");
    }

    public AbstractFile getChild(int i) {
        System.out.println("对不起,不支持该方法!");
        return null;
    }

    public abstract void killVirus();
}
    如果客户端代码针对抽象类 AbstractFile 编程,在调用文件对象的这些方法时将出现错误提示。如果不希望出现任何错误提示,我们可以在客户端定义文件对象时不使用抽象层,而直接使用具体叶子构件本身,客户端代码片段如下所示:
class Client {
    public static void main(String args[]) {
        //不能透明处理叶子构件
        ImageFile file1,file2;
        TextFile file3,file4;
        VideoFile file5;
        AbstractFile folder1,folder2,folder3,folder4;
        //其他代码省略
      }
}

    这样就产生了一种不透明的使用方式,即在客户端不能全部针对抽象构件类编程,需要使用具体叶子构件类型来定义叶子对象。

解决方案二:除此之外,还有一种解决方法是在抽象构件 AbstractFile 中不声明任何用于访问和管理成员构件的方法,代码如下所示:

abstract class AbstractFile {
    public abstract void killVirus();
}
    此时,由于在 AbstractFile 中没有声明 add()、remove() 等访问和管理成员的方法,其叶子构件子类无须提供实现;而且无论客户端如何定义叶子构件对象都无法调用到这些方法,不需要做任何错误和异常处理,容器构件 再根据需要增加访问和管理成员的方法,但这时候也存在一个问题:客户端不得不使用容器类本身来声明容器构件对象,否则无法访问其中新增的 add()、remove() 等方法,如果客户端一致性地对待叶子和容器,将会导致容器构件的新增对客户端不可见,客户端代码对于容器构件无法再使用抽象构件来定义,客户端代码片段如 下所示:


class Client {  
    public static void main(String args[]) {  

        AbstractFile file1,file2,file3,file4,file5;  
        Folder folder1,folder2,folder3,folder4; //不能透明处理容器构件  
        //其他代码省略  
    }  
}  

    在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式:

透明组合模式

    透明组合模式中,抽象构件 Component 中声明了所有用于管理成员对象的方法,包括 add()、remove() 以及 getChild() 等方法,这样做的好处是确保所有的构件类都有相同的接口。在客户端看来,叶子对象与容器对象所提供的方法是一致的,客户端可以相同地对待所有的对象。透明 组合模式也是组合模式的标准形式,虽然上面的解决方案一在客户端可以有不透明的实现方法,但是由于在抽象构件中包含 add()、remove() 等方法,因此它还是透明组合模式,透明组合模式的完整结构如图所示:


    透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的。叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提 供 add()、remove() 以及 getChild() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)。

安全组合模式

    安全组合模式中,在抽象构件 Component 中没有声明任何用于管理成员对象的方法,而是在 Composite 类中声明并实现这些方法。这种做法是安全的,因为根本不向叶子对象提供这些管理成员对象的方法,对于叶子对象,客户端不可能调用到这些方法,这就是解决方 案二所采用的实现方式。安全组合模式的结构如图所示:


    安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能 完全针对抽象编程,必须有区别地对待叶子构件和容器构件。在实际应用中,安全组合模式的使用频率也非常高,在 Java AWT 中使用的组合模式就是安全组合模式。

实例UML图

java演示代码

//MyElement.java
public abstract class MyElement {
	public abstract void eat();
}

//Apple.java
public class Apple extends MyElement {
	public void eat() {
		System.out.println("吃苹果!");
	}
}

//Banana.java
public class Banana extends MyElement {
	public void eat() {
		System.out.println("吃香蕉!");
	}
}

//Pear.java
public class Pear extends MyElement {
	public void eat() {
		System.out.println("吃梨子!");
	}
}

//Plate.java 容器构建类
import java.util.ArrayList;

public class Plate extends MyElement {
	private ArrayList list = new ArrayList();

	public void add(MyElement element) {
		list.add(element);
	}

	public void delete(MyElement element) {
		list.remove(element);
	}

	public void eat() {
		for (Object object : list) {
			((MyElement) object).eat();
		}
	}
}

//Client.java
public class Client {
	public static void main(String a[]) {
		MyElement obj1, obj2, obj3, obj4, obj5;
		Plate plate1, plate2, plate3;

		obj1 = new Apple();
		obj2 = new Pear();
		plate1 = new Plate();
		plate1.add(obj1);
		plate1.add(obj2);

		obj3 = new Banana();
		obj4 = new Banana();
		plate2 = new Plate();
		plate2.add(obj3);
		plate2.add(obj4);

		obj5 = new Apple();
		plate3 = new Plate();
		plate3.add(plate1);
		plate3.add(plate2);
		plate3.add(obj5);

		plate1.eat();
		plate2.eat();
		plate3.eat();
	}
}

PHP代码演示

<?php

abstract class MenuComponent {
	public $name;
	public abstract function getName();
	public abstract function add(MenuComponent $menu);
	public abstract function remove(MenuComponent $menu);
	public abstract function getChild($i);
	public abstract function show();
}

class MenuItem extends MenuComponent {
	public function __construct($name) {
		$this -> name = $name;
	}

	public function getName() {
		return $this -> name;
	}

	public function add(MenuComponent $menu) {
		return false;
	}

	public function remove(MenuComponent $menu) {
		return false;
	}

	public function getChild($i) {
		return null;
	}

	public function show() {
		echo " |-" . $this -> getName() . "\n";
	}

}

class Menu extends MenuComponent {
	public $menuComponents = array();
	public function __construct($name) {
		$this -> name = $name;
	}

	public function getName() {
		return $this -> name;
	}

	public function add(MenuComponent $menu) {
		$this -> menuComponents[] = $menu;
	}

	public function remove(MenuComponent $menu) {
		$key = array_search($menu, $this -> menuComponents);
		if ($key !== false)
			unset($this -> menuComponents[$key]);
	}

	public function getChild($i) {
		if (isset($this -> menuComponents[$i]))
			return $this -> menuComponents[$i];
		return null;
	}

	public function show() {
		echo ":" . $this -> getName() . "\n";
		foreach ($this->menuComponents as $v) {
			$v -> show();
		}
	}

}

class testDriver {
	public function run() {
		$menu1 = new Menu('文学');
		$menuitem1 = new MenuItem('绘画');
		$menuitem2 = new MenuItem('书法');
		$menuitem3 = new MenuItem('小说');
		$menuitem4 = new MenuItem('雕刻');
		$menu1 -> add($menuitem1);
		$menu1 -> add($menuitem2);
		$menu1 -> add($menuitem3);
		$menu1 -> add($menuitem4);
		$menu1 -> show();
	}

}

$test = new testDriver();
$test -> run();

组合模式优缺点

    优点

      1、可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易

      2、客户端调用简单,客户端可以一致的使用组合结构或其中单个对象

      3、定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构

      4、更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码

    缺点

      1、使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联。

组合模式适用场景

      1、需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们

      2、让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节

组合模式总结

      1、 组合模式用于将多个对象组合成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(叶子对象)和组合对象(容器对象)的使用具有一致性。

      2、 组合对象的关键在于它定义了一个抽象构建类,它既可表示叶子对象,也可表示容器对象,客户仅仅需要针对这个抽象构建进行编程,无须知道他是叶子对象还是容器对象,都是一致对待

      3、 组合模式虽然能够非常好地处理层次结构,也使得客户端程序变得简单,但是它也使得设计变得更加抽象,而且也很难对容器中的构件类型进行限制,这会导致在增加新的构件时会产生一些问题。

参考

  1. 百度百科 http://baike.baidu.com/view/3591789.htm

  2. 博客园 http://www.cnblogs.com/peida/archive/2008/09/09/1284686.html

  3. 博客园2 http://www.cnblogs.com/chenssy/p/3299719.html

  4. 课本

  5. 极客学院 http://wiki.jikexueyuan.com/project/design-pattern-structurized/composite-one.html

  6. CSDN http://blog.csdn.net/uuleaf/article/details/7642897

---

本文章采用 知识共享署名2.5中国大陆许可协议 进行许可,欢迎转载,演绎或用于商业目的。

---

二维码加载中...

扫一扫移动端访问O(∩_∩)O

发表评论

35 + 35 =
路人甲 表情
看不清楚?点图切换 Ctrl+Enter快速提交
正在加载中……