设计模式之道:解构结构型设计模式的核心原理

news2025/1/25 8:56:20

解构常见的三种结构型设计模式的核心原理

  • 一、引言:如何学习设计模式?
  • 二、责任链模式
    • 2.1、代码结构
    • 2.2、符合的设计原则
    • 2.3、案例分析:nginx 阶段处理
    • 2.4、小结
  • 三、装饰器模式
    • 3.1、代码结构
    • 3.2、符合的设计原则
    • 3.3、小结
  • 四、组合模式
    • 4.1、代码结构
    • 4.2、符合的设计原则
    • 4.3、小结
  • 五、总结

一、引言:如何学习设计模式?

学习设计模式最主要要抓住一点:就是怎么分析这个稳定点和变化点。自己实现一个框架,或者是实现一个具体的小功能,本质上分析问题的思路都是一样的,首先要去把稳定点给它抽象出来,然后针对这个变化点想着怎么去扩展它。所以这里还是要反复的介绍怎么分析这个稳定点和变化点;具体不同的设计模式是怎么来处理这个扩展(就是扩展的问题);稳定点它是怎么处理的;用C++的语言特性是怎么去解决这些问题的;沿着这个思路去学习。

前面已经介绍了设计模式当中的模板方法、观察的模式、以及策略模式,这里再次强调以下学习、掌握设计模式的学习步骤。

  • 首先,需要来了解设计模式解决了什么问题。本质上是分析它的稳定点和变化点,实际在做具体功能开发的时候也需要去抽象具体的稳定点以及想办法去扩展变化点,这样在实际开发过程当中,尽量写少量的代码去应对未来需求的变化。
  • 第二点,设计模式的代码结构是什么。需要培养一个看代码、看一些框架或者看项目代码结构的时候马上能够反应出来使用了什么设计模式,或者它符合什么设计原则,从而可以推断出代码具体的意图。熟悉实现具体设计模式的代码结构能够帮助我们对一个代码有一个敏感度,以便能够快速的进行推断和反应。
  • 第三点,看这些设计模式符合了哪些设计原则。因为设计模式是由设计原则推导过来的,所以可以按照这一个设计模式的产生的流程重新去思考这一个问题,能够帮助我们去很好的去设计我们的代码。相信很多人在具体的工作当中都有自己不同的一些设计方式,它不一定符合某一些设计模式,未来大家应对的某些需求也会自己去设计一个框架,所以可以思考它符合哪些设计原则。
  • 第四点,如何在上面扩展代码。尤其是对于初学者或刚刚参加工作的朋友们,对这个扩展代码一定要非常的清楚,就是如果在这个设计模式的基础上要修改哪些代码,这个是必须要掌握的。
  • 第五点,按照自己的需求或者自己的项目以及自己的工作场景进行一个联系,哪些需求变化可以使用设计模式;在看开源框架的时候也可以去看一下它是怎么解决这一个问题的。记住几个关键设计模式的一些典型应用场景能够帮助我们快速的反应;当具体需求来了知道该怎么使用某一些设计模式。
学习步骤
设计模式解决什么问题
稳定点
变化点
设计模式的代码结构是什么
设计模式符合哪些设计原则
如何在上面扩展代码
该设计模式有哪些典型应用场景
联系工作场景
开源框架

这个就是设计模式具体的学习步骤。

二、责任链模式

开源框架Nginx也会用到责任链,先了解一下什么是责任链模式,责任链模式的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止

定义解析:“请求的发送者和接收者之间的一个耦合关系”是请求的发送由多个接收者(或处理对象)来进行处理的,并且这些处理对象是连成了一个链条,具体的请求会沿着链条的顺序依次进行传递,直到有一个对象处理为止。假设有一个对象处理了,则它处理完就直接返回了,后面的对象就不会处理了,这个就是责任链的定义,它描述的一个功能。

定义特别复杂,只要来分析它解决的问题,从稳定点和变化点来进行分析。

  • 稳定点:处理流程是稳定的,第一个稳定点是请求按照链条传递,第二个稳定点是可打断的。一个请求者对多个接收者,这个接受者是由多个对象构成的,请求会沿着这个链条依次进行处理,因此可以看到请求会沿着这个链条进行传递是一个稳定点。请求是由多个接受者来进行处理的,如果被其中一个处理了,就可以打断,后面的就不会被处理了,这个称之为可打断,这也是一个稳定点。
  • 变化点:处理节点的个数、处理条件、处理顺序。一个请求沿着处理链条依次进行处理,这个链条可能会新增加一个处理流程,这是第一个变化点,可能会增加一些功能,或者要去减少一些功能;第二个变化点就是可能会去进行调整处理流程节点的顺序(即调整他们请求处理的顺序)化点。

2.1、代码结构

先来看这一个具体的案例,具体的请假流程:

请求流程,1 天内需要主程序批准,3 天内需要项目经理批准,3 天以上需要老板批准。

说明:有一个这样的规定,一天内需要主程序来去批准,如果主程序批准了,那么项目经理就不需要看这个请假流程了,那么老板更加不需要知道了;如果是两天的请假,主程序把握不住,就把这个流程传递给下一个人去处理,项目经理就根据目前的项目进度来看一看能不能请两天假,如果可以请两天假,在这里处理完就结束了,后面的人都不需要看了(即不需要让老板知道,老板就不需要关注这个事情);如果要请一个月假,项目经理把握不住了,需要让更上层的老板来进行处理了,老板可能就看这个员工最近在公司表现怎么样(KPI可不可以),如果是一个人才,那么还是批了,要是经常KPI很低,然后经常浑水摸鱼,那么当然老板直接就可能…。这个就是处理的流程的背景,这个背景显然就是责任链模式一样的。

那么来看一下它的稳定点和变化点,同样的,来分析这个稳定点和变化点跟责任链的需求到底是怎么对应上的,首先来看一下它符不符合稳定点(请求是不是按照链条传递),流程是按照主程序、项目经理、老板依次进行传递,而且是可以打断的(主程序处理了,那么项目经理不需要知道这个事情了,项目经理处理了,那么老板就不需要知道这个事情了,可打断)。

那么再来看一下这个变化点。可能主程序就很抱怨,为什么总是有请假的事情来干扰他写代码,那么前面能不能增加一个前台,前台来负责处理一下半天以内的(比如请假一个小时也来打扰一下,那么主程序就不用干活了),让主程序来休息一下,不需要关注这么多小事情,这个就是新增流程节点(比如增加一个前台的处理流程),这是第一个变化需求。第二个变化需求是这个时间条件,比如主程序觉得这个一天不合适,能不能把他的条件改成三天,或者改为十天以内再来麻烦项目经理,又或者说一个月以上的请假才来麻烦老板,就是处理条件也是一个变化点。

分析具体需求的时候,一定要把它的稳定点和变化点来分析出来,稳定点要通过抽象的流程来解决,让它变得更稳定,变化点要想办法能不能够通过扩展的方式去扩展它,扩展的方式有两种思路:第一是看看能不能通过继承的方式来去复写它,本质上就是多态;第二是能不能够通过组合的方式(比如说接口组合的方式)来实现扩展功能。

解释完这个案例背景,接下来看一下它的代码结构。先来看不使用设计模式是怎么来处理的:

#include <string>

class Context {
public:
    std::string name;
    int day;
};

class LeaveRequest {
public:
    bool HandleRequest(const Context &ctx) {
        if (ctx.day <= 1)
            HandleByMainProgram(ctx);
        else if (ctx.day <= 3)
            HandleByProjMgr(ctx);
        else
            HandleByBoss(ctx);
    }

private:
    bool HandleByBeaty(const Context &ctx) {
        
    }
    bool HandleByMainProgram(const Context &ctx) {
        
    }
    bool HandleByProjMgr(const Context &ctx) {
        
    }
    bool HandleByBoss(const Context &ctx) {

    }
};

这个示例很简单,就是有一个请假流程,按照请假多少天来顺序传递,比如说有一个主程序处理一天的,项目经理处理三天以内的,否则就是老板去处理了。这个示例的处理流程里会有很多判断,未来如果还要加东西(加一个前台的处理)或可能还要修改条件(10天、一个月等)需要修改这个类,那么整个来说这个类就是不稳定,它的职责太多了,即它又有可能会改这个条件,又可能会增加节点,内容非常的不稳定,显然这不是设计模式,都不符合设计原则。

那符合设计原则的做法是怎么样的,对于稳定点而言,需要按照请求链条进行传递,首先想到了单链表,即这里会有一个链表关系;然后这个链表里面的节点都是一个个的处理对象,需要去处理它的变化点,也就是这些处理对象的节点的个数还会增加或减少,而且它里面的内容可能也会改变,这个处理条件本质上就是处理对象的内容,要消除处理对象的差异可以用接口(就是处理具体的对象)。这个接口就是它的职责(处理具体的请求),它只是跑到处理节点上来了,所以要把节点进行抽象。

代码实现如下:

#include <string>

class Context {
public:
    std::string name;
    int day;
};

// 稳定点 抽象  变化点 扩展 (多态)
//  从单个处理节点出发,我能处理,我处理,我不能处理交给下一个人处理
//  链表关系如何抽象

class IHandler {
public:
    virtual ~IHandler() : next(nullptr) {}
    void SetNextHandler(IHandler *next) { // 链表关系
        next = next;
    }
    bool Handle(const Context &ctx) {
        if (CanHandle(ctx)) {
            return HandleRequest(ctx);
        } else if (GetNextHandler()) {
            return GetNextHandler()->Handle(ctx);
        } else {
            // err
        }
        return false;
    }
    // 通过函数来抽象 处理节点的个数  处理节点顺序
    static bool handler_leavereq(Context &ctx) {
        IHandler * h0 = new HandleByBeauty();
        IHandler * h1 = new HandleByMainProgram();
        IHandler * h2 = new HandleByProjMgr();
        IHandler * h3 = new HandleByBoss();
        h0->SetNextHandler(h1);
        h1->SetNextHandler(h2);
        h2->SetNextHandler(h3);
        return h0->Handle(ctx);
    }
protected:
    virtual bool HandleRequest(const Context &ctx) {return true};
    virtual bool CanHandle(const Context &ctx) {return true};
    IHandler * GetNextHandler() {
        return next;
    }
private:
    IHandler *next; // 组合基类指针
};

// 能不能处理,以及怎么处理
class HandleByMainProgram : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day <= 10)
            return true;
        return false;
    }
};

class HandleByProjMgr : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day <= 20)
            return true;
        return false;
    }
};
class HandleByBoss : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day < 30)
            return true;
        return false;
    }
};

class HandleByBeauty : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day <= 3)
            return true;
        return false;
    }
};

int main() {
    // 设置下一指针 
    Context ctx;
    if (IHander::handler_leavereq(ctx)) {
        cout << "请假成功";
    } else {
        cout << "请假失败";
    }
    
    return 0;
}

这里抽象了一个接口IHandler ,接口的作用是抽象稳定点,对于单个处理节点而言,稳定点是能处理就处理,不能处理就交给下一个人处理。那链表关系怎么构建的?链表关系如何,示例用一个静态的成员函数handler_leavereq来进行抽象,可以在这个地方去改对象就行了,这里面只处理对象或者顺序的改变。

接下来再来看到有一个总体的流程Handle,就是能处理就处理,不能处理就给下一个人处理,通过一个Handle函数来进行抽象,能处理就去处理;还有一个具体的处理的请求的流程HandleRequest去处理它。因为它是一个变化点,要进行扩展,在这里用了多态的方式进行扩展(能不能够处理?是怎么处理的?以及呢交给下一个人处理)。要注意到Handle是一个递归调用,递归调用交给下一个人去调用它的Handle

示例中是这么处理的:有一个主程序,要实现能不能够处理、条件是什么、是怎么处理的;还有一个项目经理,要实现能不能够处理、处理条件是什么、是怎么处理的,同样的还有一个具体的老板,要实现具体的处理的条件是什么、怎么处理的。假设要增加一个前台,只需要去扩展这个IHandler ,然后自己实现能不能处理、处理的条件是什么、怎么处理;接下来只需要在handler_leavereq这个地方去构建这个链表关系来处理它就行了。

使用就非常简单了,直接定义一个Context ,然后调用handler_leavereq就可以了。打断功能在Handle函数已经实现,处理了就直接打断了。

因此,代码结构是:从单节点出发,实现处理接口,实现一个构建链表关系的静态接口

扩展代码:

  • 实现处理接口。针对增加节点。
  • 修改静态接口。主要是调整顺序、增加节点、删除节点的处理。

2.2、符合的设计原则

  1. 组合优于继承。
  2. 面向接口编程。
  3. 接口依赖。

2.3、案例分析:nginx 阶段处理

结构图:
在这里插入图片描述
调度图:
在这里插入图片描述

2.4、小结

要点:

  • 解耦请求方和处理方,请求方不知道请求是如何被处理,处理方的组成是由相互独立的子处理构成,子处理流程通过链表的方式连接,子处理请求可以按任意顺序组合。
  • 责任链请求强调请求最终由一个子处理流程处理;通过了各个子处理条件判断。
  • 责任链扩展就是功能链,功能链强调的是,一个请求依次经由功能链中的子处理流程处理。
  • 将职责以及职责顺序运行进行抽象,那么职责变化可以任意扩展,同时职责顺序也可以任意扩展。

本质:分离职责,动态组合。

结构图:
在这里插入图片描述
思维导图:
在这里插入图片描述

三、装饰器模式

定义:动态地给一个对象增加一些额外的职责。就增加功能而言,装饰器模式比生产子类更为灵活

装饰器它跟责任链不一样,责任链是一个一个的处理的接口,依次的传递,并且具备一个打断的功能;装饰器是在一个类的一个对象的,就是一个功能的基础上给他去扩展功能。定义可能听都听不懂,首先需要分析它的一个变化点和稳定点是什么,这是非常重要的。

  • 稳定点:顺序无关地增加职责
  • 变化点:增加

装饰器模式的稳定点是要给它进行增加职责,就是原来有一个类已经有职责了,现在在它的基础上增加职责,这个增加职责不要使用继承的方式,因为会生产子类,应该要通过组合的方式,组合优于继承。还有一个稳定点就是顺序无关。值得注意的是责任链也可以用来实现这个增加职责,但是责任链要求有顺序,而装饰器是跟顺序无关的,就跟装修一样,是先放电视机,还是先放沙发,它是没有什么顺序的,顺序上是无关的,可以在上面一直增加功能,并且是使用组合的方式,而不是使用继承的方式,因为继承的方式耦合度太高了。变化点就是增加。可能职责会一直不断的膨胀,一直会增加,关键的就是这个增加。

3.1、代码结构

示例背景:

普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能针对不同的职位产生不同的奖金组合。

计算出工资总共有多少钱,具体这个工资算法的先后顺序是没什么关系的,来看一下它是怎么来实现这个功能。

首先看一下没有使用设计模式的实现方式:

// 普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能产生不同的奖金组合;
// 销售奖金 = 当月销售额 * 4%
// 累计奖金 = 总的回款额 * 0.2%
// 部门奖金 = 团队销售额 * 1%
// 环比奖金 = (当月销售额-上月销售额) * 1%
// 销售后面的参数可能会调整
class Context {
public:
    bool isMgr;
    // User user;
    // double groupsale;
};

class Bonus {
public:
    double CalcBonus(Context &ctx) {
        double bonus = 0.0;
        bonus += CalcMonthBonus(ctx);
        bonus += CalcSumBonus(ctx);
        if (ctx.isMgr) {
            bonus += CalcGroupBonus(ctx);
        }
        return bonus;
    }
private:
    double CalcMonthBonus(Context &ctx) {
        double bonus/* = */;
        return bonus;
    }
    double CalcSumBonus(Context &ctx) {
        double bonus/* = */;
        return bonus;
    }
    double CalcGroupBonus(Context &ctx) {
        double bonus/* = */;
        return bonus;
    }
};

int main() {
    Context ctx;
    // 设置 ctx
    Bonus *bonus = new Bonus;
    bonus->CalcBonus(ctx);
}

这个很简单,就不在说明了,主要问题就是实现类不稳定,发生变化时需要不断修改具体类。

再来看一下使用了设计模式的实现方式:

#include <iostream>
// 普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能产生不同的奖金组合;
// 销售奖金 = 当月销售额 * 4%
// 累计奖金 = 总的回款额 * 0.2%
// 部门奖金 = 团队销售额 * 1%
// 环比奖金 = (当月销售额-上月销售额) * 1%
// 销售后面的参数可能会调整
using namespace std;
class Context {
public:
    bool isMgr;
    // User user;
    // double groupsale;
};


class CalcBonus {    
public:
    CalcBonus(CalcBonus * c = nullptr) : cc(c) {}
    virtual double Calc(Context &ctx) {
        return 0.0; // 基本工资
    }
    virtual ~CalcBonus() {}

protected:
    CalcBonus* cc;
};

class CalcMonthBonus : public CalcBonus {
public:
    CalcMonthBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double mbonus /*= 计算流程忽略*/; 
        return mbonus + cc->Calc(ctx);
    }
};

class CalcSumBonus : public CalcBonus {
public:
    CalcSumBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double sbonus /*= 计算流程忽略*/; 
        return sbonus + cc->Calc(ctx);
    }
};

class CalcGroupBonus : public CalcBonus {
public:
    CalcGroupBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double gbnonus /*= 计算流程忽略*/; 
        return gbnonus + cc->Calc(ctx);
    }
};

class CalcCycleBonus : public CalcBonus {
public:
    CalcCycleBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double gbnonus /*= 计算流程忽略*/; 
        return gbnonus + cc->Calc(ctx);
    }
};

int main() {
    // 1. 普通员工
    Context ctx1;
    CalcBonus *base = new CalcBonus();
    CalcBonus *cb1 = new CalcMonthBonus(base);//依赖注入
    CalcBonus *cb2 = new CalcSumBonus(cb1);


    cb2->Calc(ctx1);
    // 2. 部门经理
    Context ctx2;
    CalcBonus *cb3 = new CalcGroupBonus(cb1);
    cb3->Calc(ctx2);
}

首先实现一个这样的接口CalcBonus,一个算工资的接口,它采用protected来组合自己,扩展功能。通过组合的方式组合基类指针的方式,它是有职责的,通常这个基类就是基本工资,基类通常还会实现一些基本功能(即一些基本职责),然后通过继承扩展,但是扩展这个计算工资的这个接口是一种多态组合,继承这个计算的接口,然后调用自己的计算的流程。即自己实现Calc,然后加上前面组合的部分。

装饰器的使用是这样的,示例中有一个普通员工算工资,先算基本工资,然后基本工资算完之后把这个基本工资通过依赖注入的方式注入到下一个,这里又有累计奖金,又有月度奖金,要把它带进去,那么最终就调用这个cb2->Calc(ctx1)来实现,去调用计算所有的工资,这就是普通员工的所有工资;还有一个部门经理,直接把前面计算好的传进来,部门经理就会把base、1、2、3全部都带进来了,那么就实现了计算工资。

这个就是装饰器模式,和顺序无关,可以任意调整。

代码结构就是:通过组合基类指针的方式,所有子类都继承基类扩展功能,使用依赖注入累加功能。

扩展代码:只需要写一个子类继承基类,然后现自己的职责,然后在掉用的地方new,最后调用职责接口。

3.2、符合的设计原则

  • 组合优于继承。
  • 面向接口编程。
  • 接口依赖。

3.3、小结

要点:

  • 通过采用组合而非继承的手法, 装饰器模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。 避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
  • 不是解决“多子类衍生问题”问题,而是解决“父类在多个方向上的扩展功能”问题。
  • 装饰器模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,实现复用装饰器的功能。

什么时候使用?
不影响其他对象的情况下,以动态、透明的方式给对象添加职责;每个职责都是完全独立的功能,彼此之间没有依赖。

本质:动态组合

结构图:
在这里插入图片描述
思维导图:
在这里插入图片描述

四、组合模式

定义:将对象组合成树型结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

组合模式就是部分和整体的关系,非常的简单。稳定点就是部分和整体的层次关系,并且要使用这个对象跟组合对象具有一致性;也就是说对象之间的关系是稳定的,并且对它的使用也是具有一致性的。它的变化点就是可能会去增加一些对象,这里面的对象显然在这个地方就是树状关系,它主要解决了对象之间是树状层次关系的(有叶子节点、有组合节点,有这种组合节点就是指树状结构),相信在开发过程当中都会碰到这种场景:对象关系是属于树状关系,不同的对象一个是叶子节点对象,一个是组合对象;现在第一步就要消除他们两者之间的差异,同样是用接口来消除他们的差异。

  • 稳定点:“具备层次关系”是稳定的和对象和组合对象可统一使用。
  • 变化点:对象的职责变更和组合对象里对象数量的变更。

4.1、代码结构

class IComponent
{
public:
    IComponent(/* args */);
    ~IComponent();
    virtual void Execute() = 0;
    virtual void AddChild(IComponent *ele) {}
    virtual void RemoveChild(IComponent *ele) {}
};

class Leaf : public IComponent
{
public:
    virtual void Execute() {
        cout << "leaf exxcute" << endl;
    }
};

class Composite : public IComponent
{
private:
    std::list<IComponent*> _list;
public:
    virtual void AddChild(IComponent *ele) {
        // ...
    }
    virtual void RemoveChild(IComponent *ele) {
        // ...
    }
    virtual void Execute() {
        for (auto iter = _list.begin(); iter != _list.end(); iter++) {
            iter->Execute();
        }
    }
};

这就是具体的一个的整体和部分的一个抽象,示例中抽象的一个具体的一个组件对象IComponent,这个组件对象因为要消除叶子节点和组合对象的差异,有添加节点AddChild,还有一个具体执行的接口Execute,还有一个移除节点RemoveChild,添加节点和移除节点是组合节点,Execute是叶子节点执行具体的功能。叶子节点和组合节点都继承这个IComponent 接口,但是叶子节点只有执行的职责,只是去执行。组合对象也是继承IComponent 接口,因为要使单个对象和组合对象的使用具有一致性,所以要用接口来进行一个统一,因此它需要实现三种功能,但是具体功能职责的实现需要去委托下面的叶子节点去实现,所以要用一个容器来进行组合所有的对象,在组合对象中会把具体执行某一个功能的职责一直传递到叶子节点去执行它相对应的功能。

这个就是组合模式,组合模式使用的非常的频繁,尤其是在游戏开发过程当中,游戏开发当中会有一个用户玩家,它会有很多的系统(比如签到的系统、跑马灯系统等等),这些系统都会绑定在这个用户身上,都会统一继承同一个接口,比如说跑灯功能又会有分为很多的分支,有一些子的功能通过这种组合模式把所有的功能进行一个组合,为什么我们要通过组合的方式来实现这个功能呢?因为它的变化点是可能会增加,就比如说某一个组合节点可能还会要增加一些child,这个是它的变化点,可能还要移除一些变化点,某一些功能已经不想实现了,那么要remove掉它;还有就是这个具体的组合对象,它是把它的功能进行委托的,可能具体的叶子它是执行具体某一些功能的,那么它的职责也会发生变更,通过这样子呢就进行了一个解耦具体的功能,在叶子节点当中去修改它,这个是一个变化点;第二个变化点就是具体的组合对象的叶子节点的一个变更,会通过add和remove来添加职责、移除职责。

代码结构:通过一个接口,消除叶子节点和组合节点的差异。

  • 接口用于整合整体和部分的差异。
  • 叶子节点用于实现具体的职责。
  • 组合节点职责委托叶子节点实现,同时具备组合叶子节点职责。

4.2、符合的设计原则

  • 组合优于继承。
  • 面向接口编程。
  • 接口依赖。

4.3、小结

什么时候使用组合模式?

  • 如果你想表示对象的部分-整体层次结构,可以选用组合模式,把整体和部分的操作统一起来,使得层次结构实现更简单,从外部来使用这个层次结构也容易;
  • 如果你希望统一地使用组合结构中的所有对象,可以选用组合模式,这正是组合模式提供的主要功能。

怎么实现?
将叶子节点当成特殊的组合对象看待,从而统一叶子对象和组合对象。

结构图:
在这里插入图片描述
思维导图:
在这里插入图片描述

五、总结

本文探讨了责任链模式、装饰器模式和组合模式的设计原则、代码结构以及它们在实际案例中的应用。这些设计模式提供了灵活性和可维护性,帮助开发人员构建可扩展的软件系统。

责任链模式是一种行为设计模式,它通过将请求沿着处理链传递,使多个对象都有机会处理请求。本文介绍了责任链模式的代码结构,并讨论了如何设计符合开闭原则和单一职责原则的责任链。通过分析 nginx 阶段处理的案例,展示了责任链模式在实际中的应用。

装饰器模式是一种结构设计模式,它允许在不改变现有对象的结构的情况下,动态地向对象添加新的功能。本文探讨了装饰器模式的代码结构,并讨论了如何设计符合开闭原则和单一职责原则的装饰器。通过示例代码,展示了装饰器模式如何增强对象的功能。

组合模式是一种结构设计模式,它允许将对象组合成树形结构,以表示“整体-部分”层次关系。本文介绍了组合模式的代码结构,并讨论了如何设计符合开闭原则和单一职责原则的组合模式。通过使用组合模式,可以简化处理复杂结构的代码。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1279220.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习——第03章 Python程序设计语言(3.1 Python语言基础)

无论是在机器学习还是深度学习中&#xff0c;Python已经成为主导性的编程语言。而且&#xff0c;现在许多主流的深度学习框架&#xff0c;例如PyTorch、TensorFlow也都是基于Python。本课程主要是围绕“理论实战”同时进行&#xff0c;所以本章将重点介绍深度学习中Python的必备…

JOSEF 快速中间继电器 KZJ-4H-L DC220V 导轨安装

快速中间继电器KZJ-4H-LDC220V导轨安装导轨安装是广泛用于电力系统&#xff0c;能够断货开或开通大负载&#xff0c;并且具有较强的断弧能力&#xff0c;适用于交流50/60Hz。电压24380V,直流电压24280V自动控制电路中以增加保护和控制回路的触点数量与触点容量。 KZJ系列快速中…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《市场环境下运行的光热电站子系统容量优化配比研究》

这个标题涉及到对市场环境下运行的光热电站子系统进行容量优化配比的研究。让我们逐步解读&#xff1a; 市场环境下运行的光热电站&#xff1a; 这指的是光热电站在实际市场环境中的运行&#xff0c;可能包括了市场相关的经济、政策、竞争等因素。 子系统&#xff1a; 光热电站…

把握生成式AI新机遇,亚马逊云科技助力下一位独角兽

文章目录 前言亚马逊云科技生成式AI创业热潮向应用与工具链集中生成式AI初创生而全球化 赛道更细分、布局更广阔后记 前言 DoNews11月20日消息&#xff0c;当一项新技术出现&#xff0c;并成为行业主流甚至是变革的“敲门砖”时&#xff0c;企业应该如何应对&#xff1f; 202…

[WP] ISCTF2023 Web 部分题解

圣杯战争!!! 反序列化伪协议读取 where_is_the_flag 环境变量根目录当前目录 绕进你的心里 利用正则最大回溯绕过 easy_website or select 用双写绕过 空格用/**/绕&#xff0c;报错注入 wafr codesystem(ca\t /f*) webinclude 扫描得到index.bak备份文件打开为加密的代码 写…

PyLMKit(4):基于本地知识库的检索增强生成RAG

基于本地知识库的检索增强生成RAG 0.项目信息 日期&#xff1a; 2023-12-2作者&#xff1a;小知课题: RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;是一种利用知识库检索的方法&#xff0c;提供与用户查询相关的内容&#xff0c;从而…

Hdoop学习笔记(HDP)-Part.07 安装MySQL

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

数据链路层之网桥

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

[IIS服务]搭建unityWebGl项目服务器(用idea失败了,这次用IIS)

1、确认安装服务 没有安装的&#xff0c;点击安装&#xff0c;安装完成后下一步。 2、配置IIS服务&#xff08;很多小伙伴更新了windows找不到&#xff0c;可以使用cmd运行control admintools打开下图页面&#xff09; 打开管理器之后添加一个网站。 路径选择网站路径&#xf…

Linux驱动开发学习笔记1《字符设备驱动开发》

目录 一、字符设备驱动简介 二、chrdevbase 字符设备驱动开发实验 1.创建驱动程序的目录 2.创建vscode工程 3.编写实验程序 4.编译驱动程序和测试APP代码 &#xff08;1&#xff09;加载驱动模块 &#xff08;2&#xff09;创建设备节点文件 &#xff08;3&#xff…

设计模式-结构型模式之代理设计模式

文章目录 八、代理设计模式 八、代理设计模式 代理设计模式通过代理控制对象的访问&#xff0c;可以详细访问某个对象的方法&#xff0c;在这个方法调用处理&#xff0c;或调用后处理。既(AOP微实现) 。 代理有分静态代理和动态代理&#xff1a; 静态代理&#xff1a;在程序…

阅读笔记|A Survey of Large Language Models

阅读笔记 模型选择&#xff1a;是否一定要选择参数量巨大的模型&#xff1f;如果需要更好的泛化能力&#xff0c;用于处理非单一的任务&#xff0c;例如对话&#xff0c;则可用选更大的模型&#xff1b;而对于单一明确的任务&#xff0c;则不一定越大越好&#xff0c;参数小一…

Basemap地图绘制_Python数据分析与可视化

Basemap地图绘制 安装和使用地图投影地图背景在地图上画数据 Basemap是Matplotlib的一个子包&#xff0c;负责地图绘制。在数据可视化过程中&#xff0c;我们常需要将数据在地图上画出来。 比如说我们在地图上画出城市人口&#xff0c;飞机航线&#xff0c;军事基地&#xff0c…

Windows远程桌面提示出现身份验证错误 要求的函数不支持

现象 解决方案&#xff1a; 在cmd运行框输入&#xff1a;gpedit.msc打开组策略编辑器路径&#xff1a;计算机配置→管理模板→Windows组件→远程桌面服务→远程桌面会话主机→安全开启远程连接要求使用指定的安全层 禁用要求使用网络级别的身份验证对远程连接的用户进行身份验…

光学3D表面轮廓仪超0.1nm纵向分辨能力,让显微形貌分毫毕现

在工业应用中&#xff0c;光学3D表面轮廓仪超0.1nm的纵向分辨能力能够高精度测量物体的表面形貌&#xff0c;可用于质量控制、表面工程和纳米制造等领域。 与其它表面形貌测量方法相比&#xff0c;光学3D表面轮廓仪达到纳米级别的相移干涉法(PSI)和垂直扫描干涉法(VSI)&#x…

深入理解Servlet(下)

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 在这一篇文章里&#x…

Centos图形化界面封装OpenStack Ubuntu镜像

目录 背景 环境 搭建kvm环境 安装ubuntu虚机 虚机设置 系统安装 登录虚机 安装cloud-init 安装cloud-utils-growpart 关闭实例 删除细节信息 删除网卡细节 使虚机脱离libvirt纳管 结束与验证 压缩与转移 验证是否能够正常运行 背景 一般的镜像文件在上传OpenSt…

计算机毕业设计 基于协同推荐的白酒销售管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

TCA9548A I2C 多路复用器 Arduino 使用相同地址 I2C 设备

在本教程中&#xff0c;我们将学习如何将 TCA9548A I2C 多路复用器与 Arduino 结合使用。我们将讨论如何通过整合硬件解决方案来使用多个具有相同地址的 Arduino 的 I2C 设备。通过使用 TCA9548A I2C 多路复用器&#xff0c;我们将能够增加 Arduino 的 I2C 地址范围&#xff0c…

前端打包添加前缀

vue2添加前缀 router的base加上前缀 export default new Router({mode: history, // 去掉url中的#base: privateDeployUrl, // 这里加上前缀scrollBehavior: () > ({y: 0}),routes: constantRoutes })vue.config.js&#xff0c;publicPath属性加上前缀 publicPath: proces…