组合模式

25次阅读

共计 2112 个字符,预计需要花费 6 分钟才能阅读完成。

简介

组合模式是用在树状结构中,表示部分和整体的层次结构,组合模式使客户端同等对待对象(叶子节点)和对象的组合(容器)。也分为透明组合模式和安全组合模式,透明组合模式实现了父类的所有方法,安全组合模式不实现管理叶子节点的接口。本文只记录透明组合模式。

UML 类图

示例

树状结构在我们软件设计中很常见。例如菜单下面常常会有子菜单。在此我们构造一个使用文件菜单的示例。我们使用 vector 来作为叶子节点的容器。
抽象对象,叶子节点和容器,composite.h

#ifndef COMPOSITE_H
#define COMPOSITE_H

#include <iostream>
#include <vector>
#include <string>
using namespace std;
#define SAFE_DELETE(p) if(p){delete p; p = NULL;}

class CMenu
{
public:
    CMenu(string strName):m_strName(strName){}
    virtual void Add(CMenu* pMenu) = 0;
    virtual void Remove(CMenu* pMenu) = 0;
    virtual CMenu* GetChild(int nIndex) = 0;
    virtual void ShowMenuName(int indent) = 0;
public:
    string m_strName;
};

class CLeafMenu:public CMenu
{
public:
    CLeafMenu(string strName):CMenu(strName){}
    void Add(CMenu* pMenu)
    {cout<<"Leaf can not add."<<endl;}
    void Remove(CMenu* pMenu)
    {cout<<"Leaf can not remove."<<endl;}
    CMenu* GetChild(int nIndex)
    {
        cout<<"Leaf has no child."<<endl;
        return NULL;
    }
    void ShowMenuName(int indent)
    {string str(indent, '-');
        cout<<str<<m_strName<<endl;
    }
};

class CCompositeMenu:public CMenu
{
public:
    CCompositeMenu(string strName):CMenu(strName){}
    void Add(CMenu* pMenu)
    {m_pMenuVec.push_back(pMenu);
    }
    void Remove(CMenu* pMenu)
    {for(vector<CMenu*>::iterator iter = m_pMenuVec.begin(); iter != m_pMenuVec.end(); ++ iter)
        {if(*iter == pMenu)
            {m_pMenuVec.erase(iter);
                SAFE_DELETE(pMenu);
                break;
            }
        }
    }
    CMenu* GetChild(int nIndex)
    {return m_pMenuVec[nIndex];
    }
    void ShowMenuName(int indent)
    {string str(indent, '-');
        cout<<str<<"+"<<m_strName<<endl;
        for(vector<CMenu*>::iterator iter = m_pMenuVec.begin(); iter != m_pMenuVec.end(); ++ iter)
        {(*iter)->ShowMenuName(indent + 1);
        }
    }
public:
    vector<CMenu*> m_pMenuVec;
};

#endif

客户端调用,main.cpp

#include "composite.h"

#define SAFE_DELETE(p) if(p){delete p; p = NULL;}

int main(int argc, char* argv[])
{CMenu* pRootMenu = new CCompositeMenu("File");
    CMenu* pNew = new CCompositeMenu("New");
    CMenu* PNewProject = new CLeafMenu("Project");
    CMenu* pNewSolution = new CLeafMenu("Solution");
    CMenu* pClose = new CLeafMenu("Close");

    pRootMenu->Add(pNew);
    pNew->Add(PNewProject);
    pNew->Add(pNewSolution);
    pRootMenu->Add(pClose);
    pRootMenu->ShowMenuName(1);
    SAFE_DELETE(pClose);
    SAFE_DELETE(pNewSolution);
    SAFE_DELETE(PNewProject);
    SAFE_DELETE(pNew);
    SAFE_DELETE(pRootMenu);
    return 0;
}

输出结果:

-+File
--+New
---Project
---Solution
--Close

正文完
 0

组合模式

25次阅读

共计 2000 个字符,预计需要花费 5 分钟才能阅读完成。

⭐️ 更多前端技术和知识点,搜索订阅号 JS 菌 订阅

  • 组合模式在对象间形成树形结构;
  • 组合模式中基本对象和组合对象被一致对待;
  • 无须关心对象有多少层, 调用时只需在根部进行调用;

实现原理

  • 创建宏任务并维护一个任务列表 list
  • 创建宏任务方法 add 将 task push 到 list 中
  • 创建 execute 方法循环遍历 list 中的 task 对象
  • task 对象必须拥有一个名为 execute 的方法(用来在宏任务中遍历 list)

代码实现

const MacroCommand = function() {this.lists = [] // 宏任务维护一个任务列表
}
MacroCommand.prototype.add = function(task) {this.lists.push(task) // add 方法增加任务
}
MacroCommand.prototype.execute = function() {for (let index = 0; index < this.lists.length; index++) {this.lists[index].execute() // execute 方法执行任务}
}

const command1 = new MacroCommand() // 通过 new 操作符创建任务

command1.add({execute: () => {console.log('command1-1') }
})

command1.add({execute: () => {console.log('command1-2') }
})

const command2 = new MacroCommand()

command2.add({execute: () => {console.log('command2-1') }
})

command2.add({execute: () => {console.log('command2-2') }
})

command2.add({execute: () => {console.log('command2-3') }
})

const macroCommand = new MacroCommand() // 假定 macroCommand 为宏任务

macroCommand.add(command1) // 将其他子任务推如任务列表
macroCommand.add(command2)

macroCommand.execute() // 宏命令执行操作后,command 将依次递归执行
// command1-1
// command1-2
// command2-1
// command2-2
// command2-3

应用

扫描文件夹

文件夹下面可以为另一个文件夹也可以为文件, 我们希望统一对待这些文件夹和文件, 这种情形适合使用组合模式。

const Folder = function(folder) {
    this.folder = folder
    this.lists = []}
Folder.prototype.add = function(res) {this.lists.push(res)
}
Folder.prototype.scan = function() {console.log(` 开始扫描文件夹: ${this.folder}`)
    for (let index = 0; index < this.lists.length; index++) {this.lists[index].scan()}
}

const File = function(file) {this.file = file}
File.prototype.add = function() {throw Error('文件中不可添加文件')
}
File.prototype.scan = function() {console.log(` 开始扫描文件: ${this.file}`)
}

const folder = new Folder('根文件夹')
const folder1 = new Folder('JS')
const folder2 = new Folder('其他')

const file = new File('JS prototype')
const file1 = new File('CSS 编程艺术')
const file2 = new File('HTML 标记语言')
const file3 = new File('HTTP-TCP-IP')

folder.add(folder1)
folder.add(folder2)

folder1.add(file)
folder2.add(file1)
folder2.add(file2)
folder2.add(file3)

folder.scan()

// 开始扫描文件夹: 根文件夹
// 开始扫描文件夹: JS
// 开始扫描文件: JS prototype
// 开始扫描文件夹: 其他
// 开始扫描文件: CSS 编程艺术
// 开始扫描文件: HTML 标记语言
// 开始扫描文件: HTTP-TCP-IP

请关注我的订阅号,不定期推送有关 JS 的技术文章,只谈技术不谈八卦 ????

正文完
 0

组合模式

25次阅读

共计 2864 个字符,预计需要花费 8 分钟才能阅读完成。

组合模式
定义

将对象组合成树形结构以表示“部分 - 整体”的层次结构。
组合模式是客户端对单个对象和组合对象保持一致的方式处理。

类型
结构型
适用场景

希望客户端可以忽略组合对象与单个对象的差异是
处理一个树形结构时

优点

清楚地定义分层次的复杂对象,表示对象的全部或部分层次。
让客户端忽略了层次的差异,方便对整个层次结构进行控制。
简化客户端代码

下面开始写代码,我们首先假设一个业务场景,假设我们有一个课程,这个课程里面有很多课程包括 java 课程 python 等等,我们定义一个抽象类,让这些课程类继承这个抽象类,那么我们就可以认为这些课程是一个对象。
public abstract class CatalogComponent {
public void add(CatalogComponent catalogComponent){
throw new UnsupportedOperationException(“ 不支持添加操作 ”);
}

public void remove(CatalogComponent catalogComponent){
throw new UnsupportedOperationException(“ 不支持删除操作 ”);
}

public String getName(CatalogComponent catalogComponent){
throw new UnsupportedOperationException(“ 不支持获取名称操作 ”);
}

public double getPrice(CatalogComponent catalogComponent){
throw new UnsupportedOperationException(“ 不支持获取价格操作 ”);
}

public void print(){
throw new UnsupportedOperationException(“ 不支持打印操作 ”);
}

}
这个就是抽象方法,至于我们为什么要在这个抽象类中抛出异常呢?这个问题我们等写完了其他的类再解答。
public class Course extends CatalogComponent {
private String name;
private double price;

public Course(String name, double price) {
this.name = name;
this.price = price;
}

@Override
public String getName(CatalogComponent catalogComponent) {
return this.name;
}

@Override
public double getPrice(CatalogComponent catalogComponent) {
return this.price;
}

@Override
public void print() {
System.out.println(“Course Name:”+name+” Price:”+price);
}

}
这是一个课程类,我们继承了抽象类,有价格和名称两个属性。
public class CourseCatalog extends CatalogComponent {
private List<CatalogComponent> items = new ArrayList<CatalogComponent>();
private String name;
private Integer level;

public CourseCatalog(String name,Integer level) {
this.name = name;
this.level = level;
}

@Override
public void add(CatalogComponent catalogComponent) {
items.add(catalogComponent);
}

@Override
public String getName(CatalogComponent catalogComponent) {
return this.name;
}

@Override
public void remove(CatalogComponent catalogComponent) {
items.remove(catalogComponent);
}

@Override
public void print() {
System.out.println(this.name);
for(CatalogComponent catalogComponent : items){
if(this.level != null){
for(int i = 0; i < this.level; i++){
System.out.print(” “);
}
}
catalogComponent.print();
}
}

}

这个是目录类,有两个属性,层级和名称。这个是我们看到代码,就解释一下为什么在抽象类中要抛出异常,因为我们的子类有的功能我们都重写了抽象类的方法,但是如果没有重写的方法也是被继承了的。如果这时候被调用了,子类是没有这个功能我希望不被调用所以就直接抛异常。最后我们看看测试类的代码。
public class CompositeTest {
public static void main(String[] args) {
CatalogComponent linuxCourse = new Course(“Linux 课程 ”,11);
CatalogComponent windowsCourse = new Course(“Windows 课程 ”,11);

CatalogComponent javaCourseCatalog = new CourseCatalog(“Java 课程目录 ”,2);

CatalogComponent mmallCourse1 = new Course(“Java 设计模式一 ”,55);
CatalogComponent mmallCourse2 = new Course(“Java 设计模式二 ”,66);
CatalogComponent designPattern = new Course(“Java 设计模式三 ”,77);

javaCourseCatalog.add(mmallCourse1);
javaCourseCatalog.add(mmallCourse2);
javaCourseCatalog.add(designPattern);

CatalogComponent imoocMainCourseCatalog = new CourseCatalog(“ 课程主目录 ”,1);
imoocMainCourseCatalog.add(linuxCourse);
imoocMainCourseCatalog.add(windowsCourse);
imoocMainCourseCatalog.add(javaCourseCatalog);

imoocMainCourseCatalog.print();
}
}

组合模式最大的问题在于要花代码去判断到底是那一个类,因为我们在使用的时候都是使用了父类对象。

正文完
 0