65.9K
CodeProject 正在变化。 阅读更多。
Home

在 C++ 和 Qt 中实现组合模式的两种方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (23投票s)

2012 年 5 月 17 日

CPOL

4分钟阅读

viewsIcon

75517

downloadIcon

541

在本文中,我将通过使用组合设计模式、C++和Qt框架来展示树形结构的面向对象实现。我还会解释为什么在Qt中我们不使用GoF描述的组合模式标准设计(尽管你可以使用)。

引言

本文展示了两种将对象组合成树状结构以表示部分-整体层级关系的方法。第一个示例解释了如何使用简单的GoF组合模式(使用标准的C++),第二个示例则侧重于使用Qt Nokia框架。

背景

下图展示了组合模式的UML类图

有关组合模式的更多信息,请参阅以下维基百科文章或Gamma, Erich; Richard Helm, Ralph Johnson, John M. Vlissides (1995) 的著作《设计模式:可复用面向对象软件的基础》。

使用标准C++实现组合模式的方法

好的,现在我们需要构建一个树形结构来实现一个层级(组织结构)。一个组织可以由顶层的首席执行官(CEO)以及其下方的各种类型的经理和更底层的员工组成。最终,层级中会有人处于叶子节点。叶子节点指的是最底层的节点。所以有两种人:一种是有人向他汇报,另一种是无人向他汇报(参见文章开头的图)。假设客户(例如税务检查员)想了解该组织的薪资情况。总薪资由个人薪资组成,因此所有员工都应该能够返回他们自己的薪资。当客户想了解任何部门(当然,部门可以是整个公司)的薪资时,每个员工都应该能够返回自己的薪资以及其下属所有人员的薪资。

使用组合模式设计是那些能让想要组织构建层级结构的人们的生活更轻松的创新之一。

好的,让我们为我们的示例创建一个UML(类图)。

上面的图代表了一个叶子节点(Worker)和一个父节点(Manager)。请注意,Manager可以是CEO、CTO或VP等。这也是一个非常简单的继承与多态结合的示例。

现在,我们需要仔细观察这个图中的几个地方——

  • Component(组件):只包含一个接口——`printSalary`,这是一个纯虚方法,必须由派生类(Worker & Manager)实现。
  • Manager(经理):包含自己,并且包含Worker(s)。Manager必须始终是树的根节点。
  • Worker(员工):只包含自己。

下面的C++示例实现了一个Component类,它可以是Worker或Manager(由多个Worker组成)。每个组件都可以打印其薪资。

class Component
{
  public:
    Component(std::string name, double salary) 
          : m_fullName(name), m_valueSalary (salary) {}

    virtual void printSalary(int level) = 0;
    
    //Of course these data members should be private, 
    //but I did not wish to "litter" this class with 
    //superfluous functions-interfaces,
    //therefore I have left these data members of class to be public
    std::string            m_fullName;
    double                 m_valueSalary;
};

/** "Leaf" */
class Worker : public Component
{  
  public:
    Worker(std::string name , double salary): Component(name,salary)
    {
    }
    void printSalary(int level)
    {
        for(int j=0; j < level; ++j) cout << "\t";
        cout << "Worker : " <<  
            m_fullName.c_str() << ",salary: " << 
            m_valueSalary << "$\n";
    }
};

/** "Composite" */
class Manager: public Component
{
  public:
    Manager(std::string name , double salary) : Component(name,salary)
    {
    }
    void add(Component *cmp)
    {
        m_children.push_back(cmp);
    }
    void printSalary(int level)
    {    
        for(int j=0; j < level; ++j) cout << "\t";
        cout << "Manager : " <<  this->m_fullName.c_str() << 
            ",salary: " << m_valueSalary << "$\n";

        if(!m_children.empty())
        {
            for(int x=0; x < level; ++x) cout << "\t";
            cout << "Subordinates  of " <<  
                m_fullName.c_str() << ":\n";
            ++level;
            for (int i = 0; i < m_children.size(); ++i)
              m_children[i]->printSalary(level);
        }
    }

   private:
    // The manager can have a number of people(managers or workers) 
    // under his/her supervision 
    // and that is the reason we have the vector here (for 
    // navigating a hierarchical organisation, 
    // for typing an individual salary)
    vector < Component * > m_children;
};

int main()
{   
    //Let's define a big chief
    Manager president ("Gerard Butcher", 1000000.0);
    
    //Let's define several average chiefs
    Manager manager_production_department ("John Smith",400000.0);
    Manager manager_engineering_department ("Michael Biner",400000.0);
    Manager manager_quality_control_department ("David Jaskson",280000.0);
    Manager manager_sales_management_division ("Tom Vilow",270000.0);
    Manager manager_general_affairs_department ("Janet Teyllor" ,200000.0);
    
    //Let's define several managers of a engineering department
    Manager team_leader_RandD ("Jorge Creig", 250000.0); 
    Manager team_leader_QA ("Arnold Lambero", 200000.0); 
    
    //Let's define several engineers of a engineering department
    Worker software_developer1 ("Andrey Lapidos", 200000.0);
    Worker software_developer2 ("Maxim  Laertsky", 240000.0);
    Worker tester ("Miki  Minaj", 130000.0);
    
    //Now we will add the number of persons as assistants of president
    president.add(&manager_production_department);
    president.add(&manager_engineering_department);
    president.add(&manager_quality_control_department);
    president.add(&manager_sales_management_division);
    president.add(&manager_general_affairs_department );

    //Now we will add the number of persons as assistants of manager engineering department
    manager_engineering_department.add(&team_leader_RandD);
    manager_engineering_department.add(&team_leader_QA );

    //Now we will add the number of persons as assistants of team leader the R&D
    team_leader_RandD.add(&software_developer1);
    team_leader_RandD.add(&software_developer2);

    //Now we will add the tester as assistant of team leader the QA
    team_leader_QA.add(&tester);

    cout << "The hierarchy of the company,\ni.e. president and all who is under his supervision :\n\n" ;
    president.printSalary(0);

    cout << '\n';                 
}

使用Qt4跨平台框架实现组合模式的方法

我们知道Qt中存在QObject类。QObject类是所有Qt对象的基类。继承自QObject的类非常有用,因为我们可以表达继承自QObject的类对象之间的父子关系。通过这个类及其方法(`setParent`、`findChildren`和`parent`),我将在下面进行解释,我们可以放弃之前的设计(实现两个不同的类),而是使用一种新的设计(两种情况都只有一个类:Worker和Manager)。与之前的解决方案一样,树的根节点(即组织层级的顶层)QObject将有很多子节点,但没有父节点。最简单的QObjects(即该树的叶子节点)将各自有一个父节点,但没有子节点。客户端代码可以递归地处理树的每个节点。
因此,我们将定义类图……

QObject的公共接口允许我们构建一个类似树的组织表示,通过实例化一个WorkerOrManager并调用`setParent()`将其添加到适当的子列表中。

class WorkerOrManager : public QObject
{
public:
    WorkerOrManager(QString name , double salary)
    {
        m_fullName    = name;
        m_valueSalary = salary;
    }

    void printSalary(int level)
    {
        for(int j=0; j < level; ++j) std::cout << "\t";
        std::cout << "Worker : " <<  
            m_fullName.toStdString() << ",salary: " << 
            m_valueSalary << "$\n";

        QList<WorkerOrManager*> children = 
            findChildren<WorkerOrManager*>();

        //Here, we want to check if the object is a manager
        if(!children.isEmpty())
        {
            for(int j=0; j < level; ++j) std::cout << "\t";
            std::cout << "Subordinates  of " << 
                m_fullName.toStdString() << ":\n";
            ++level;

            for (int i = 0; i < children.size(); ++i)
            {
                //We deduce data only about direct subordinates
                if(children[i]->parent() == this)
                {
                    children[i]->printSalary(level);
                }
            }
        }
    }

  private:
     QString      m_fullName;
     double       m_valueSalary;
};


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    //Let's define a big chief
    WorkerOrManager president ("Gerard Butcher", 1000000.0);

    //Let's define several average  chiefs
    WorkerOrManager manager_production_department ("John Smith",400000.0);
    WorkerOrManager manager_engineering_department ("Michael Biner",400000.0);
    WorkerOrManager manager_quality_control_department ("David Jaskson",280000.0);
    WorkerOrManager manager_sales_management_division ("Tom Vilow",270000.0);
    WorkerOrManager manager_general_affairs_department ("Janet Teyllor" ,200000.0);

    //Let's define several managers of a engineering department
    WorkerOrManager team_leader_RandD ("Jorge Creig", 250000.0);
    WorkerOrManager team_leader_QA ("Arnold Lambero", 200000.0);

    //Let's define several engineers of a engineering department
    WorkerOrManager software_developer1 ("Andrey Lapidos", 200000.0);
    WorkerOrManager software_developer2 ("Maxim  Laertsky", 240000.0);
    WorkerOrManager tester ("Miki  Minaj", 130000.0);

    //Now we will add the number of persons as assistants of president
    manager_production_department.setParent(&president);
    manager_engineering_department.setParent(&president);
    manager_quality_control_department.setParent(&president);
    manager_sales_management_division.setParent(&president);
    manager_general_affairs_department.setParent(&president);

    //Now we will add the number of persons as assistants of manager engineering department
    team_leader_RandD.setParent(&manager_engineering_department);
    team_leader_QA.setParent(&manager_engineering_department);

    //Now we will add the number of persons as assistants of team leader the R&D
    software_developer1.setParent(&team_leader_RandD);
    software_developer2.setParent(&team_leader_RandD);

    //Now we will add the tester as assistant of team leader the QA
    tester.setParent(&team_leader_QA);

    cout << "The hierarchy of the company,\ni.e. president and all who is under his supervision :\n\n" ;
    president.printSalary(0);

    return a.exec();
}

每当我们有两个QObject对象(例如objA和objB),并且想让一个对象成为另一个对象的子对象时,我们只需调用`QObject::setParent`。

例如,在调用`objB.setParent(objA)`之后,`objA`将成为`objB`的父对象。通过使用`QObject::parent()`方法,我们可以检查谁是父对象。

请注意,由于以下方法,我们可以访问该对象的所有后代:

QList<T> QObject::findChildren ( const QString & name = QString() ) const

正如Qt文档中所述:“此方法返回可以转换为类型T且名称与正则表达式regExp匹配的此对象的子对象,如果没有此类对象,则返回空列表。搜索是递归进行的。”

总之,通过使用`QObject`类和上述方法,我们可以放弃使用标准的组合模式,而采用Qt特有的设计,如本节所述。

摘要

请注意,在两种不同的设计中,我们得到了相同的结果,即对于相同的输入,我们得到相同的输出。

历史

  • 2012年4月10日:初次发布
© . All rights reserved.