设计模式学习 行为型模式

Posted 聆听微风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式学习 行为型模式相关的知识,希望对你有一定的参考价值。

设计模式学习(二) 行为型模式

一、模板方法模式

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

使用场景:有多个子类共有逻辑相同的方法;重要的、复杂的方法,可以考虑作为模板方法。

(1)角色说明:

  1. 抽象类AbstractClass : 用来定义算法框架和抽象动作

  2. 具体实现类ConcreteClass: 用来实现算法框架中的具体步骤,完成对应与特定子类相关的功能

(2)业务需求:

(3)实现代码:

    public partial class Program
    
        static void Main(string[] args)
        
            Child child = new Child();
            child.m();
        
    
public abstract class Parent

    public abstract void op1();
    public abstract void op2();

    public void m()
    
        op1();
        op2();
    


public class Child : Parent

    public override void op1()
    
        Console.WriteLine("OP1 Child");
    

    public override void op2()
    
        Console.WriteLine("OP2 Child");
    

(4)模式特点:

复用:子类可以复用父类定义好的方法

拓展:后续根据业务需求对子类的方法进行修改。

注意:关键是把握变与不变。变化的东西考虑使用abstract,让子类重写;不变的不需要重写

二、状态模式模式

对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

简单的说,当内部状态改变了,它所提供的方法也改变了。

应用场景:行为随状态改变而改变的场景;条件、分支语句的代替者。

(1)角色说明:

  • 环境(Context)角色:也叫环境类, 又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
  • 环境类实际上是真正拥有状态的对象,我们只是将环境类中与状态有关的代码提取出来封装到专门的状态类中。
  • 抽象状态(State)角色: 它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
  • 具体状态(Concrete State)角色: 它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。

案例代码:

  public partial class Program
    
        static void Main(string[] args)
        
            Context context = new Context();
            context.Handle();
            context.Handle();
            context.Handle();

        
    

    public class Context
    
        private State state;

        //定义环境类的初始状态
        public Context()
        
            this.state = new ConcreteStateA();
        

        //设置新状态
        public void setState(State state)
        
            this.state = state;
        


        //对请求做处理
        public void Handle()
        
            state.Handle(this);
        
    

    //抽象状态类
    public abstract class State
    
        public abstract void Handle(Context context);
    

    //具体状态A类
    public class ConcreteStateA : State
    
        public override void Handle(Context context)
        
            Console.WriteLine("当前状态是 A.");
            context.setState(new ConcreteStateB());
        
    
    

    //具体状态B类
    class ConcreteStateB : State
    
        public override void Handle(Context context)
        
            Console.WriteLine("当前状态是 B.");
            context.setState(new ConcreteStateA());
        
    

状态模式将一个对象在不同状态下的不同行为封装在一个个状态类中,通过设置不同的状态对象可以让环境对象拥有不同的行为,而状态转换的细节对于客户端而言是透明的,方便了客户端的使用。

在实际开发中,状态模式具有较高的使用频率,在工作流和游戏开发中状态模式都得到了广泛的应用,例如公文状态的转换、游戏中角色的升级等。

三、策略模式

策略模式:策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口 和 具体行为的实现。

策略模式最大的特点是行为的变化,行为之间可以相互替换。

本模式使得算法可独立于使用它的用户而变化。

(1)角色

1.策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法。

2.具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现。

3.策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。

(2)代码实现略

(3)使用场景:允许系统动态的选择算法。

四、命令模式

命令(Command)模式又叫作动作(Action)模式或事务(Transaction)模式,是一种对象的行为模式。将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。

角色说明:

实体对象与行为操作之间往往也存在耦合关系。

行为请求者通过“命令”行为实现者去执行一定的行为。

GoF:将请求封装成一个对象,从而使我们可用不同的请求对客户程序进行参数化操作,以及对请求排队或记录讲求日志,以及支持可撤销的操作。

    public partial class Program
    
        static void Main(string[] args)
        
            Receiver receiver = new Receiver();
            Command command = new ConcreteCommand(receiver);
            Invoker invoker = new Invoker();
            invoker.SetCommand(command);
            invoker.invoke();
        
    

    public interface Command
    
        public void Execute();
    

    public class ConcreteCommand : Command
    
        private Receiver receiver = null;
        public ConcreteCommand(Receiver receiver)
        
            this.receiver = receiver;
        

        public void Execute()
        
            receiver.action();
        
    

    public class Receiver
    
        public void action()
        
            Console.WriteLine("接受者开始工作");
        
    

    public class Invoker
    
        private Command Command = null;

        public void SetCommand(Command command)
        
            this.Command = command;
        

        public void invoke()
        
            Command.Execute();
        
    

Invoker:是命令的管理类,这里可以进行设置命令的执行链,设置一些的执行方法(例如撤销等)。

在以下条件下可以考虑使用命令模式:
• 如果需要抽象出需要执行的动作,并参数化这些对象,可以选用命令模式。将这些需要执行的动作抽象成为命令,然后实现命令的参数化配置。
• 如果需要在不同的时刻指定、排列和执行请求,可以选用命令模式。将这些请求封装成为命令对象,然后实现将请求队列化。
• 如果需要支持取消操作,可以选用命令模式,通过管理命令对象,能很容易地实现命令的恢复和重做功能。
• 如果需要支持当系统崩溃时,能将系统的操作功能重新执行一遍,可以选用命令模式。将这些操作功能的请求封装成命令对象,然后实现日志命令,就可以在系统恢复以后,通过日志获取命令列表,从而重新执行一遍功能。
• 在需要事务的系统中,可以选用命令模式。命令模式提供了对事务进行建模的方法。命令模式有一个别名就是Transaction。

五、责任链模式

在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。

请求在这个链上传递,直到链上的某一个对象决定处理此请求。

发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。

使用场景的原理:层级处理(动态指定处理某一组请求时,在不确定接受者的的情况下,向多个对象发送请求时)

小案例的实现代码:

    public partial class Program
    
        static void Main(string[] args)
        
            Handler level1 = new Leader();
            Handler level2 = new Boss();
            level1.SetNextHandler(level2);

            level1.process(8);
            level1.process(15);

        


    

    public abstract class Handler
    
        public Handler NextHandler;
        public void SetNextHandler(Handler nextHandler)
        
            this.NextHandler = nextHandler;
        
        public abstract void process(int info);
    

    public class Leader : Handler
    
        public override void process(int info)
        
            if (info > 0 && info < 11)
            
                Console.WriteLine("Leader 处理");
            
            else
            
                NextHandler.process(info);
            
        
    

    public class Boss : Handler
    
        public override void process(int info)
        
            Console.WriteLine("Boss 处理");
        
    

六、观察者模式

定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象得到通知并被自动更新

模式角色说明:

观察者模式主要由这四个角色组成,抽象主题角色(Subject)、具体主题角色(ConcreteSubject)、抽象观察者角色(Observer)和具体观察者角色(ConcreteObserver)。

  • 抽象主题角色(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
  • 具体主题角色(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
  • 抽象观察者角色(Observer):主要是负责从备忘录对象中恢复对象的状态。

使用场景:

需要关联行为的场景;
事件需要创建一个触发链的场景,比如监控;
跨系统的消息交换场景,比如消息队列、事件总线的处理机制。

小案例描述:张三借了王五的钱。当张三有钱以后通知王五还钱

小案例的实现代码:

Credit 观察者

Debit 主题对象(这里是抽象主题对象),Zhangsan是具体主题对象

通过订阅的方式:主题对象添加观察者。

主题对象通过方法:去通知观察者

    public partial class Program
    
        static void Main(string[] args)
        
            Debit zhansan = new Zhangsan();
            zhansan.borrow(new Wangwu());
            zhansan.notifCredits();
        

    

    interface Debit
    
        void borrow(Credit credit);
        void notifCredits();
    

    /// <summary>
    /// 借款人
    /// </summary>
    class Zhangsan : Debit
    
        private List<Credit> allCredits = new List<Credit>();
        public void borrow(Credit credit)
        
            allCredits.Add(credit);
        

        public void notifCredits()
        
            Console.WriteLine("张三通知还款了");

            foreach (Credit item in allCredits)
            
                item.takeMoney();
            
            
        
    

    /// <summary>
    /// 被借款人
    /// </summary>
    interface Credit
    
        void takeMoney();
    

    class Wangwu: Credit
    
        public void takeMoney()
        
            Console.WriteLine("王五拿到欠款了");
        
    

七、中介者模式

中介者模式(Mediator Pattern),定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为,属于行为型模式。 其主要的目的是用来降低多个对象和类之间的通信复杂性。

角色说明:

中介者模式主要由这四个角色组成, 抽象中介者(Mediator)、具体中介者(ConcreteMediator)、 抽象同事类(Colleague)和具体同事类(ConcreteColleague) 。

  • 抽象中介者(Mediator): 定义了同事对象到中介者对象之间的接口。
  • 具体中介者(ConcreteMediator): 实现抽象中介者的方法,它需要知道所有的具体同事类,同时需要从具体的同事类那里接收信息,并且向具体的同事类发送信息。
  • 抽象同事类(Colleague): 定义了中介者对象的接口,它只知道中介者而不知道其他的同事对象。
  • 具体同事类(ConcreteColleague) : 每个具体同事类都只需要知道自己的行为即可,但是他们都需要认识中介者。

说明:

  1. 中介者模式通过中介者对象来封装一系列的对象交互,将对象间复杂的关系网状结构变成结构简单的以中介者为核心的星形结构,对象间一对多的关联转变为一对一的关联,简化对象间的关系,便于理解;各个对象之间的关系被解耦,每个对象不再和它关联的对象直接发生相互作用,而是通过中介者对象来与关联的对象进行通讯,使得对象可以相对独立地使用,提高了对象的可复用和系统的可扩展性。

  2. 在中介者模式中,中介者类处于核心地位,它封装了系统中所有对象类之间的关系,除了简化对象间的关系,还可以对对象间的交互进行进一步的控制。

小案例的代码实现:让代理人帮助两个人相亲。

抽象代理人接口提供pair和register方法。具体代理人实现。

Person调用findPartner,实际上是调用Agency的方法。

    public partial class Program
    
        static void Main(string[] args)
        
            Agency agency = new AgencyImpl();
            Person person1 = new Person("张三", 12, 15, agency);
            Person person2 = new Person("李四", 15, 15, agency);
            agency.register(person1);
            agency.register(person2);
            agency.pair(person1);
        



    
    public interface Agency
    
        public void pair(Person person);
        public void register(Person person);

    

    public class Person
    
        public string name;
        public int age;
        public int requsterAge;
        public Agency agency;

        public Person(string name, int age, int requsterAge, Agency agency)
        
            this.name = name;
            this.age = age;
            this.requsterAge = requsterAge;
            this.agency = agency;
        

        public void findPartner()
        
            agency.pair(this);
        
    

    public class AgencyImpl : Agency
    
        List<Person> listPerson = new List<Person>();
        public void register(Person person)
        
            listPerson.Add(person);
        

        public void pair(Person person)
        
            foreach (Person p in listPerson)
            
                if (person.requsterAge == p.age)
                
                    Console.WriteLine("让 "+person.name + " 与 " + p.name + " 相亲");
                
            
        

八、迭代器模式

迭代器模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示,属于行为型模式。 它提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。

 角色说明:

  ◊ Iterator:迭代器定义访问和遍历元素的接口

  ◊ ConcreteIterator

    ° 具体迭代器实现迭代器接口

    ° 对该聚合遍历时跟踪当前位置

  ◊ Aggregate:聚合定义创建Iterator对象的接口

  ◊ ConcreteAggregate:具体聚合,实现相应迭代器的接口,返回具体迭代器的一个适当的实例。

  在迭代器模式中,ConcreteAggregate通过Aggregate定义的接口得到Iterator,并且这是一个ConcreteIterator,该ConcreteIterator具体实现了对ConcreteAggregate的访问与遍历的方法。通过ConcreteIterator可以访问并使用集合中的元素。

    /// <summary>
    /// 抽象迭代器,提供接口
    /// </summary>
    public abstract class Iterator
    
        public abstract object First();
        public abstract object Next();
        public abstract bool IsDone();
        public abstract object CurrentItem();
    


    /// <summary>
    ///  具体迭代器
    /// </summary>
    public class ConcreteIterator : Iterator
    
        private ConcreteAggregate _aggregate;
        private int _current = 0;

        public ConcreteIterator(ConcreteAggregate aggregate)
        
            this._aggregate = aggregate;
        

        public override object First()
        
            return _aggregate[0];
        

        public override object Next()
        
            object ret = null;
            if (_current < _aggregate.Count - 1)
            
                ret = _aggregate[++_current];
            

            return ret;
        

        public override object CurrentItem()
        
            return _aggregate[_current];
        

        public override bool IsDone()
        
            return _current >= _aggregate.Count;
        
    

    /// <summary>
    /// 抽象聚合
    /// </summary>
    public abstract class Aggregate
    
        public abstract Iterator CreateIterator();
    

    /// <summary>
    ///  具体聚合
    /// </summary>
    public class ConcreteAggregate : Aggregate
    
        private ArrayList _items = new ArrayList();

        public override Iterator CreateIterator()
        
            return new ConcreteIterator(this);
        

        public int Count
        
            get  return _items.Count; 
        

        public object this[int index]
        
            get  return _items[index]; 
            set  _items.Insert(index, value); 
        
    



    public partial class Program
    
        static void Main(string[] args)
        
            ConcreteAggregate a = new ConcreteAggregate();
            a[0] = "Item A";
            a[1] = "Item B";
            a[2] = "Item C";
            a[3] = "Item D";

            ConcreteIterator i = new ConcreteIterator(a);

            Console.WriteLine("Iterating over collection:");
            object item = i.First();
            while (item != null)
            
                Console.WriteLine(item);
                item = i.Next();
            
        
    

九、访问者模式

访问者模式(VisitorPattern),顾名思义使用了这个模式后就可以在不修改已有程序结构的前提下,通过添加额外的访问者来完成对已有代码功能的提升,它属于行为模式。访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
其主要目的是将数据结构与数据操作分离。

角色:

  • 抽象访问者(Visitor)角色:声明了一个或者多个方法操作,形成所有的具体访问者角色必须实现的接口。
  • 具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作。
  • 用户(Client)角色:声明一个接受操作,接受一个访问者对象作为一个参数。
  • 具体节点(ConcreteNode)角色:实现了抽象节点所规定的接受操作。
  • 结构对象(ObjectStructure)角色:有如下的责任,可以遍历结构中的所有元素。

使用场景:

对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作;
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。

小案例说明:

有一台小机器人,它因为某些问题,计算计算1+1=2时,只能显示"1+1",而无法显示完全。所以工程人员使用补丁包的方式让机器人升级。

小案例代码实现:

Vistor 是接口,可以定义具体的实现类

元素接收访问器,又把自己传递给访问器

UpdateVisitor 是具体访问器

Hardware 是抽象元素

CPU 是具体元素

Robot作为客户,有元素对象。可以使用Accept命令来使用具体访问器

    public partial class Program
    
        static void Main(string[] args)
        
            Robot robot = new Robot();
            robot.calc();
            Vistor updatePack = new UpdateVisitor();
            robot.accept(updatePack);
            robot.calc();
        
    


    public class Robot
    
        private CPU cpu;
        public Robot()
        
            cpu = new CPU("1+1");
        

        public void calc()
        
            cpu.run();
        

        public void accept(Vistor vistor)
        
            cpu.accept(vistor);
        
    

    public abstract class Hardware
    
        public string command;
        public Hardware(string command)
        
            this.command = command;
        

        public void run()
        
            Console.WriteLine(command);
        

        public abstract void accept(Vistor vistor);
    

    public interface Vistor
    
        public void vistorCPU(CPU cpu);

    

    public class UpdateVisitor : Vistor
    
        public void vistorCPU(CPU cpu)
        
            cpu.command += "=2";
        
    

    public class CPU : Hardware
    
        public CPU(string command) : base(command)
        

        

        public override void accept(Vistor vistor)
        
            vistor.vistorCPU(this);
        
    

十、备忘录模式

备忘录模式(Memento Pattern)用于保存一个对象的某个状态,以便在适当的时候恢复对象,该模式属于行为型模式。
其主要目的是在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

简单来说:备份

角色说明:

  • 备忘录(Memento):主要的功能是包含要被恢复的对象的状态。
  • 发起人(Originator):在创建的时候,会在备忘录对象中存储状态。
  • 负责人(Caretaker):主要是负责从备忘录对象中恢复对象的状态。

代码实现:

    public partial class Program
    
        static void Main(string[] args)
        
            History history = new History();    
            Document document = new Document();
            document.change("abc");
            history.push(document.save());

            document.change("bcd");
            history.push(document.save());

            document.change("fgh");

            document.resume(history.getLastVersion());
            document.print();
            document.resume(history.getLastVersion());
            document.print();
        
    

    public class Document
    
        private string content;
        public BackUp save()
        
            return new BackUp(content);
        
        public void resume(BackUp backUp)
        
            content = backUp.content;
        
        public void change(string content)
        
            this.content = content;
        
        public void print()
        
            Console.WriteLine(content);
        
    

    public interface Memonto
     

    public class BackUp: Memonto
    
        public string content;

        public BackUp(string content)
        
            this.content = content;
        
    

    public class History
    
        Stack<BackUp> backUpstack = new Stack<BackUp>();
        public void push(BackUp backUp)
        
            backUpstack.Push(backUp);
        

        public BackUp getLastVersion()
        
            return backUpstack.Pop();
        
    

十一、解释器模式

解释器模式顾名思义,就是对某事物进行解释。给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。

解释器模式其实就是对某事物进行解释。比如生活中经常用到的计算器,将我们用的语言转换成计算器预言,还有我们编写代码时用到的正则表达式等等。

角色说明:

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

几个应用场景:

  1. 进行人民币大写与对应数字的转换

一个小的实现案例:

    public partial class Program
    
        static void Main(string[] args)
        
            string sentence = "美丽的古老的北京";
            new Context().Interpreter(sentence);
            Console.ReadLine();
        
    

    public abstract class AbstractExpression
    
        public abstract void Interpreter(string context);
    

    public class TerminalExpression : AbstractExpression
    
        public override void Interpreter(string context)
        
            Console.WriteLine($"Interpreter in TerminalExpression: context");
        
    
    public class NonterminalExpression : AbstractExpression
    
        public override void Interpreter(string context)
        
            if (context.Contains("的"))
            
                var index = context.IndexOf("的");
                Console.WriteLine($"Interpreter in NonterminalExpression: context.Substring(0, index + 1)");
                new NonterminalExpression().Interpreter(context.Substring(index + 1));
            
            else
            
                new TerminalExpression().Interpreter(context);
            
        
    

    public class Context
    
        public void Interpreter(string sentence)
        
            if (sentence.Contains("的"))
            
                new NonterminalExpression().Interpreter(sentence);
            
            else
            
                new TerminalExpression().Interpreter(sentence);
            
        
    

从零开始学习Java设计模式 | 行为型模式篇:状态模式

在本讲,我们来学习一下行为型模式里面的第五个设计模式,即状态模式。

状态模式引入案例

在学习状态模式之前,我们先来看一个案例,通过该案例来引出状态模式。

这个案例就是通过电梯按钮来控制一个电梯的状态。一个电梯有开门状态、关门状态、停止状态、运行状态等四种状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,那么就不能进行开门操作。为什么呢?你想啊,现在电梯正处于运行状态呢,然后门开了,这是不是非常危险呀!所以,当电梯处于运行状态时是不允许执行开门操作的。而如果电梯门是停止状态,那么就可以执行开门操作了。

下面我们就来看一下对于以上案例所设计出来的类图。

从以上类图中可以看到,我们首先定义了一个ILift接口,而且它里面声明有四个常量,即OPENING_STATE、CLOSING_STATE、RUNNING_STATE、STOPPING_STATE,它们分别是来表示电梯的四种状态的,即开启状态、关闭状态、运行状态和停止状态。

此外,ILift接口还提供了5个抽象方法,它们分别是:

  1. setState(int state):设置电梯的状态。因为我们总得记录一下当前电梯的一个状态吧!
  2. open():电梯开门的方法
  3. close():电梯关门的方法
  4. stop():电梯停止的方法
  5. run():电梯运行的方法

注意,ILift接口就是用于提高程序扩展性的,如果后期有其他的实体也拥有电梯的四种状态,那么我们完全可以让它去实现该接口。

然后,我们再来看一下ILift接口的子实现类,即Lift。可以看到,在该类里面定义了一个state属性,这个属性就是用来记录当前电梯状态的。除此之外,该类还重写了父接口中的所有抽象方法。

至于那个客户端类,我们就不用过多地去关注它了。所以,整个看下来,系统设计起来还是比较简单的。接下来,我们就得编写代码来实现以上案例了。

首先,打开咱们的maven工程,并在com.meimeixia.pattern包下新建一个子包,即state.before,也即实现以上案例的具体代码我们是放在了该包下。

然后,创建电梯接口,这里我们就命名为了ILift。

package com.meimeixia.pattern.state.before;

/**
 * 电梯接口
 * @author liayun
 * @create 2021-09-17 10:44
 */
public interface ILift {

    // 定义四个电梯状态的常量
    int OPENING_STATE = 1;
    int CLOSING_STATE = 2;
    int RUNNING_STATE = 3;
    int STOPPING_STATE = 4;

    // 设置电梯状态的功能
    void setState(int state);

    // 电梯操作功能
    void open();

    void close();

    void run();

    void stop();

}

接着,创建电梯接口的子实现类,即电梯类,这里我们就命名为了Lift。

package com.meimeixia.pattern.state.before;

/**
 * 电梯类(ILift接口的子实现类)
 * @author liayun
 * @create 2021-09-17 10:51
 */
public class Lift implements ILift {

    // 声明一个记录当前电梯状态的成员变量
    private int state;

    @Override
    public void setState(int state) {
        this.state = state;
    }

    @Override
    public void open() {
        switch (state) { // 判断当前电梯的状态
            case OPENING_STATE:
                // 如果当前电梯正处于开启状态,那么我们再去开门,这就没有任何意义了,所以这儿我们什么事都不做
                break;
            case CLOSING_STATE:
                // 如果当前电梯正处于关闭状态,那么我们就能开电梯门了
                System.out.println("电梯打开了...");
                // 设置当前电梯状态为开启状态
                setState(OPENING_STATE);
                break;
            case STOPPING_STATE:
                // 如果当前电梯正处于停止状态,那么我们也是能开电梯门的
                System.out.println("电梯打开了...");
                // 设置当前电梯状态为开启状态
                setState(OPENING_STATE);
                break;
            case RUNNING_STATE:
                // 如果当前电梯正处于运行状态,那么我们肯定是不能去开门的,因为电梯运行时是不能开门的,所以这儿我们什么事都不做
                break;
        }
    }

    @Override
    public void close() {
        switch (this.state) {
            case OPENING_STATE:
                // 如果当前电梯正处于开启状态,那么我们就能关闭电梯门了
                System.out.println("电梯关门了...");
                // 设置当前电梯状态为关闭状态
                this.setState(CLOSING_STATE);
                break;
            case CLOSING_STATE:
                // 如果当前电梯正处于关闭状态,那么我们再去关门,这就没有任何意义了,所以这儿我们什么事都不做
                // do nothing
                break;
            case RUNNING_STATE:
                // 如果当前电梯正处于运行状态,很显然,此时电梯门肯定是关着的,那么我们就不能再去关门了,所以这儿我们什么事都不做
                // do nothing
                break;
            case STOPPING_STATE:
                // 如果当前电梯正处于停止状态,很显然,此时电梯门肯定也是关着的,那么我们就不能再去关门了,所以这儿我们什么事都不做
                // do nothing
                break;
        }
    }

    @Override
    public void run() {
        switch (this.state) {
            case OPENING_STATE:
                // 如果当前电梯正处于开启状态,那么我们肯定是不能让电梯运行的,因为电梯不能开着门就走,所以这儿我们什么事都不做
                // do nothing
                break;
            case CLOSING_STATE:
                // 如果当前电梯正处于关闭状态,那么我们就能让电梯运行了
                System.out.println("电梯开始运行了...");
                // 设置当前电梯状态为运行状态
                this.setState(RUNNING_STATE);
                break;
            case RUNNING_STATE:
                // 如果当前电梯正处于运行状态,那么我们再去运行电梯,这就没有任何意义了,所以这儿我们什么事都不做
                // do nothing
                break;
            case STOPPING_STATE:
                // 如果当前电梯正处于停止状态,那么我们也是可以让电梯运行的
                System.out.println("电梯开始运行了...");
                // 设置当前电梯状态为运行状态
                this.setState(RUNNING_STATE);
                break;
        }
    }

    @Override
    public void stop() {
        switch (this.state) {
            case OPENING_STATE:
                // 如果当前电梯正处于开启状态,那么我们再让电梯停止下来,就没有必要了,因为开门的电梯已经是停止的了(正常情况下),所以这儿我们什么事都不做
                // do nothing
                break;
            case CLOSING_STATE:
                // 如果当前电梯正处于关闭状态,那么我们就能让电梯停止下来了,因为电梯关门时才可以停止
                System.out.println("电梯停止了...");
                // 设置当前电梯状态为停止状态
                this.setState(STOPPING_STATE);
                break;
            case RUNNING_STATE:
                // 如果当前电梯正处于运行状态,那么我们也是能让电梯停止的,因为电梯运行时本身就可以停止啊
                System.out.println("电梯停止了...");
                // 设置当前电梯状态为停止状态
                this.setState(STOPPING_STATE);
                break;
            case STOPPING_STATE:
                // 如果当前电梯正处于停止状态,那么我们再去让电梯停止下来,这就没有任何意义了,所以这儿我们什么事都不做
                // do nothing
                break;
        }
    }

}

最后,创建客户端类用于测试。

package com.meimeixia.pattern.state.before;

/**
 * @author liayun
 * @create 2021-09-17 11:20
 */
public class Client {

    public static void main(String[] args) {
        // 创建电梯对象
        Lift lift = new Lift();

        // 设置当前电梯的状态
        lift.setState(ILift.OPENING_STATE);

        // 打开
        lift.open();
        lift.close();
        lift.run();
        lift.stop();
    }
}

此时,运行以上客户端类,打印结果如下图所示,下面我就来解释一下为何会打印出这样的结果。

当前电梯正处于开启状态,于是你再去开电梯门,那就没有任何意义了,所以执行电梯的open方法去开电梯门时,你会发现并没有任何输出。但是,现在关闭电梯门是可行的,所以在执行电梯的close方法时,你就能看到相应的输出结果了,而且此时电梯的状态就变成关闭状态了。

当电梯处于关闭状态时,你就能让电梯运行起来了,所以在执行电梯的run方法时,你就能看到相应的输出结果了,而且此时电梯的状态又变成了运行状态。

当电梯处于运行状态时,能让电梯停止吗?当然可以,所以在执行电梯的stop方法时,你就能看到相应的输出结果了,而且此时电梯的状态又变成了停止状态。

大家试想一下,如果将当前电梯的状态设置为运行状态,那么打印的结果又会是什么呢?

你会发现只打印了一句话,为什么会这样呢?因为当前电梯正处于运行状态,那么此时是不允许你去开电梯门的,要是你在电梯运行的过程中开门那得多危险啊!那去关电梯门,可不可以呢?大可不必啊,因为电梯在运行过程中,本身电梯门就是关闭的,你再去关电梯门,不是有点脱裤子放屁的意思吗?那去运行电梯,可不可以呢?同样的道理啊,大可不必,因为电梯本身就在运行过程中,你再去运行电梯,那就没有必要了。那去让电梯停下来呢?此时就可以了,所以在执行电梯的stop方法时,你就能看到相应的输出结果了,即电梯停止了…

大家再想一下,我们上面设计的系统有没有什么问题啊?是不是有如下这样的问题啊!

  • 使用了大量的switch case这样的判断语句(当然了,有些人比较喜欢使用if else语句,不过效果都是一样),使程序的可阅读性变得特别差。尤其是咱们Lift类中的方法,你会发现阅读起来体验特别特别差。

  • 扩展性很差。如果新加了断电的状态,那么我们就需要修改上面的判断逻辑。

    其实,不光要修改上面的判断逻辑,我们还得在ILift接口里面定义一个表示断电状态的常量,然后再定义一个抽象的方法,接下来,在子类中还要去重写这个方法,并且对于前面已经定义好的四个方法也要进行一个修改,所以程序的扩展性是非常差的。

问题既然出现了,那么应该如何解决呢?嘿嘿,此时,我们就要使用状态模式了。那什么是状态模式呢?下面我就会讲到。

概述

上面我们做了一个电梯的案例,也引出了该电梯案例所存在的问题,并提出了解决方案,也就是使用状态模式来进行一个改进。那什么是状态模式呢?接下来,我们就来看一看它的概念。

对有状态的对象,把复杂的"判断逻辑"提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

大家猛然看到状态模式的概念会有点懵!不过没关系,下面我会向大家一句一句来解释。

以上是关于设计模式学习 行为型模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式学习 行为型模式

从零开始学习Java设计模式 | 行为型模式篇:状态模式

从零开始学习Java设计模式 | 行为型模式篇:状态模式

从零开始学习Java设计模式 | 行为型模式篇:责任链模式

从零开始学习Java设计模式 | 行为型模式篇:责任链模式

从零开始学习Java设计模式 | 行为型模式篇:解释器模式