设计模式(五)--行为类模式

1.设计之禅:第10章 模板方法模式 · 设计模式之禅(第2版) · 看云

2.本文引用博客:https://blog.csdn.net/zhengzhb/article/category/926691

3.图解设计模式:行为型模式 — Graphic Design Patterns

0.总结

行为类模式包括责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式
行为模式描述了对象和类的模式,以及它们之间的通信模式
策略模式的意图是封装算法,它认为“算法”已经是一个完整的、不可拆分的原子业务(注意这里是原子业务,而不是原子对象),即其意图是让这些算法独立,并且可以相互替换,让行为的变化独立于拥有行为的客户;而命令模式则是对动作的解耦,把一个动作的执行分为执行对象(接收者角色)、执行行为(命令角色),让两者相互独立而不相互影响。
策略模式的环境角色只是一个委托作用,负责算法的替换;而状态模式的环境角色不仅仅是委托行为,它还具有登记状态变化的功能,与具体的状态类协作,共同完成状态切换行为随之切换的任务。
状态模式旨在解决内在状态的改变而引起行为改变的问题,它的出发点是事物的状态,封装状态而暴露行为,一个对象的状态改变,从外界来看就好像是行为改变
触发链和责任链虽然都是链结构,责任链模式基本上不改变消息对象的结构,虽然每个节点都可以参与消费(一般是不参与消费),类似于“雁过拔毛”,但是它的结构不会改变

1.模板模式

定义:定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法中的某些特定步骤。
三种方式

  • 抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现。
  • 模版方法:由抽象类声明并加以实现。一般来说,模版方法调用抽象方法来完成主要的逻辑功能,并且,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。
  • 钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模版方法的逻辑

使用:分为模板方法和基本方法(处理基本逻辑,设为protected),/调用模板方法中调用处理基本逻辑,“父类怎么调用子类的方法”。
就像邮件有开头和结尾, 把重复的开头和结尾抽象成一个类,固定的流程抽象出来函数

2.中介者模式: 

定义:用一个中介者对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使耦合松散,而且可以独立地改变它们之间的交互。
结构

  • 抽象中介者:定义好同事类对象到中介者对象的接口,用于各个同事类之间的通信。一般包括一个或几个抽象的事件方法,并由子类去实现。
  • 中介者实现类:从抽象中介者继承而来,实现抽象中介者中定义的事件方法。从一个同事类接收消息,然后通过消息影响其他同时类。中介者最重要的方法叫做事件方法,处理多个对象之间的关系
  • 同事类:如果一个对象会影响其他的对象,同时也会被其他对象影响,那么这两个对象称为同事类。在类图中,同事类只有一个,这其实是现实的省略,在实际应用中,同事类一般由多个组成,他们之间相互影响,相互依赖。同事类越多,关系越复杂。并且,同事类也可以表现为继承了同一个抽象类的一组实现组成。在中介者模式中,同事类之间必须通过中介者才能进行消息传递。

优点:各类的结构由网状变成星状,每个对象只是与中介者Mediator之间产生依赖
举例:A是B的10倍,进销存,组成同事抽象类和中介抽象类

3.观察者模式:  

定义:定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。那就是一个对象要时刻监听着另一个对象,只要它的状态一发生改变,自己随之要做出相应的行动
结构: 

  • 被观察者:从类图中可以看到,类中有一个用来存放观察者对象的Vector容器(之所以使用Vector而不使用List,是因为多线程操作时,Vector在是安全的,而List则是不安全的),这个Vector容器是被观察者类的核心,另外还有三个方法:attach方法是向这个容器中添加观察者对象;detach方法是从容器中移除观察者对象;notify方法是依次调用观察者对象的对应方法。这个角色可以是接口,也可以是抽象类或者具体的类,因为很多情况下会与其他的模式混用,所以使用抽象类的情况比较多。
  • 观察者:观察者角色一般是一个接口,它只有一个update方法,在被观察者状态发生变化时,这个方法就会被触发调用。
  • 具体的被观察者:使用这个角色是为了便于扩展,可以在此角色中定义具体的业务逻辑。
  • 具体的观察者:观察者接口的具体实现,在这个角色中,将定义被观察者对象状态发生变化时所要处理的逻辑。

注意:异步处理就要考虑线程安全和队列的问题,在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次)
例子:文件系统,ATM取钱, 广播收音机

4.命令模式: 

定义:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
例子:在GUI开发中,一个按钮的点击是一个命令,可以采用命令模式;模拟DOS命令的时候,当然也要采用命令模式;触发-反馈机制的处理等
结构

  • Command类:是一个抽象类,类中对需要执行的命令进行声明,一般来说要对外公布一个execute方法用来执行命令。
  • ConcreteCommand类:Command类的实现类,对抽象类中声明的方法进行实现。
  • Client类:最终的客户端调用类。
  • Invoker类:调用者,负责调用命令。
  • Receiver类:接收者,负责接收命令并且执行命令。

执行的时序首先是调用者类,然后是命令类,最后是接收者类。把命令的调用者与执行者分开,使双方不必关心对方是如何操作的;多数请求-响应模式的功能,比较适合使用命令模式,实现记录日志、撤销操作等功能

5.责任链模式

定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
组成

  • 抽象处理类:抽象处理类中主要包含一个指向下一处理类的成员变量nextHandler和一个处理请求的方法handRequest,handRequest方法的主要主要思想是,如果满足处理的条件,则有本处理类来进行处理,否则由nextHandler来处理。
  • 具体处理类:具体处理类主要是对具体的处理逻辑和处理的适用条件进行实现。

例子:责任链模式其实就是一个灵活版的if…else…语句:demo转
注意:链中节点数量需要控制,避免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。

// 1.实际判定条件复杂,可能是查询数据库等,代码会堆积
// 2.耦合高:继续添加处理条件要改if else, 是有顺序的
void test(Request request)
{
    int money = request.getRequestMoney();
    if (money < 1000)
    {
        Clerk.response(request);
    }
    else if (money <= 5000)
    {
        Leader.response(request);
    }
    else if (money < 10000)
    {
        Manager.response(request);
    }
}

// 定义借款需求:抽象了request.getRequestMoney();
class BorrowRequest
{
private:
    int requestMoney;

public:
    BorrowRequest(int money)
    {
        requestMoney = money;
    }
    int getMoney()
    {
        return requestMoney;
    }
};

// 抽象实现if else
class AbstructClerk
{
private:
    AbstructClerk mSuperior;
public:
    void setSuperior(AbstructClerk superior)
    {
        mSuperior = superior;
    }
    void approveRequest(BorrowRequest request)
    {
        if (getLimit() >= request.getMoney())
        {
            std::cout << "OK" << std::endl;
        }
        else
        {
            if (mSuperior != nullptr)
            {
                mSuperior.approveRequst(request);
            } else
            {
                std::cout << "not OK" << std::endl;
            }
        }
    }
    virtual int getLimint()
    {
    }
};

// 具体员工
class Clerk : public AbstructClerk
{
private:
    /* data */
public:
    virtual int getLimint()
    {
        return 1000;
    }
};
// 具体经理
class Leader : public AbstructClerk
{
private:
    /* data */
public:
    virtual int getLimint()
    {
        return 5000;
    }
};
// 具体主管
class Manager : public AbstructClerk
{
private:
    /* data */
public:
    virtual int getLimint()
    {
        return 10000;
    }
};

int main ()
{
    AbstructClerk clerk = new Clerk();
    AbstructClerk leader = new Leader();
    AbstructClerk manager = new Manager();

    clerk.setSuperior(leader);
    leader.setSuperior(manager);

    clerk.approveRequest(new BorrowRequest(10000));
    clerk.approveRequest(new BorrowRequest(100000));
    return 0;
}

6. 解释器模式:

定义:给定一种语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子。
结构

  • 抽象解释器:声明一个所有具体表达式都要实现的抽象接口(或者抽象类),接口中主要是一个interpret()方法,称为解释操作。具体解释任务由它的各个实现类来完成,具体的解释器分别由终结符解释器TerminalExpression和非终结符解释器NonterminalExpression完成。
  • 终结符表达式:实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。终结符一半是文法中的运算单元,比如有一个简单的公式R=R1+R2,在里面R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。                                
  • 非终结符表达式:文法中的每条规则对应于一个非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,+就是非终结符,解析+的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
  • 环境角色:这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,我们给R1赋值100,给R2赋值200。这些信息需要存放到环境角色中,很多情况下我们使用Map来充当环境角色就足够了。

特性:解释器模式真的是一个比较少用的模式,因为对它的维护实在是太麻烦了,想象一下,一坨一坨的非终结符解释器,假如不是事先对文法的规则了如指掌,或者是文法特别简单,则很难读懂它的逻辑。解释器模式在实际的系统开发中使用的很少,因为他会引起效率、性能以及维护等问题

 7. 迭代器模式:

定义:提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节。
组成

  • 抽象容器:一般是一个接口,提供一个iterator()方法,例如java中的Collection接口,List接口,Set接口等。
  • 具体容器:就是抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkList,Set接口的哈希列表的实现HashSet等。
  • 抽象迭代器:定义遍历元素所需要的方法,一般来说会有这么三个方法:取得第一个元素的方法first(),取得下一个元素的方法next(),判断是否遍历结束的方法isDone()(或者叫hasNext()),移出当前对象的方法remove(),
  • 迭代器实现:实现迭代器接口中定义的方法,完成集合的迭代

8.备忘录模式

定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。就可以将该对象恢复到原先保存的状态
结构

  • 发起人:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
  • 备忘录:负责存储发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
  • 管理角色:对备忘录进行管理,保存和提供备忘录。、

例子:需要备份的时候就创建一个备份,然后丢给备忘录管理者进行管理,要取的时候再从管理者手中拿到这个备份。这个备份者就类似于一个备份的仓库管理员,创建一个丢进去,需要的时候再拿出来。这就是备忘录模式。clone方式的备忘录,

9.状态模式:  

定义:当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类
结构

  • State——抽象状态角色接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换。
  • ConcreteState——具体状态角色每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。
  • Context——环境角色定义客户端需要的接口,并且负责具体状态的切换。

优点:避免了过多的switch...case或者if...else语句的使用,避免了程序的复杂性,提高系统的可维护性
例子:状态机

10.策略模式:

定义:定义一组算法,将每个算法都封装起来,并且使他们之间可以互换。
结构

  • 封装类:也叫上下文,对策略进行二次封装,目的是避免高层模块对策略的直接调用。
  • 抽象策略:通常情况下为一个接口,当各个实现类中存在着重复的逻辑时,则使用抽象类来封装这部分公共的代码,此时,策略模式看上去更像是模版方法模式。
  • 具体策略:具体策略角色通常由一组封装了算法的类来担任,这些类之间可以根据需要自由替换。
  • 彻底消灭if-else,你需要这8种方案! - 知乎 (zhihu.com)
// 1.实际判定条件复杂,可能是查询数据库等,代码会堆积
// 2.耦合高:继续添加处理条件要改switch case, 是有顺序的
double discount(double price, int type)
{
    switch (type)
    {
    case 1:
        return price * 0.4;
        break;
    case 2:
        return price * 0.5;
        break;
    default:
        break;
    }
}

// 抽象实现if else
class Abstrategy
{
public:
    virtual double getDiscoutPrice(double price);
};

// 具体员工
class Clerk : public Abstrategy
{
private:
    /* data */
public:
    virtual double getDiscoutPrice(double price)
    {
        return 1000;
    }
};
// 具体经理
class Leader : public Abstrategy
{
private:
    /* data */
public:
    virtual double getDiscoutPrice(double price)
    {
        return 100;
    }
};
// context
class Price
{
private:
    Abstrategy mStrategy;
public:
    Price(Abstrategy st)
    {
        mStrategy = st;
    }
    double discount(double price)
    {
        return mStrategy.getDiscoutPrice(price);
    }
};

int main ()
{
    Price p = new Price(new Leader());
    std::cout << p.discount(3000) << std::endl;
    return 0;
}

11.访问模式: 

定义:封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
结构

  • 抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法中的参数定义哪些对象是可以被访问的。
  • 访问者:实现抽象访问者所声明的方法,它影响到访问者访问到一个类后该干什么,要做什么事情。
  • 抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。抽象元素一般有两类方法,一部分是本身的业务逻辑,另外就是允许接收哪类访问者来访问。
  • 元素类:实现抽象元素类所声明的accept方法,通常都是visitor.visit(this),基本上已经形成一种定式了。
  • 结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值