Java设计模式----组合模式

# 标签: 读博客


对作者的源码进行了重构,加入了自己的分析和总结。


本文转载自:http://blog.csdn.net/guolin_blog/article/details/9153753

听说你们公司最近新推出了一款电子书阅读应用,市场反应很不错,应用里还有图书商城,用户可以在其中随意选购自己喜欢的书籍。你们公司也是对此项目高度重视,加大了投入力度,决定给此应用再增加点功能。

好吧,你也知道你是逃不过此劫了,没过多久你的leader就找到了你。他告诉你目前的应用对每本书的浏览量和销售量做了统计,但现在想增加对每个书籍分类的浏览量和销售量以及所有书籍总的浏览量和销售量做统计的功能,希望你可以来完成这项功能。

领导安排的工作当然是推脱不掉的,你只能硬着头皮上了,不过好在这个功能看起来也不怎么复杂。

你比较喜欢看小说,那么就从小说类的统计功能开始做起吧。首先通过getAllNovels方法可以获取到所有的小说名,然后将小说名传入getBrowseCount方法可以得到该书的浏览量,将小说名传入getSaleCount方法可以得到该书的销售量。你目前只有这几个已知的API可以使用,那么开始动手吧!

public int getNovelsBrowseCount() {  
    int browseCount = 0;  
    List<String> allNovels = getAllNovels();  
    for (String novel : allNovels) {  
        browseCount += getBrowseCount(novel);  
    }  
    return browseCount;  
}  
  
public int getNovelsSaleCount() {  
    int saleCount = 0;  
    List<String> allNovels = getAllNovels();  
    for (String novel : allNovels) {  
        saleCount += getSaleCount(novel);  
    }  
    return saleCount;  
}

很快你就写下了以上两个方法,这两个方法都是通过获取到所有的小说名,然后一一计算每本小说的浏览量和销售量,最后将结果相加得到总量。

小说类的统计就完成了,然后你开始做计算机类书籍的统计功能,代码如下所示:

public int getComputerBooksBrowseCount() {  
    int browseCount = 0;  
    List<String> allComputerBooks = getAllComputerBooks();  
    for (String computerBook : allComputerBooks) {  
        browseCount += getBrowseCount(computerBook);  
    }  
    return browseCount;  
}  
  
public int getComputerBooksSaleCount() {  
    int saleCount = 0;  
    List<String> allComputerBooks = getAllComputerBooks();  
    for (String computerBook : allComputerBooks) {  
        saleCount += getSaleCount(computerBook);  
    }  
    return saleCount;  
}

除了使用了getAllComputerBooks方法获取到所有的计算机类书名,其它的代码基本和小说统计中的是一样的。

现在你才完成了两类书籍的统计功能,后面还有医学类、自然类、历史类、法律类、政治类、哲学类、旅游类、美食类等等等等书籍。你突然意识到了一些问题的严重性,工作量大倒还不算什么,但再这么写下去,你的方法就要爆炸了,这么多的方法让人看都看不过来,别提怎么使用了。

这个时候你只好向你的leader求助了,跟他说明了你的困惑。只见你的leader思考了片刻,然后自信地告诉你,使用组合模式不仅可以轻松消除你的困惑,还能出色地完成功能。

他立刻向你秀起了编码操作,首先定义一个Statistics接口,里面有两个待实现方法:

public interface Statistics {  
  
    int getBrowseCount();  
      
    int getSalesCount();  
  
}

然后定义一个用于统计小说类书籍的NovelStatistics类,实现接口中定义的两个方法:

public class NovelStatistics implements Statistics {  
  
    @Override  
    public int getBrowseCount() {  
        int browseCount = 0;  
        List<String> allNovels = getAllNovels();  
        for (String novel : allNovels) {  
            browseCount += getBrowseCount(novel);  
        }  
        return browseCount;  
    }  
  
    @Override  
    public int getSalesCount() {  
        int saleCount = 0;  
        List<String> allNovels = getAllNovels();  
        for (String novel : allNovels) {  
            saleCount += getSaleCount(novel);  
        }  
        return saleCount;  
    }  
  
}

在这两个方法中分别统计了小说类书籍的浏览量和销售量。那么同样的方法,你的leader又定义了一个ComputerBookStatistics类用于统计计算机类书籍的浏览量和销售量:

public class ComputerBookStatistics implements Statistics {  
  
    @Override  
    public int getBrowseCount() {  
        int browseCount = 0;  
        List<String> allComputerBooks = getAllComputerBooks();  
        for (String computerBook : allComputerBooks) {  
            browseCount += getBrowseCount(computerBook);  
        }  
        return browseCount;  
    }  
  
    @Override  
    public int getSalesCount() {  
        int saleCount = 0;  
        List<String> allComputerBooks = getAllComputerBooks();  
        for (String computerBook : allComputerBooks) {  
            saleCount += getSaleCount(computerBook);  
        }  
        return saleCount;  
    }  
  
}

这样将具体的统计实现分散在各个类中,就不会再出现你刚刚那种方法爆炸的情况了。不过这还没开始真正使用组合模式呢,好戏还在后头,你的leader吹嘘道。

再定义一个MedicalBookStatistics类实现了Statistics接口,用于统计医学类书籍的浏览量和销售量,代码如下如示:

public class MedicalBookStatistics implements Statistics {  
  
    @Override  
    public int getBrowseCount() {  
        int browseCount = 0;  
        List<String> allMedicalBooks = getAllMedicalBooks();  
        for (String medicalBook : allMedicalBooks) {  
            browseCount += getBrowseCount(medicalBook);  
        }  
        return browseCount;  
    }  
  
    @Override  
    public int getSalesCount() {  
        int saleCount = 0;  
        List<String> allMedicalBooks = getAllMedicalBooks();  
        for (String medicalBook : allMedicalBooks) {  
            saleCount += getSaleCount(medicalBook);  
        }  
        return saleCount;  
    }  
  
}

不知道你发现了没有,计算机类书籍和医学类书籍其实都算是科技类书籍(hehehe呵呵)它们是可以组合在一起的。这个时候你的leader定义了一个TechnicalStatistics类用于对科技这一组合类书籍进行统计:

public class TechnicalStatistics implements Statistics {  
      
    private List<Statistics> statistics = new ArrayList<Statistics>();  
      
    public TechnicalStatistics() {  
        statistics.add(new ComputerBookStatistics());  
        statistics.add(new MedicalBookStatistics());  
    }  
  
    @Override  
    public int getBrowseCount() {  
        int browseCount = 0;  
        for (Statistics s : statistics) {  
            browseCount += s.getBrowseCount();  
        }  
        return browseCount;  
    }  
  
    @Override  
    public int getSalesCount() {  
        int saleCount = 0;  
        for (Statistics s : statistics) {  
            saleCount += s.getBrowseCount();  
        }  
        return saleCount;  
    }  
  
}

可以看到,由于这个类是组合类,和前面几个类还是有不少区别的。首先TechnicalStatistics中有一个构造函数,在构造函数中将计算机类书籍和医学类书籍作为子分类添加到statistics列表当中,然后分别在getBrowseCount和getSalesCount方法中遍历所有的子分类,计算出它们各自的浏览量和销售量,然后相加得到总额返回。

组合模式的扩展性非常好,没有各种条条框框,想怎么组合就怎么组合,比如所有书籍就是由各个分类组合而来的,你的leader马上又向你炫耀了统计所有书籍的浏览量和销售量的办法。

定义一个AllStatistics类实现了Statistics接口,具体代码如下所示:

public class AllStatistics implements Statistics {  
  
    private List<Statistics> statistics = new ArrayList<Statistics>();  
  
    public AllStatistics() {  
        statistics.add(new NovelStatistics());  
        statistics.add(new TechnicalStatistics());  
    }  
  
    @Override  
    public int getBrowseCount() {  
        int browseCount = 0;  
        for (Statistics s : statistics) {  
            browseCount += s.getBrowseCount();  
        }  
        return browseCount;  
    }  
  
    @Override  
    public int getSalesCount() {  
        int saleCount = 0;  
        for (Statistics s : statistics) {  
            saleCount += s.getBrowseCount();  
        }  
        return saleCount;  
    }  
  
}

在AllStatistics的构造函数中将小说类书籍和科技类书籍作为子分类添加到了statistics列表当中,目前你也就只写好了这几个分类。然后使用同样的方法在getBrowseCount和getSalesCount方法中统计出所有书籍的浏览量和销售量。

当前组合结构的示意图如下:

 Java设计模式----组合模式_第1张图片

new TechnicalStatistics().getBrowseCount();

而获取所有书籍的总销量,你只需要调用:

new AllStatistics().getSalesCount();

当然你后面还可以对这个组合结构随意地改变,添加各种子分类书籍,而且子分类的层次结构可以任意深,正如前面所说,组合模式的扩展性非常好。

你的leader告诉你,目前他写的这份代码重复度比较高,其实还可以好好优化一下的,把冗余代码都去除掉。当然这个任务就交给你来做了,你的leader可是大忙人,早就一溜烟跑开了。

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

我的补充:


不去评论作者的文章了,我只针对问题说看法:(专注谈技术问题)

组合模式,在设计模式中属于:结构型,也就是有鬼的地方,有花样,有改进的地方主要在它的机构组织。

组合是特殊的聚合,是一种整体-部分的强关系:

不平等的关系: 整体-部分不同于关联这种平等关系,整体-部分强调了整体和部分的区别;

强关系:     意味着,整体和部分的生命周期是相关的。部分离开整体无法独立存在,(聚合则不强调生命周期的概念或者弱化这种概念)。再具体的总结就是,部分的生命周期是由整体负责的,在整体内部负责控制部分的生存死亡,(聚合则是把生死权交给外部或者部分自己,而整体则不管了)。

(生命周期的相关性不是表面上看的,而是通过整体是否能控制部分的生命周期来判断的)

(有些人说眼睛和身体是组合,键盘和电脑是聚合,我去,如果电脑在内部控制了键盘的创建,销毁(你别说哪天还真实现了这种方案呢?很多实验室的很棒的发明就没有推向市场,别说你啥都知道啊),那它们还是聚合么? 别看表面,看本质啊,看它们内部是怎么处理的,然后再去判断,(看TM的源码---linus)好了我只能说这么多了)


首先对于本文这个案例,统计所有书籍,然后递归统计下属分类的图书。

应该从大头入手,把“统计”这个operation()单独拉出来。

用接口也好,继承也好,总是要体现大的包含中的,中的包含小的。。。以此类推。

思维顺序不要错,还有,不要忽略它是结构型,重点是组织好结构,对于行为(方法)单独扔出来吧。

我举一个很简单的比方:我们去统计某个文件夹下的文件数目,顺序是这样的:(linux中)

home目录---->merlin用户目录---->workspace目录----->eclipse目录

其中每一层目录都是可以统计它其中的文件数目的。(统计这个方法是一致的)------这说明啥?

这才是组合模式的精华: (操作唯一,然后结构组合)

对单个对象,对组合对象,我的操作都是一样的,根本没有啥区别!

(我统计home目录这个大的组合对象下的文件数目用count()方法,找eclipse目录这个单个对象里的文件数目,我也用count()方法)


开始重写,乱讲,写之前一定要先整理一下思路,抓一抓不变以及可变因素,不然上去一些就错!

现在可变因素是:

    getAllNovels()  

    getAllComputers()

    getAllCategories()

     ..... 一些列不同的API  (mb你从数据库里面查出来怎么就搞了这么多方法了,肯定是dao层没有重构好!sorry,应该说service层用了泛型,还会有这些问题么?)



现在不变的是:

getBrowseCount(name)

getSaleCount(name)


要达到的效果是:

统计 某个具体分类和 大的分类的方法是一样的。 (单个对象和组合对象的使用具有唯一性)

初步设想为 getCategorySaleCount和getgetCategoryBrowseCount

先写一个通用接口:

public interface Statistics {
	public  int getCategoryBrowseCount();
	public  int getCategorySaleCount();
	//放入default类中吧,放接口中就成public的了,不希望外部调用它
	//public  List<String> getBooksNames(); 
	public boolean hasSubComponent();  //看当前类是部分还是整体
}

再写一个默认实现类:(免去子类重写的麻烦)

//千万别用这个类去实例化啊!
public class DefaultStatistics implements Statistics {
	
	//如果是整体那么它必有部分,在它的构造函数中初始化,没有的话直接赋值为Null
	protected List<Statistics> component = null;  

	/*-----------------需要子类重写的方法 start()-----------------------*/
	
	/**只有"部分"才需要重写它  (整体的话,让它返回null)
	 * 我们的目的是统计数量,并不是为了获得名字 (不需要外部调用它)
	 */
	protected List<String> getBooksNames() {
		return null;
	}

	/**默认是"部分"
	 * 返回true表示是"整体" 则只重写该方法返回true就好,不用重写上面的getBooksNames()
	 * 整体记得在构造函数中初始化component对象
	 */
	
	@Override
	public boolean hasSubComponent() {
		return false;
	}
	/*-----------------需要重写的方法 end()-----------------------------*/
	
	
	
	
	
	
	
	
	/**下面子类不需要重写**/
	
	@Override
	public int getCategorySaleCount() {
		int saleCount = 0;  
		//通过hasSubComponent()和getBooksNames()双判断
		if(hasSubComponent()){ //有子类直接用子类的方法
			if(component!=null){//避免没有在子类构造函数中初始化compenent
				for(Statistics temp: component){
					saleCount += temp.getCategorySaleCount();
				}
			}
		}else{
			List<String> allBooks = getBooksNames();  
			if(allBooks!=null){  //避免子类没有重写getBooksNames()
		        for (String Book : allBooks) {  
		            saleCount += Data.getSaleCount(Book);  
		        }
			}
		}
        return saleCount;  
	}

	@Override
	public int getCategoryBrowseCount() {
		int browseCount = 0; 
		
		if(!hasSubComponent()){
			List<String> allBooks = getBooksNames();  
			for (String book : allBooks) {  
				browseCount += Data.getBrowseCount(book);  
			}  
		}
		else{
			if(component!=null){
				for(Statistics temp: component){
					browseCount += temp.getCategoryBrowseCount();
				}
			}
		}
        return browseCount;  
	}
}

为了后面测试方面,我们还要实现一些原文作者声明过的可用API,

getBrowseCount(name)

getSaleCount(name)

  getAllNovels()  

  getAllComputers()

  getAllMedicals()等为测试提供原始数据

public class Data {
	
    public static List<String> getAllNovels(){
    	ArrayList<String> data = new ArrayList<>();  //jdk1.7
    	data.add("三国演义");
    	data.add("水浒传");
    	data.add("红楼梦");
    	return data;
    }

    public static List<String> getAllComputers(){
    	ArrayList<String> data = new ArrayList<>();  //jdk1.7
    	data.add("电脑报");
    	data.add("通信周刊");
    	data.add("程序员杂志");
    	data.add("Unix高级编程");
    	return data;
    }

    public static List<String> getMedicals(){
    	ArrayList<String> data = new ArrayList<>();  //jdk1.7
    	data.add("内科研究");
    	data.add("外科研究");
    	return data;
    }
	
	
	/**
	 * 随机返回一个数字作为 浏览量 (1000以内)写固定数字,后面test时候要做验证
	 */
	public static int getBrowseCount(String bookName){
		
		//return new Random().nextInt(1000);
		return 500;
	}

	/**
	 * 随机返回一个数字作为 销售量---写固定数字,后面test时候要做验证
	 */
	public static int getSaleCount(String bookName){
		
		//return new Random().nextInt(1000);
		return 1000;
	}
	
	/**
	 * 测试一下随机数(100以内),后面test的时候要做验证
	 * @param args
	 */
	public static void main(String[] args){
		System.out.println(getBrowseCount("gugu"));
		
	}

}


好了下面,分别做novel,medical,tech(即novel+medical),以及综合AllCategories的统计:

public class NovelsStatistics extends DefaultStatistics {

	@Override
	protected List<String> getBooksNames() {  //千万别写成public了
		return new ArrayList<String>(Data.getAllNovels());
	}

}
public class ComputerStatistics extends DefaultStatistics { 

	@Override
	protected List<String> getBooksNames() {//不要写成public的了
		
		return Data.getAllComputers();
	}

}
public class MedicalStatistics extends DefaultStatistics {

	@Override
	protected List<String> getBooksNames() {  //不要写成public的了
		
		return Data.getMedicals();
	}

}
//
public class TechnicalStatistics extends DefaultStatistics {
	
	//private List<Statistics> component = new ArrayList<>();
	
	public  TechnicalStatistics(){
		component = new ArrayList<>(); //父类DefaultStatistics中已经声明过了
		
		component.add( new ComputerStatistics()); //这里放入的实际是ComputerStatistics
		component.add( new NovelsStatistics()); //实际放入的是NovelStatistics
	}
	
	@Override
	public boolean hasSubComponent() {
		return true;
	}
	

	
	
	/**
	 * public  int getCategoryBrowseCount();
	 * public  int getCategorySaleCount();
	 * public  List<String> getBooksNames();  
	 * 全部直接用父类的就好了(父类已经做好了判断和处理,最大化的重用了)
	 */

}
public class AllCategoryStatistics extends DefaultStatistics {
	
	public  AllCategoryStatistics(){
		
		component = new ArrayList<>();
		component.add(new TechnicalStatistics());  //小整体
		component.add(new MedicalStatistics());   //部分
	}

	@Override
	public boolean hasSubComponent() {
		
		return true;
	}

}


最后来测试一下就可以了:

public class Test {

	/**
	 * 调用类
	 */
	public static void main(String[] args) {
		
		/**
		 * 查看一下所有的类别
		 */
		
		AllCategoryStatistics allBooks = new AllCategoryStatistics();
		//allBooks.getBooksNames(); //调用不了吧haha
		System.out.println("所有的books浏览总量-----"+allBooks.getCategoryBrowseCount());
		System.out.println("所有的books销售总量-----"+allBooks.getCategorySaleCount());
		
		
		/**
		 * 看一下次级分类
		 */
		TechnicalStatistics techs = new TechnicalStatistics();
		System.out.println("techs浏览总量-----"+techs.getCategoryBrowseCount());
		System.out.println("techs销售总量-----"+techs.getCategorySaleCount());
		
		
		
		
		/**
		 * 查看一下医药的类别咯
		 */
		MedicalStatistics medicals = new MedicalStatistics();
		System.out.println("医药的浏览总量是:----"+medicals.getCategoryBrowseCount());
		System.out.println("医药的销售总量是:----"+medicals.getCategorySaleCount());
		//novels.getBooksNames();  //调用不了吧---故意隐藏起来不让调用
		
		
		System.out.println("--------------验证一下是否正确-----------------");
		System.out.println("浏览总量是否一致:");
		System.out.println(
				allBooks.getCategoryBrowseCount()==techs.getCategoryBrowseCount()
				+medicals.getCategoryBrowseCount());
		
		System.out.println("销售总量是否一致:");
		System.out.println(
				allBooks.getCategorySaleCount()==techs.getCategorySaleCount()
				+medicals.getCategorySaleCount());
		
	}

}

结果如下:

Java设计模式----组合模式_第2张图片




你可能感兴趣的:(Java设计模式----组合模式)