设计模式之----组合模式

设计模式之组合模式

  • 组合模式相关总结
  • 组合模式案例一(餐馆子菜单)
  • 组合模式案例二(目录结构)

组合模式相关总结

在学习的时候看到一个写的非常好的博客,可以好好学习一下。
  • 组合模式为处理树形结构提供了一种较为完美的解决方案,它描述了如何将容器和叶子进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器和叶子。
  • 组合模式(Composite Pattern):组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。
  • 组合模式的关键是定义了一个抽象构件类(Component),它既可以代表叶子(Leaf),又可以代表容器(中间的),而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。
  • 这种组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。比如:文件目录显示,多及目录呈现等树形结构数据的操作。
  • Java中的Swing组件: 在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。

使用场景:

  • 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。
  • 在一个使用面向对象语言开发的系统中需要处理一个树形结构。
  • 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。
  • 你想表示对象的部分-整体层次结构(树形结构)。
  • 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

这里写图片描述

组合模式案例一(餐馆子菜单)

这个案例在迭代器模式中讲过。现在是增加需求,要在中餐厅中增加一个子菜单,类似在文件夹中又有一个文件夹,是树型的结构。
这里写图片描述
这里写图片描述
这里写图片描述

统一管理的超类:

package combine;


import java.util.Iterator;

/**
 * 所有类型的超类类型
 */
public abstract class MenuComponent {

    public String getName(){  //默认的
        return "";
    }

    public double getPrice(){   //菜单项才有  子菜单是没有的
        return 0;
    }

    public boolean isVegetable(){
        return false;
    }

    public abstract void print();


    public Iterator getIterator(){
        return new NullIterator();
    }
}

菜单项:

package combine;


/**
 * 这个是菜单项
 */
public class MenuItem extends MenuComponent {

    private String name;
    private boolean vegetable;
    private double price;

    public MenuItem(String name, double price, boolean vegetable) {
        this.name = name;
        this.price = price;
        this.vegetable = vegetable;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public boolean isVegetable() {
        return vegetable;
    }

    @Override
    public void print() {  //和迭代器有点不同
        System.out.println(getName() + " " + getPrice() + " " + isVegetable());
    }

    @Override
    public double getPrice() {
        return price;
    }
}

子菜单(就是饭后甜点):

package combine;


import java.util.ArrayList;
import java.util.Iterator;

/**
 * 子菜单
 * 子菜单中按道理应该放的是MenuItem,但是为了解决存储一致性,放的是超类
 * 相当于中间的结点
 */
public class SubMenu extends MenuComponent {
    private ArrayList<MenuComponent>menuItems;  //注意这里放的是超类的类型 很好的解决了存储的一致性问题

    public SubMenu() {
        menuItems = new ArrayList<>();
        addItem("sub 甜点1(是蔬菜)",5.5,true);
        addItem("sub 甜点2(不是蔬菜)",6.6, false);
    }

    public void addItem(String name,double price,boolean vegetable){
        MenuItem menuItem = new MenuItem(name,price,vegetable);  //虽然数据结构是超类,但是具体放入的是超类的子类
        menuItems.add(menuItem);
    }

    @Override
    public Iterator getIterator(){
        return new ComposeIterator(menuItems.iterator());
    }

    @Override
    public void print() {
        System.out.println("----------This is SubMenu------------");
    }
}

两个餐馆:

package combine;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * 蛋糕店
 */
public class CakeHouse extends MenuComponent {

    private ArrayList<MenuComponent> menuItems;

    public CakeHouse() {
        menuItems = new ArrayList<>();

        addItem("蛋糕店 奶油草莓蛋糕",44.4,false);
        addItem("蛋糕店 黄瓜",22.2,true);
    }

    public void addItem(String name,double price,boolean vegetable){
        MenuItem menuItem = new MenuItem(name,price,vegetable);
        menuItems.add(menuItem);
    }


    @Override
    public Iterator getIterator() {
        return new ComposeIterator(menuItems.iterator());
    }

    @Override
    public void print() {
        System.out.println("---------This is CakeHouse---------");
    }
}

注意这里中餐厅的餐馆还有子菜单:

package combine;

import java.util.Iterator;

/**
 *中餐厅
 */
public class DinerHouse extends MenuComponent {

    private final static int Max_Item = 5;//最多只有五个菜单项

    private int cur; //游标   注意这里设置成public 的是为了要Waitress中使用

    private MenuComponent[] menuItems;  //注意这里是超类类型的

    public DinerHouse() {
        menuItems = new MenuComponent[Max_Item];
        addItem("中餐厅 大鱼",66.6,false);
        addItem("中餐厅 白菜",11.1,true);

        addSubMenu(new SubMenu()); //这个是关键
    }


    public void addItem(String name,double price,boolean vegetable){
        MenuItem menuItem = new MenuItem(name,price,vegetable);
        if(cur >= Max_Item){
            System.out.println("sorry,menu is full! can't add any item!");
        }else {
            menuItems[cur++] = menuItem;
        }
    }

    private void addSubMenu(MenuComponent subMenu) { //注意类型还是  MeunCompoent类型
        if(cur >= Max_Item){
            System.out.println("sorry,menu is full! can't add any item!");
        }else {
            menuItems[cur++] = subMenu;  //添加一个子菜单
        }
    }

    @Override
    public Iterator getIterator() {
        return new ComposeIterator(new DinerHouseIterator());
    }

    private class DinerHouseIterator implements Iterator {  //自己重写的迭代器
        private int pos;

        public DinerHouseIterator() {
            pos = 0;
        }

        @Override
        public boolean hasNext() {
            if(pos < cur){
                return true;
            }
            return false;
        }

        @Override
        public Object next() {
            MenuComponent menuItem = menuItems[pos++];  //注意是超类的
            return menuItem;
        }

        @Override
        public void remove() {

        }
    }

    @Override
    public void print() {
        System.out.println("---------This is DinerHouse---------");
    }
}

空迭代器:

package combine;

import java.util.Iterator;

public class NullIterator implements Iterator {

    @Override
    public boolean hasNext() {
        return false;
    }

    @Override
    public Object next() {
        return null;
    }

    @Override
    public void remove() {

    }
}

组合迭代器:

package combine;


import java.util.Iterator;
import java.util.Stack;

/**
 * 组合迭代器(统一的遍历迭代器)
 * //菜单,子菜单,菜单项都是同一个结构,不需要区别
 */
public class ComposeIterator implements Iterator{

    private Stack<Iterator>stack = new Stack<>();

    public ComposeIterator(Iterator iterator) {
        stack.push(iterator);
    }

    @Override
    public boolean hasNext() {
        if(stack.isEmpty())return false;
        Iterator top = stack.peek();
        if(!top.hasNext()) {  //如果顶层的迭代器 没有下一个  顶层的迭代器用光了  也就是看整个栈(整个树下)
            stack.pop();
            return hasNext();
        }else { //一开始就有,那就有呗
            return true;
        }
    }

    @Override
    public Object next() {
        if(hasNext()){
            Iterator top = stack.peek(); //取到栈顶
            MenuComponent menuCompoent = (MenuComponent) top.next(); //这个是递归
            stack.push(menuCompoent.getIterator()); //同时准别下一层(如果有的话,,也就是层次遍历?)
            return menuCompoent;
        }
        return null;
    }

    @Override
    public void remove() {

    }
}

女招待:

package combine;

import java.util.ArrayList;
import java.util.Iterator;

public class Waitress {

    private ArrayList<MenuComponent> iterators = new ArrayList<>();

    public Waitress() {

    }

    public void addComponent(MenuComponent menuComponent){
        iterators.add(menuComponent);
    }


    public void printMenu(){
        Iterator iterator;
        MenuComponent meunItem;
        for(int i = 0; i < iterators.size(); i++){
            iterators.get(i).print(); //打印的是菜单
            iterator = iterators.get(i).getIterator(); //获取到菜单的迭代器
            while(iterator.hasNext()){  //组合的会深入到内部
                meunItem = (MenuComponent)iterator.next(); //菜单项或者是子菜单
                meunItem.print();
            }
        }
    }
    //打印是否为素食
    public void printVegetableMenu(){
        Iterator iterator;
        MenuComponent meunItem;
        for(int i = 0; i < iterators.size(); i++){
            iterators.get(i).print(); //打印的是菜单
            iterator = iterators.get(i).getIterator(); //获取到菜单的迭代器
            while(iterator.hasNext()){  //组合的会深入到内部
                meunItem = (MenuComponent)iterator.next(); //菜单项或者是子菜单
                if(meunItem.isVegetable()) {
                    meunItem.print();
                }
            }
        }
    }
}

测试:

package combine;

public class MyTest {

    public static void main(String[] args) {

        Waitress waitress = new Waitress();

        CakeHouse cakeHouse = new CakeHouse();
        DinerHouse dinerHouse = new DinerHouse();

        waitress.addComponent(cakeHouse);
        waitress.addComponent(dinerHouse);
        waitress.printMenu();
        System.out.println("\n" +"---------------华丽的分割线--------------"+ "\n");
        waitress.printVegetableMenu();
    }
}

测试效果:
这里写图片描述


组合模式案例二(目录结构)

(1)普通的写法:

文件结点:

package combine.practice.bad;

/**
 * 文件
 */
public class Filer {
    String fileName;//文件名

    public Filer(String fileName){
        this.fileName = fileName;
    }

    public void display(){//文件显示方法
        System.out.println(fileName);
    }
}

目录结构:

package combine.practice.bad;

import java.util.ArrayList;
import java.util.List;

public class Folder {
    String nodeName;    //目录名

    public Folder(String nodeName) {
        this.nodeName = nodeName;
    }

    List<Folder> nodeList = new ArrayList<>();   //目录的下级目录列表
    List<Filer> fileList = new ArrayList<>();   //目录的下级文件列表

    public void addNoder(Folder noder) {//新增下级目录
        nodeList.add(noder);
    }

    public void addFiler(Filer filer) {//新增文件
        fileList.add(filer);
    }

    public void display() {//显示下级目录及文件
        for (Folder noder : nodeList) {
            System.out.println(noder.nodeName);
            noder.display();       //递归显示目录列表
        }
        for (Filer filer : fileList) {
            filer.display();
        }
    }
}

测试:

package combine.practice.bad;

import java.io.File;

public class MyTest {

    public static void createTree(Folder folder){
        File file = new File(folder.nodeName);
        File[] f = file.listFiles();

        for(File fi : f){
            if(fi.isFile()){
                Filer filer = new Filer(fi.getAbsolutePath());
                folder.addFiler(filer);
            }
            if(fi.isDirectory()){
                Folder subFolder = new Folder(fi.getAbsolutePath());
                folder.addNoder(subFolder);
                createTree(subFolder);
            }
        }
    }

    public static void main(String[] args) {
        Folder noder = new Folder("/home/zxzxin/RESOUCE/TestforDesignCombine");
        createTree(noder);//创建目录树形结构
        noder.display();//显示目录及文件
    }
}

效果:
这里写图片描述

使用组合模式设计

从上面的代码中可以看出,我们分别定义了文件节点对象与目录节点对象,这是因为文件与目录之间的操作不同,文件没有下级节点,而目录可以有下级节点,但是我们能不能这么想:既然文件与目录都是可以作为一个节点的下级节点而存在,那么我们可不可以将二者抽象为一类对象,虽然二者的操作不同,但是我们可以在实现类的方法实现中具体定义,比如文件没有新增下级节点的方法,我们就可以在文件的这个方法中抛出一个异常,不做具体实现,而在目录中则具体实现新增操作显示操作二者都有,可以各自实现。而且由于我们将文件与目录抽象为一个类型,那么结合多态我们可以进行如下实现:

这里写图片描述

package combine.practice.good;

/**
 * 将文件与目录统一看作是一类节点,做一个抽象类来定义这种节点,
 * 然后以其实现类来区分文件与目录,在实现类中分别定义各自的具体实现内容
 */
public abstract class Node {
    protected String name; //注意子类继承

    public Node(String name){
        this.name = name;
    }


    //新增节点:文件节点无此方法,目录节点重写此方法
    public void addNode(Node node) throws Exception{
        throw new Exception("Invalid exception");
    }


    //显示节点:文件与目录均实现此方法
    public abstract void display();
}
package combine.practice.good;

/**
 * 文件
 */
public class Filer extends Node{

    //通过构造器为文件节点命名
    public Filer(String name) {
        super(name);
    }


    //显示文件节点
    @Override
    public void display() {
        System.out.println(name);
    }
}
package combine.practice.good;

import java.util.ArrayList;
import java.util.List;

public class Folder extends Node {

    public Folder(String name) {//通过构造器为当前目录节点赋名
        super(name);
    }

    List<Node> nodeList = new ArrayList<>();  //统一的抽象父类

    //新增节点
    public void addNode(Node node) throws Exception{
        nodeList.add(node);
    }
    //递归循环显示下级节点
    @Override
    public void display() {
        System.out.println(name);
        for(Node node:nodeList){
            node.display();
        }
    }
}

测试类:

package combine.practice.good;


import java.io.File;

public class MyTest {

    public static void createTree(Folder folder) throws Exception {
        File file = new File(folder.name);
        File[] f = file.listFiles();

        for(File fi : f){
            if(fi.isFile()){
                Filer filer = new Filer(fi.getAbsolutePath());
                folder.addNode(filer);
            }
            if(fi.isDirectory()){
                Folder subFolder = new Folder(fi.getAbsolutePath());
                folder.addNode(subFolder);
                createTree(subFolder);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Folder noder = new Folder("/home/zxzxin/RESOUCE/TestforDesignCombine");
        createTree(noder);//创建目录树形结构
        noder.display();//显示目录及文件
    }
}

效果是一样的。

猜你喜欢

转载自blog.csdn.net/zxzxzx0119/article/details/81812201