设计模式--组合模式
来源:互联网 发布:台湾人活跃于中国网络 编辑:程序博客网 时间:2024/05/17 04:33
组合模式:允许你将对象组合成树形结构来表现"整体/部分"层次结构。组合能让客户以一致的方式处理个别对象以及对象组合;
知识点的梳理:
- 组合模式提供一个结构,可同时包容个别对象和组合对象;
- 组合模式允许客户对个别对象以及组合对象一视同仁;
- 组合结构内的任意对象称为组件,组件可以是组合,也可以是叶节点;
- 在实现组合模式时,有许多设计上的折衷。需要根据需要平衡透明性和安全性;
- 在餐厅,煎饼屋的基础上,增加咖啡厅!
- 先来看看咖啡厅的菜单,将代码整合进案例
import java.util.Hashtable;
import java.util.Iterator;
//咖啡厅菜单实现Menu接口,所以女招待使用咖啡厅菜单的方法,就和其他的两个菜单没有两样
public class CafeMenuimplements Menu{
//菜单项使用散列表存储
HashtablemenuItems = new Hashtable();
public CafeMenu(){
addItem("Veggie Burger and Air Fries"
,"Veggie burger on a whole wheat bun,lettuce,tomato,and fries"
,true
,3.99);
addItem("Soup of the day"
,"A cup of the soup of the day,with a side salad"
,false
,3.69);
addItem("Burrito"
,"A large burrito,with whole pinto beans,salsa,guacamole"
,true
,4.29);
}
//创建新的菜单项,并将它加入到散列列表中
private void addItem(Stringname, String description, booleanvegetarian, double price) {
MenuItemmenuItem = new MenuItem(name,description,vegetarian,price);
menuItems.put(menuItem.getName(),menuItem);
}
//实现createIterator方法。
@Override
public Iterator createIterator() {
//在这里取值的部分的迭代器
returnmenuItems.values().iterator();
}
}
- 让女招待认识咖啡厅
public class Waitress {
MenupancakeHouseMenu;
MenudinerMenu;
MenucafeMenu;//咖啡厅菜单会和其他菜单一起被传入女招待的构造器中,然后记录在一个实例变量中
public Waitress(MenupancakeHouseMenu,Menu dinerMenu,Menu cafeMenu){
this.pancakeHouseMenu =pancakeHouseMenu;
this.dinerMenu =dinerMenu;
this.cafeMenu =cafeMenu;
}
public void printMenu(){
IteratorpancakeIterator = pancakeHouseMenu.createIterator();
IteratordinerIterator = dinerMenu.createIterator();
//将咖啡厅加入晚餐的菜单
IteratorcafeIterator = cafeMenu.createIterator();
System.out.println("MENU\n----\nBREAKFAST");
printMenu(pancakeIterator);
System.out.println("\nLUNCH");
printMenu(dinerIterator);
//传入printMenu打印
System.out.println("\nDINNER");
printMenu(cafeIterator);
}
private void printMenu(Iteratoriterator) {
while(iterator.hasNext()){
MenuItemmenuItem = (MenuItem)iterator.next();//取得下一项
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}
}
- 测试代码
public class MenuTestDrive {
public static void main(String[]args) {
//创建两张新菜单
PancakeHouseMenupancakeHouseMenu = new PancakeHouseMenu();
DinerMenudinerMenu = new DinerMenu();
CafeMenucafeMenu = new CafeMenu();
//然后创建一个女招待,并将菜单传送给她
Waitresswaitress = new Waitress(pancakeHouseMenu,dinerMenu,cafeMenu);
waitress.printMenu();//打印出菜单
}
}
效果:
- 升级代码
- 上面的示例成功解决了在原有基础上,再次增加一个咖啡厅是完全可能的。但在程序中调用三次printMenu()貌似不太明智。每次一有新的菜单加入,就必须打开女招待实现并加入更多的代码,着违反了"开放-关闭原则"
- 如果将这些菜单全都打包进一个ArrayList中,然后取得它的迭代器,遍历每个菜单的话,是不是就解决了呢?来试试看
public class Waitress {
ArrayListmenus;//现在只需要一个菜单ArrayList
public Waitress(ArrayListmenus){
this.menus =menus;
}
public void printMenu(){
IteratormenuIterator = menus.iterator();
while(menuIterator.hasNext()){
//遍历菜单,把每个菜单的迭代器传给重载的printMenu()方法
Menumenu = (Menu)menuIterator.next();
printMenu(menu.createIterator());
}
}
//这个函数不需要改变
void printMenu(Iteratoriterator) {
while(iterator.hasNext()){
MenuItemmenuItem = (MenuItem)iterator.next();//取得下一项
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}
}
这样做的话,暂时可以解决问题
- 新的需求
- 现在餐厅希望能够加上一份餐后甜点的"子菜单";
- 现在不仅仅要支持多个菜单,还要支持菜单中的菜单;
- 如果能让甜点菜单变成餐厅菜单集合的一个元素,就很好了。但是根据现在的实例,根本无法做到;
- 现在怎么办?如果不重新设计,就无法容纳未来增加的菜单或子菜单等需求。那我们真正需要什么呢?
- 我们需要某种树形结构,可以容纳菜单,子菜单和菜单项;
- 我们需要确定能够在每个菜单的各个项之间游走,而且至少要像想在用迭代器一样方便;
- 还需要能够更有弹性地在菜单项之间游走。比如,可能需要遍历贴点菜单,或者可以遍历餐厅的整个菜单(包括甜点菜单);
- 定义组合模式
- 以菜单为例来说明问题:
- 这个模式能够创建一个树形结构,在同一个结构中处理嵌套菜单和菜单项组。通过将菜单和项放在相同的结构中,我们创建了一个"整体/部分"层次结构,即由菜单和菜单项组成的对象树;但是可以将它视为一个整体,像是一个丰富的大菜单;
- 使用组合结构,能把相同的操作应用在组合和个别对象上。换句话说,在大多数情况下,可以忽略对象组合和个别对象之间的差别;
- 类图
- 说明:组合模式和迭代器模式有什么关系
- 两者合作无间,可以在组合的实现中使用迭代器,而且做法还不只一种;
- 利用组合设计菜单
- 我们需要创建一个组件接口来作为菜单和菜单项的共同接口,这样就可以针对菜单或菜单项调用相同的方法;
- 如何让菜单符合组合模式的结构呢?
- 重新设计菜单
- 实现菜单组件
- 所有的组件都必须实现MenuComponent接口。然而,叶节点和组合节点的角色不同,所以有些方法可能并不适合某些节点。面对这种情况,有时候,最好是抛出运行时异常;
public abstract class MenuComponent {
//以下把"组合"方法组织在一起,即新增,删除和取得菜单组件
public void add(MenuComponentmenuComponent){
throw new UnsupportedOperationException();
}
public void remove(MenuComponentmenuComponent){
throw new UnsupportedOperationException();
}
public MenuComponent getChild(inti){
throw new UnsupportedOperationException();
}
//以下是"操作"方法,它们被菜单项使用,其中有一些也也可用在菜单上,再过几页你就会在菜单代码中看到了
public String getName(){
throw new UnsupportedOperationException();
}
public String getDescription(){
throw new UnsupportedOperationException();
}
public double getPrice(){
throw new UnsupportedOperationException();
}
public boolean isVegetarian(){
throw new UnsupportedOperationException();
}
//该方法是一个"操作"方法,这个方法同时被菜单和菜单项所实现,和但我们还是在这里提供了默认的操作
public void print(){
throw new UnsupportedOperationException();
}
}
- 实现菜单项
//扩展MenuComponent接口
public class MenuItemextends MenuComponent{
Stringname;
Stringdescription;
booleanvegetarian;
doubleprice;
public MenuItem(Stringname,String description,booleanvegetarian,doubleprice){
this.name=name;
this.description =description;
this.vegetarian =vegetarian;
this.price =price;
}
public String getName() {
returnname;
}
public String getDescription() {
returndescription;
}
public boolean isVegetarian() {
returnvegetarian;
}
public double getPrice() {
returnprice;
}
//在MenuComponent类里覆盖print()方法。对菜单项来说,此方法会打印出完整的菜单项条目,包括:名字,描述,价格以及是否为素食
public void print(){
System.out.print(" "+getName());
if(isVegetarian()){
System.out.print("(v)");
}
System.out.println(", "+getPrice());
System.out.println(" --"+getDescription());
}
}
- 实现组合菜单
- 现在已经有了菜单项,还需要组合类,也就是菜单咯。这个组合类可以持有菜单项或者其他菜单。有一些方法并未在MenuComopnent类中实现,比如getPrice()和isVegertarian(),因为这些方法对菜单而言没有太大的意义;
//菜单和菜单项一样,都是MenuComponent
public class Menuextends MenuComponent {
//菜单可以有任意数目的孩子,这些孩子都必须属于MenuComponent类型,我们使用内部的ArrayList记录它们
ArrayListmenuComponents = new ArrayList();
Stringname;
Stringdescription;
//这和之前的实现不一样,我们将给每个菜单一个名字和一个描述。以前,每个菜单的类名称就是此菜单的名字
public Menu(Stringname,String description){
this.name =name;
this.description =description;
}
//在这里将菜单项和其他菜单加入到菜单中。因为菜单和菜单项都是MenuComponent,所以我们只需用一个方法就可以两者兼顾
//同样的道理,也可以删除或者取得某个MenuComponent
public void add(MenuComponentmenuComponent){
menuComponents.add(menuComponent);
}
public void remove(MenuComponentmenuComponent){
menuComponents.remove(menuComponent);
}
public MenuComponent getChile(inti){
return (MenuComponent)menuComponents.get(i);
}
//这是用来取得名字和描述的getter方法
//这里没有覆盖getPrice()或isVegetarian(),因为这些方法对Menu来说没有意义。如果尝试调用这些方法的话,就会得到父类定义的异常
public String getName(){
returnname;
}
public String getDescription(){
returndescription;
}
//打印菜单的名称和描述,还要打印出菜单内所有组件的内容:其他菜单和菜单项
public void print(){
System.out.print("\n"+getName());
System.out.println(", "+getDescription());
System.out.println("----------------------");
//便利过程中,可能遇到其他菜单,或老是遇到菜单项。由于菜单和菜单项都实现了print(),那我们只要调用print()即可。
Iteratoriterator = menuComponents.iterator();
while(iterator.hasNext()){
MenuComponentmenuComponent = (MenuComponent)iterator.next();
menuComponent.print();
}
//在遍历期间,如果遇到另一个菜单对象,它的print()方法会开始另一个遍历,依次类推
}
}
- 测试前的准备工作
- 更新女招待的代码--她可是菜单的主要客户:
public class Waitress {
MenuComponentallMenus;
//只需要将最顶层的菜单组件交给她就可以了,最顶层菜单包含其他所有菜单,可以称为allMenus
public Waitress(MenuComponentallMenus){
this.allMenus =allMenus;
}
public void printMenu(){
//只需要调用最顶层菜单的print(),就可以打印整个菜单层次,包括所有菜单及所有菜单项
allMenus.print();
}
}
- 运行时菜单的组合:
- 编写测试代码
public class MenuTestDrive {
public static void main(String[]args) {
//先创建所有的菜单对象
MenuComponentpancakeHouseMenu = new Menu("PANCAKE HOUSE MENU","Breakfase");
MenuComponentdinerMenu = new Menu("DINER MENU","Lunch");
MenuComponentcafeMenu = new Menu("CAFE MENU","Dinner");
MenuComponentdessertMenu = new Menu("DESSERT MENU","Dessert of course!");
//需要一个最顶层的菜单,将它称为allMenus
MenuComponentallMenus = new Menu("ALL MENUS","All menus combined");
//使用组合的add()方法,将每个菜单都加入到顶层菜单allMenus中
allMenus.add(pancakeHouseMenu);
allMenus.add(dinerMenu);
allMenus.add(cafeMenu);
pancakeHouseMenu.add(new MenuItem("K&B`s Pancake Breakfast"
,"Pancakes with scrambled eggs,and toast"
,true
,2.99));
pancakeHouseMenu.add(new MenuItem("Regular Pancake Breakfase"
,"Pancakes with fried eggs,sausage"
,false
,2.99));
pancakeHouseMenu.add(new MenuItem("Blueberry Pancakes"
,"Pancakes made with fresh blueberries"
,true
,3.49));
pancakeHouseMenu.add(new MenuItem("Waffles"
,"Waffles,with your choice of blueberries or strawberries"
,true
,3.59));
//在这里加入菜单项
dinerMenu.add(new MenuItem("Vegetarian BLT"
,"(Fakin') Bacon with lettuce & tomato on whole wheat"
,true
,2.99));
dinerMenu.add(new MenuItem("BLT"
,"Bacon with lettuce & tomato on whole wheat"
,false
,2.99));
dinerMenu.add(new MenuItem("Soup of the day"
,"Soup of the day,with a side of potato salad"
,false
,3.29));
dinerMenu.add(new MenuItem("Hotdog"
,"A hot dog,with saurkraut,relish,onions,topped with cheese"
,false
,3.05));
dinerMenu.add(new MenuItem("Steamed Veggies and Brown Rice"
,"Steamed Vegetabls over brown rice"
,true
,3.99));
dinerMenu.add(new MenuItem("Pasta"
,"Spagheti with Marinara Sauce,and a slice of sourdough bread"
,true
,3.89));
//在菜单中加入另一个菜单。由于菜单和菜单项都是MenuComponent,所以菜单可以顺利地被被加入
dinerMenu.add(dessertMenu);
dessertMenu.add(new MenuItem("Apple Pie"
,"Apple pie with a flakey crust,topped with vanilla ice cream"
,true
,1.59));
dessertMenu.add(new MenuItem("Cheesecake"
,"Creamy New York cheesecake,with a chocolate graham crust"
,true
,1.99));
dessertMenu.add(new MenuItem("Sorbet"
,"A scoop of raspberry and a scoop of lime"
,true
,1.89));
cafeMenu.add(new MenuItem("Veggie Burger and Air Fries"
,"Veggie burger on a whole wheat bun,lettuce,tomato,and fries Soup of the day"
,true
,3.99));
cafeMenu.add(new MenuItem("Soup of the day"
,"A cup of the soup of the day,with a side salad"
,true
,3.69));
cafeMenu.add(new MenuItem("Burrito"
,"A large burrito,with whole pinto beans,salsa,guacamole"
,true
,4.29));
//将整个菜单层级构造完毕,把它整个交给女招待
Waitresswaitress = new Waitress(allMenus);
waitress.printMenu();
}
}
效果:
ALL MENUS, All menus combined
----------------------
PANCAKE HOUSE MENU, Breakfase
----------------------
K&B`s Pancake Breakfast(v), 2.99
--Pancakes with scrambled eggs,and toast
Regular Pancake Breakfase, 2.99
--Pancakes with fried eggs,sausage
Blueberry Pancakes(v), 3.49
--Pancakes made with fresh blueberries
Waffles(v), 3.59
--Waffles,with your choice of blueberries or strawberries
DINER MENU, Lunch
----------------------
Vegetarian BLT(v), 2.99
--(Fakin') Bacon with lettuce & tomato on whole wheat
BLT, 2.99
--Bacon with lettuce & tomato on whole wheat
Soup of the day, 3.29
--Soup of the day,with a side of potato salad
Hotdog, 3.05
--A hot dog,with saurkraut,relish,onions,topped with cheese
Steamed Veggies and Brown Rice(v), 3.99
--Steamed Vegetabls over brown rice
Pasta(v), 3.89
--Spagheti with Marinara Sauce,and a slice of sourdough bread
DESSERT MENU, Dessert of course!
----------------------
Apple Pie(v), 1.59
--Apple pie with a flakey crust,topped with vanilla ice cream
Cheesecake(v), 1.99
--Creamy New York cheesecake,with a chocolate graham crust
Sorbet(v), 1.89
--A scoop of raspberry and a scoop of lime
CAFE MENU, Dinner
----------------------
Veggie Burger and Air Fries(v), 3.99
--Veggie burger on a whole wheat bun,lettuce,tomato,and fries Soup of the day
Soup of the day(v), 3.69
--A cup of the soup of the day,with a side salad
Burrito(v), 4.29
--A large burrito,with whole pinto beans,salsa,guacamole
- 问题:一个类,不是应该只有一个职责吗?现在组合模式是让一个类有两个责任的模式。它现在要管理层次结构,还要执行菜单的操作。
- 组合模式以单一责任设计原则换取透明性。通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合的叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点,对客户是透明的。
- 在MenuComponent类中,同时具有两种类型的操作。因为客户有机会对一个元素做一些没有意义的操作(例如试图把菜单添加到菜单项),所以失去了一些"安全性";这是设计上的决策;当然也可以采用另一种方向的设计,将责任区分开来放在不同的接口中。这么一来,设计上就比较安全,但也因此失去了透明性,客户的代码将必须用条件语句和instanceof操作fu
- 综上,有时候我们会故意做一些看似违反设计原则的事情,这是一个典型的折衷做法;
- 组合模式中使用迭代器
- 在上例中print()方法中,已经出现了迭代器。
- 如果需要,可以让女招待实现迭代器遍历整个组合。比如,女招待可能想要游走整个菜单,挑出素食项;
- 想要实现一个组合迭代器,要为每个组件都加上createIterator()方法,从抽象的MenuComponent类下手:
public abstract class MenuComponent {
//其余代码不变
public Iterator createIterator(){
throw new UnsupportedOperationException();
}
}
- 在菜单和菜单项类中实现这个方法:
public class Menuextends MenuComponent {
//其余代码不变
public Iterator createIterator(){
//这里使用新的,被称为CompositeIterator的迭代器。这个迭代器知道如何遍历任何组合
//将目前组合的迭代器传入它的构造函数
return new CompositeIterator(menuComponents.iterator());
}
}
public class MenuItemextends MenuComponent{
//其余代码不变
public Iterator createIterator(){
//这里的空迭代器需要延展出来说说
return new NullIterator();
}
}
- 空迭代器:这可是空对象"设计模式"的另一个例子
- 菜单项既然没有可遍历的,那么我们要如何实现createIterator()方法呢?两种选择:
- 返回null:可以让createIterator()方法返回null,但是如果这么做,我们的客户代码就需要条件判断语句来判断返回值是否为null;
- 返回一个迭代器,而这个迭代器的hasNext()永远返回false:这样客户不需要担心返回值是null,我们等于创建一个"没有任何意义"的迭代器;
- 显然第二个选择会好一点,来看看它的代码:
//一个什么都不做的迭代器
public class NullIteratorimplements Iterator {
@Override
public boolean hasNext() {
//最重要的,当hasNext()被调用时,永远返回false
return false;
}
@Override
public Object next() {
return null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
- OK,了解了空迭代器,来看看组合迭代器
- CompositeIterator的工作是遍历组件内的菜单项,而且确保所有的子菜单(以及子子菜单....)都被包括进来。
import java.util.Iterator;
import java.util.Stack;
//实现java.util.Iterator接口
public class CompositeIteratorimplements Iterator {
Stackstack = new Stack();
//将要遍历的顶层组合的迭代器传入。放入一个堆栈数据结构中
public CompositeIterator(Iteratoriterator){
stack.push(iterator);
}
@Override
public Object next() {
//当客户想要取得下一个元素的时候,我们先调用hasNext()来确定是否还有下一个
if(hasNext()){
Iteratoriterator = (Iterator) stack.peek();
//如果还有下一个元素,就从堆栈中取出目前的迭代器,然后取得它的下一个元素
MenuComponentcomponent = (MenuComponent)iterator.next();
if(componentinstanceof Menu){
//如果元素是一个菜单,我们有了另一个需要被包含进遍历中的组合,所以我们将它丢进堆栈中,不管是不是菜单,我们都返回该组件
stack.push(component.createIterator());
}
returncomponent;
}else{
return null;
}
}
@Override
public boolean hasNext() {
if(stack.empty()){
//想要知道是否还有下一个元素,我们检查堆栈是否被清空;如果已经空了,就表示没有下一个元素了
return false;
}else{
//否则,我们就从堆栈的顶层中取出迭代器,看看是否还有下一个元素。如果它没有元素,我们将它弹出堆栈,然后递归地调用hasNext()
Iteratoriterator = (Iterator) stack.peek();
if(!iterator.hasNext()){
stack.pop();
return hasNext();
}else{
//否则,表示还有下一个元素,返回true
return true;
}
}
}
@Override
public void remove() {
//不支持删除,这里只有遍历
throw new UnsupportedOperationException();
}
}
- 让女招待告诉我们哪些菜是素食
public class Waitress {
MenuComponentallMenus;
//只需要将最顶层的菜单组件交给她就可以了,最顶层菜单包含其他所有菜单,可以称为allMenus
public Waitress(MenuComponentallMenus){
this.allMenus =allMenus;
}
public void printMenu(){
//只需要调用最顶层菜单的print(),就可以打印整个菜单层次,包括所有菜单及所有菜单项
allMenus.print();
}
//该方法取得allMenus的组合并得到它的迭代器来作为我们的CompositeIterator
public void printVegetarianMenu(){
Iteratoriterator = allMenus.createIterator();
System.out.println("\nVEGETARIAN MENU\n----");
//遍历组合内的每个元素
while(iterator.hasNext()){
MenuComponentmenuComponent = (MenuComponent)iterator.next();
try{
//调用每个元素的isVegetarian()方法,如果为true,就调用它的print()方法
if(menuComponent.isVegetarian()){
menuComponent.print();
}
//在菜单上实现isVegetarian()方法,让它永远抛出异常。如果异常果真发生了,就捕捉这个异常,然后继续遍历
}catch(UnsupportedOperationExceptione){}
}
}
}
测试这个方法:
Waitress waitress = new Waitress(allMenus);
// waitress.printMenu();
waitress.printVegetarianMenu();
效果:
VEGETARIAN MENU
----
K&B`s Pancake Breakfast(v), 2.99
--Pancakes with scrambled eggs,and toast
Blueberry Pancakes(v), 3.49
--Pancakes made with fresh blueberries
Waffles(v), 3.59
--Waffles,with your choice of blueberries or strawberries
Vegetarian BLT(v), 2.99
--(Fakin') Bacon with lettuce & tomato on whole wheat
Steamed Veggies and Brown Rice(v), 3.99
--Steamed Vegetabls over brown rice
Pasta(v), 3.89
--Spagheti with Marinara Sauce,and a slice of sourdough bread
Apple Pie(v), 1.59
--Apple pie with a flakey crust,topped with vanilla ice cream
Cheesecake(v), 1.99
--Creamy New York cheesecake,with a chocolate graham crust
Sorbet(v), 1.89
--A scoop of raspberry and a scoop of lime
Apple Pie(v), 1.59
--Apple pie with a flakey crust,topped with vanilla ice cream
Cheesecake(v), 1.99
--Creamy New York cheesecake,with a chocolate graham crust
Sorbet(v), 1.89
--A scoop of raspberry and a scoop of lime
Veggie Burger and Air Fries(v), 3.99
--Veggie burger on a whole wheat bun,lettuce,tomato,and fries Soup of the day
Soup of the day(v), 3.69
--A cup of the soup of the day,with a side salad
Burrito(v), 4.29
--A large burrito,with whole pinto beans,salsa,guacamole
- 关于printVegetarianMenu()方法内的try/catch
- try/catch是一种错误处理的方法,而不是程序逻辑的方法。如果这样做,而是在调用isVegetarian()方法之前,用instanceof来检查菜单组件的运行时类型,来确定它是菜单项。但是这样做,就会因为无法统一处理菜单和菜单项而失去透明性;
- 也可以改写Menu的isVegetarian()方法,让它返回false。这提供了一个简单的解决方案,同时也保持了透明性;
- 之所以使用try/catch,是为了传达:isVegetarian()是Menu没有支持的操作。这样的做法也允许后来人去为Menu实现一个合理的isVegetarian()方法,而我们不必为此再修改这里的代码了;
- 模式对比
- 设计模式:组合模式
- 设计模式-----组合模式
- 设计模式 组合模式
- 设计模式--组合模式
- 设计模式---组合模式
- 【设计模式】组合模式
- 设计模式 - 组合模式
- 设计模式----组合模式
- 设计模式 - 组合模式
- 设计模式:组合模式
- 设计模式 - 组合模式
- 设计模式-组合模式
- 设计模式 - 组合模式
- 【设计模式】组合模式
- 设计模式--组合模式
- 设计模式-组合模式
- 设计模式:组合模式
- 设计模式-----组合模式
- 关于win10下eclipse编译wordcount出现 java.net.UnknownHostException: unknown host:master
- CXF Map 绑定
- 信噪比/香农公式
- unity Shader 扭曲效果
- 微信小程序中使用wxss加载图片并实现动画
- 设计模式--组合模式
- javaScript-dom
- CocosCreator-Layout
- linux系统下安装opencv3.1以及cmake3.1
- Spring MVC的异常处理
- 文件有关
- Java过滤器与SpringMVC拦截器之间的关系与区别
- 设计模式--迭代器模式
- Eclipse下载与安装教程(最简单版)