盗用网上一个大牛的栗子,杀毒,这个应用场景可以非常形象的说明我们这里将要讲到的模式。
我要做一个文件管理工具,可以针对指定类型的文件进行特有操作,还可以对文件夹、word文档、jpg图片进行病毒查杀,我们正常想到的方法就是创建各种文件类型的类,然后再文件夹类中定义两个容器分别存放文件夹下所有word文件和jpg图片对应的类实例。代码如下:
//jpg类
public class CommonJpg {
private String m_name;
public CommonJpg(String strName)
{
m_name = strName;
}
public void setName(String strName)
{
m_name = strName;
}
public String getName()
{
return m_name;
}
public void killVirus()
{
System.out.printf("begin kill %s jpg virus !\n", m_name);
}
}
//word类
public class CommonWord {
private String m_name;
public CommonWord(String strName)
{
m_name = strName;
}
public void setName(String strName)
{
m_name = strName;
}
public String getName()
{
return m_name;
}
public void killVirus()
{
System.out.printf("begin kill %s word virus !\n", m_name);
}
}
//文件夹类
public class CommonFolder {
private String m_name;
private HashMap <String,CommonJpg> m_myJpg;
private HashMap <String,CommonWord> m_myWord;
private HashMap <String,CommonFolder> m_myFolder;
public CommonFolder(String strName)
{
m_name = strName;
m_myJpg = new HashMap <String,CommonJpg>();
m_myWord = new HashMap <String,CommonWord>();
m_myFolder = new HashMap <String,CommonFolder>();
}
public void setName(String strName)
{
m_name = strName;
}
public String getName()
{
return m_name;
}
public void addJpg(CommonJpg jpg)
{
m_myJpg.put(jpg.getName(), jpg);
}
public CommonJpg getJpg(String strName)
{
return m_myJpg.get(strName);
}
public void addWord(CommonWord word)
{
m_myWord.put(word.getName(), word);
}
public CommonWord getWord(String strName)
{
return m_myWord.get(strName);
}
public void addFolder(CommonFolder folder)
{
m_myFolder.put(folder.getName(), folder);
}
public CommonFolder getFolder(String strName)
{
return m_myFolder.get(strName);
}
public void killVirus()
{
System.out.printf("begin kill %s folder virus! \n", m_name);
Set<String> set = m_myJpg.keySet();
for(int i=0; i< set.size(); i++)
{
CommonJpg jpg = m_myJpg.get(set.toArray()[i]);
jpg.killVirus();
}
set = m_myWord.keySet();
for(int i=0; i< set.size(); i++)
{
CommonWord word = m_myWord.get(set.toArray()[i]);
word.killVirus();
}
set = m_myFolder.keySet();
for(int i=0; i< set.size(); i++)
{
CommonFolder folder = m_myFolder.get(set.toArray()[i]);
folder.killVirus();
}
}
}
//应用场景
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
CommonFolder geren = new CommonFolder("geren");
CommonJpg shenfenzheng = new CommonJpg("shenfenzheng");
geren.addJpg(shenfenzheng);
CommonWord jianli = new CommonWord("jianli");
geren.addWord(jianli);
CommonFolder gequ = new CommonFolder("gequ");
CommonJpg xusong = new CommonJpg("xusong");
gequ.addJpg(xusong);
CommonWord dizhi = new CommonWord("dizhi");
gequ.addWord(dizhi);
geren.addFolder(gequ);
geren.killVirus();
}
}
//结果
begin kill geren folder virus!
begin kill shenfenzheng jpg virus !
begin kill jianli word virus !
begin kill gequ folder virus!
begin kill xusong jpg virus !
begin kill dizhi word virus !
这样即可实现 文件夹/word文件/jpg图片 的杀毒,但是我自己在写这个代码的时候都很别扭,因为 文件夹/word文件/jpg图片 三个类内部实现有很大相似之处,都有名称的设置和获取,都有杀毒操作;因为文件夹类需要对各种文件进行维护,导致其过于臃肿,不符合单一职责原则;如果想再添加其他类型的文件类,就需要不停的修改此文件夹类,不符合开闭原则;文件夹直接依赖关联各种文件类,导致两层类的耦合过高,不好扩展,不符合依赖倒置原则;反正就是不怎么样。
考虑到后面会不断的增加各种类型的文件,所以我们可以对这个易变点进行封装,将文件的维护操作进行抽象,使其更容易扩展。这里面有两种类,一种是用来维护具体文件类的文件夹类,我们叫它枝干类,还有一种是具体的文件类,它们都实现了一些相同的功能,即杀毒操作,我们叫他们叶子类。杀毒操作需要文件/文件夹以树形的结构遍历实现,所以我们抽象出这两种类:抽象出叶子类实现了通用功能(杀毒);抽象出来的枝干类就可以使用这个抽象叶子类来对所有类型的叶子(文件)进行统一维护(一个容器,一个add,一个get即可),并且提供函数实现所有抽象叶子类的通用功能(eg:杀毒,枝干类也可能需要实现这些功能,具体看需求)的递归调用,这样后面再有新类型的叶子类加入进来只需要add一下就是先了杀毒。
在上面的方法的基础上还可以继续封装,上面方法中,枝干类需要维护两个容器,一个容器用来存储抽线叶子类,一个容器用来存储其子枝干类,这样就需要枝干类维护两个容器,实现两套接口来分别实现枝干类和叶子类的add/get/remove等操作。其实不难发现,无论是叶子类还是枝干类都需要实现通用功能(杀毒)接口,那么何不将两种类进一步抽象封装,封装成一个类来实现add/get/remove/通用功能,这样就真正意义上的实现了用户透明,用户不用管是枝干还是什么类型的叶子,只需要直接add并杀毒就行了。但是这样也有一个弊端,就是具体叶子类在实现这个抽象类的时候不能误操作add/get/remove这些维护操作接口。
以上两种实现思路都是我所说的组合模式。
组合模式将对象组合成树形结构以表示‘部分-整体’的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
一、特征
1、部分-整体的层次结构:即,这种模式一般针对以下场景:某些功能需要各种类以树形结构遍历实现,这些类针对这些功能有相同的调用接口。
2、一致性:即,这种模式下,单个对象,组合对象的调用方法一样,对于用户是透明的。我觉得这里的一致性更是指不同的叶子对象和组合对象的待用方法一样,因为这种模式下更适合添加新类型的叶子结点进行扩展
二、作用:
某些应用场景里,某些功能需要各种类以树形结构遍历实现,使用此模型从而达到新类的添加更方便,用户使用更简洁的目的。
三、实现:
这里想了以下为什么要将叶子结点和子枝干结点的实际维护操作放到Composite具体实现中,而不是直接放在Component虚基类中,这样做可以更容易的实现叶子结点和枝干结点的解耦。
我们来看看上面的例子如何实现:
//component
public abstract class Component {
protected String m_name;
public Component(String strName)
{
m_name = new String(strName);
}
public void setName(String strName)
{
m_name = strName;
}
public String getName()
{
return m_name;
}
public void addComponent(Component component)
{
}
public Component getComponent(String strName)
{
return null;
}
abstract public void killVirus();
}
//文件夹
import java.util.HashMap;
import java.util.Set;
public class Composite extends Component{
private HashMap <String,Component> myComponent;
public Composite(String strName)
{
super(strName);
myComponent = new HashMap <String,Component>();
}
public void addComponent(Component component)
{
myComponent.put(component.getName(), component);
}
public Component getComponent(String strName)
{
return myComponent.get(strName);
}
@Override
public void killVirus() {
// TODO Auto-generated method stub
System.out.printf("begin kill %s folder virus! \n", m_name);
Set<String> set = myComponent.keySet();
for(int i=0; i< set.size(); i++)
{
Component com = myComponent.get(set.toArray()[i]);
com.killVirus();
}
}
}
//图片
public class LeafJpg extends Component{
public LeafJpg(String strName)
{
super(strName);
}
@Override
public void killVirus() {
// TODO Auto-generated method stub
System.out.printf("begin kill %s jpg virus !\n", m_name);
}
}
//word
public class LeafWord extends Component{
public LeafWord(String strName)
{
super(strName);
}
@Override
public void killVirus() {
// TODO Auto-generated method stub
System.out.printf("begin kill %s word virus !\n", m_name);
}
}
//应用场景
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Component geren = new Composite("geren");
Component shenfenzheng = new LeafJpg("shenfenzheng");
geren.addComponent(shenfenzheng);
Component jianli = new LeafWord("jianli");
geren.addComponent(jianli);
Component gequ = new Composite("gequ");
Component xusong = new LeafJpg("xusong");
gequ.addComponent(xusong);
Component dizhi = new LeafWord("dizhi");
gequ.addComponent(dizhi);
geren.addComponent(gequ);
geren.killVirus();
}
}
上面实现的代码,如果我们想考虑dll文件的杀毒,我们只需要创建一个派生自Component 的dll文件类,实现其杀毒操作,在应用场景里就可以单独查杀一个dll文件对象,也可以将其加入到文件夹中杀毒,这样不会影响到文件夹类,扩展很方便。同时应用场景对所有类型的对象都使用一套操作接口即可。
四、缺点
缺点很明显,继承造成了业务逻辑的复杂。
组合模式适用于树形结构中叶子类型易变而接口不易变得情况,如果所有叶子的通用功能变化,那么此模式反而会加重开发者的负担。