设计模式之组合模式
- 组合模式相关总结
- 组合模式案例一(餐馆子菜单)
- 组合模式案例二(目录结构)
组合模式相关总结
在学习的时候看到一个写的非常好的博客,可以好好学习一下。
- 组合模式为处理树形结构提供了一种较为完美的解决方案,它描述了如何将容器和叶子进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器和叶子。
- 组合模式(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();//显示目录及文件
}
}
效果是一样的。