Pattern 3

允许多个类处理同一个请求而不需要了解彼此的功能。它在类之间提供了一种松散的耦合:类之间的唯一的联系就是相互间传递的请求。请求在类之间传递,直到其中一个类处理它为止。
特性:一、链的组织是从最特殊到最一般;二、不能保证请求一定能响应。
职责链将程序中每个对象能做什么的内容隔离。它的使用范围:
1、                具有相同方法的几个对象都适合于执行程序请求的操作,但由对象决定谁去完成操作,比把决策建立在调用代码中更合适。
2、                其中某个对象可能最适合处理请求,但你不想通过一些列If-else或switch语句去选择一个特定的对象。
3、                程序执行时,需要向处理选项链中添加的对象。
4、                在多个对象都能执行一个请求的情况下,你不想把这些相互作用的内容放在调用程序里。
 
   C#内部,窗口接受各种事件,如MouseMove事件,然后将事件转发给窗体所包含的控件。C#中只有最后的控件接受消息,而在其他语言里,每个内部控件都能接受消息。这是职责链模式的一种典型实现。我们通常认为,C#的类继承结构本身就是该模式的一个例子。如果在底层的派生类里调用一个方法,则该方法调用沿着继承链向上传递,直到发现包含有该方法的第一个父类为止,而更上层的、包含该方法其它实现的父类不会起作用。如:
   public class A
    {
        public virtual void DoSomething()
        { System.Console.WriteLine(" 执行类为:A" ); }
    }
    public class B : A
    {
        public override void DoSomething()
        { System.Console.WriteLine(" 执行类为:B" ); }
    }
public class C : B { }
   //  客户代码:C c =new C(); c.DoSomething();
// 执行的是类B的方法,而非类A的方法。
   // 要想调用A的方法唯有:
    public interface I { void DoSomething();}
    public class A : I
    {
        public virtual void DoSomething()
        { System.Console.WriteLine(" 执行类为:A" ); }
    }
    // 类B没有直接实现接口I,而是继承实现。所以可以有:
    // I i = new B();
    // 但i.DoSomething()方法调用的是A类的方法
    // 只要是实现了接口,调用的就是由该类向其父类查找第一个实现了该方法的类的方法
    public class B : A 
    {
        public new void DoSomething()
        { System.Console.WriteLine(" 执行类为:B" ); }
    }
    public class C : B { }
同其它许多模式一样,该模式的主要目的是减少对象之间的耦合。一个对象只需要知道如何将请求转发给其他对象。
链中的每个C#对象都是“自治”的,它对其它对象一无所知,只需判断它本身能否满足请求。这样,既能独立编写每个对象,又很容易构建链。
你可以决定,链中的最后一个对象使以默认方式处理它收到的所有请求,还是简单地抛弃请求。不管怎样,你必须知道链中最后一个可用的对象是哪一个。
下面是一个职责链模式样例:
    /*
    键入“Mandrill”,能看到图像Mandrill.jpg的显示;
    键入“File”,相应的文件名显示在中央列表框中;
    键入“Blue”,对应的颜色显示在县面的中央面板中;
    如果键入的是其它的字符,则文本会显示在最右边的列表框中。
    */
    public abstract class Chain
    {
        bool hasLink;
        protected Chain chn;
        public Chain() { hasLink = false; }
        public void AddToChain(Chain c) { chn = c; hasLink = true; }
        public Chain GetChain() { return chn; }
        public bool HasChain() { return hasLink; }
        protected void SendChain(string mesg)
        { if (chn != null) chn.SendToChain(mesg); }
 
}
// 图像、文件、颜色类ImageChain、FileChain、ColorChain、NoChain均继承自Chain类。
/ 如此我们只要在程序初始化时,添加如下代码:
        void InitChain()
        {
            Chain chn;
            ColorChain clrChain = new ColorChain();
            FileChain flChain = new FileChain();
            NoChain noChain = new NoChain();
            chn = new ImageChain();
            chn.AddToChain(flChain);
            flChain.AddToChain(clrChain);
            clrChain.AddToChain(noChain);
}
    最后,由于C#部提供多继承,有时候需要基类Chain是一个接口。同时职责链也不必须是线性的,可以是树形或其它结构的。
《Thinking in Pattern 》:
职责链模式可以被想象成递归(recursion)的动态泛化(generalization),这种泛化通过使用Strategy对象来完成。被调用的时候,链表里的每个Strategy对象都试图去满足这次调用。当某个策略(strategy)调用成功或者整个strategy链到达末尾的时候,这个过程结束。递归的时候,某个方法不断的调用它自己直到满足某个结束条件;对于职责链,某个方法调用它自己,进而(通过遍历strategy链表)调用它自己的不同实现,直到满足某个结束条件。所谓结束条件,要么是到达链表的末尾,要么是某一个strategy调用成功。对于第一种情况,得返回一个默认对象;如果不能提供一个默认结果,那就必须以某种方式告知调用者链表访问成功与否。 由于strategy链表中可能会有多于一个的方法满足要求,所以职责链似乎有点专家系统(expert system)的味道。因为这一系列Strategy实际上是一个链表,它可以被动态创建,所以你也可以把职责链想象成更一般化的,动态构建的switch语句。
在《Thinking in Pattern》书中的例子中,就将所有的解决方案放置在一个Strategy数组中,for语句遍历询问每一个Strategy对象是否解决了问题。这同《C#设计模式中文版》的解决方法不同。我觉得,将职责链作为一有向图来实现更加的符合实际。查找一个有向图具体节点就是我们需要找的解决方法。将所有的查询、添加职责等操作交给有向图对象,而不是封装在具体的Strategy中,这不更加完美吗?
思考:如何使用职责链实现邮件过滤?
 
《C#设计模式中文版》
命令模式只将请求转发给一个特定对象。它把一个申请特定操作的请求封装到一个对象中,并给该对象一个众所周知的公共接口,使客户不用了解实际执行的操作就能产生请求,也可以使你改变操作而丝毫不影响客户端程序。例:
    // 将申请Execute操作封装在Command类中
    //ICommand 就是众所周知的申请操作的接口
    public interface ICommand { void Execute(); }
    // 命令的实现者
    public class ImplementHere : ICommand
    {
        public void Execute()
        {   Console.WriteLine("// 方法的具体实现" );     }
 
}
// 客户端通过接口调用具体的实现方法
    ICommand cmd = new ImplementHere();
cmd.Execute();
上面代码我看不出命令模式的意义所在,为此我为调用客户代码定义了一个方法:
        public static void CommandExecute(ICommand cmd) {   cmd.Execute();  }
这样我们就可以为指定操作定义不同的实现。显而易见的,这将客户代码和ICommand接口对象紧密的联系在一起。这并不是命令模式本来的意思。为此我们可以将 实现 ICommand 接口的类对象从执行指定操作的类中分离出来,也即实现和调用的分离。例:
 
    // 执行命令操作的类必须实现该接口
    public interface ICommandHolder
{ ICommand GetCommand(); void SetCommand(ICommand cmd); }
    // 执行命令操作的类必须实现该接口
    public interface ICommandHolder
    { ICommand GetCommand(); void SetCommand(ICommand cmd); }
    // 命令接口
    public interface ICommand { void Execute(); }
    // 所有命令都单独定义为一个命令类,如打开文件这一命令:
    public class OpenCommand : ICommand
    { public void Execute() { // 打开文件的具体实现 } }
这样,我们就可以在客户端代码指定执行操作,而不是关心操作代码的具体实现。其客户代码为:
            CommandNeed cmdObj = new CommandNeed();
            cmdObj.SetCommand(new OpenCommand());
        cmdObj.GetCommand().Execute();
命令模式提供了一个便捷的存储方法并能完成Undo功能。每个命令对象都记住刚做过的事,并在有Undo请求时,只要计算量和内存需求不太过分,就能恢复到刚才的状态。我们重写Icommand接口使其包含Undo方法:
public interface ICommand
{ void Execute(); void Undo(); bool IsUndo();}
并且我们还必须让每个命令对象记录上一次的操作,这样才能取消操作。这就要求,首先,必须保存一个一执行命令列表;其次,每个命令必须包含一个自身执行情况的列表。
执行恢复操作关键就在于必须额外的提供一个UndoCommand类,我们通过调用该类的Execute方法而调用指定操作的Undo()方法。
例:自定义一个按钮,要求点击该按钮可以执行取消工作。
    public static class CommandCall
    {
       UndoCommand undoCommand;
       public static void CommandClick(object sender, EventArgs e)
        {
            ICommand cmd = ((ICommandHolder)sender).GetCommand();
            cmd.Execute();
            undoCommand.Add(cmd);
        }
    }
    // 其中UndoCommand类定义如下:
    public class UndoCommand : ICommand
    {
        ArrayList undoList;
        public UndoCommand() { undoList = new ArrayList(); }
        public void Add(ICommand cmd) { if (!cmd.IsUndo()) { undoList.Add(cmd); } }
        public bool IsUndo() { return true; }
        public void Undo(){     // 恢复操作    }
        public void Execute()
        {
            int index = undoList.Count - 1;
           if (index >= 0)
            {
                ICommand cmd = (ICommand)undoList[index];
                cmd.Undo();
                undoList.RemoveAt(index);
            }
        }
    }
    public interface ICommand { void Execute(); void Undo(); bool IsUndo(); }
    public interface ICommandHolder
    { ICommand GetCommand(); void SetCommand(ICommand cmd); }
    public class UndoButton : Button,ICommandHolder
    {
        ICommand command;
        public ICommand GetCommand() { return command; }
        public void SetCommand(ICommand cmd) { command = cmd; }
    }
    // 客户端代码
            UndoButton unBtn = new UndoButton();
            unBtn.SetCommand(new UndoCommand());
            unBtn.Click += CommandCall.CommandClick;
   思考题:1、鼠标单击列表框项目和单选按钮可构成命令,单击多选列表框也可以表示为命令。设计一个包含这些特殊的程序。
   2、乐天彩票系统使用一个随机数字生成器将整数限制在1-50的范围内,号码选择的间隔由一个随机定时器选定,选择的每个号码必须是唯一的。设计一个模式来选出每周的中将号码。
《Thinking in Pattern
从本质上说,Command就是一个函数对象:一个被封装成对象的方法。通过把方法封装到一个对象,你可以把它当作参数传给其它方法或者对象,让它们在实现你的某个请求(request)的时候完成一些特殊的操作。
Command模式最重要的一点就是,你可以通过它把想要完成的动作(action)交给一个方法或者对象。
 
    《C# 设计模式》
解释器模式:通常描述的是,为了某种语言定义一个文法,并用该文法解释语言中的语句。当一个程序要处理许多不同但又有些类似的问题时,用一种简单的语言描述这些问题,然后让程序解释该语言,是非常便利的方法。
该模式适用性:
1、                当读者需要用一个命令解释分析用户命令时。如用户可以通过输入查徐获得各种答案。
2、                当程序需要分析一个代数串时。如用户输入某个方程式,要求程序根基计算结构执行相应的操作。
3、                当程序要生成各种形式的输出时。如考虑一个程序,它可以按任意顺序显示激烈数据,并能用不同的方法对它们排序。这类程序通常称作表表生成器(Report Generators),它的底层数据可以存在一个关系数据库中,报表程序的用户接口通常比数据库用的Sql语言简单的多。
解释器模式一般有下列参与对象:
1、抽象表达式(AbstractExpression):声明一个抽象的解释操作。
2、终结符表达式(TerminalExpression):解释由文法中终结符组成的表达式。
3、非终结附表达式(NonTerminalExpression):解释文法中所有非终结符表达式。
4、上下文(Context):包含了分析器的一部分全局信息。
5、客户:格局前面的表达式类型构建语法树并调用解释操作。
 
《Thinking in Pattern
如果程序的用户在运行时刻需要更大的灵活性,例如,为了通过创建脚本来描述所期望的系统行为,你就可以使用Interpreter设计模式。这种情况下,你(需要)创建一个语言解释器并把它嵌入到你的程序中。
别忘了,每种设计模式都允许一个或多个可以变化的因素,所以,重要的是首先要知道哪个因素是变化的。有些时候是你的程序的最终用户(而不是程序的编写人员)在他们配置程序的某些方面的时候需要更大的灵活性。也就是说,他们需要做一些简单的编程工作。Interpreter模式通过添加一个语言解释器提供了这种灵活性。 问题是,开发你自己的语言并为它构建一个解释器是一项耗时的工作,而且它会分散你开发应用程序的精力。你必须问问自己是想要写完应用程序呢还是要创造一门新的语言。最好的解决方案就是代码重用:嵌入一个已经构建好并且调试过的解释器。
 
    《C# 设计模式》
迭代器模式允许使用一个标准的接口顺序访问一个数据列表或集合,而又不需要知道数据的内部表示细节。另外,也可以定义专用迭代器,让它完成一些特殊操作并且只返回数据集合中的特定元素。”Design Patterns”中指出,一个恰当的迭代器接口可以是下列形式:
    public interface IIterator
    {
        object First();   object Next();
        bool IsDone();    object CurrentItem();
}
迭代器模式的效果:
1、    数据修改。迭代器中最重要的问题是,遍历数据的同时数据正在被修改。如果代码搜索范围较宽,只是偶尔才移向下一个元素,就有可能在访问集合的同时,底层集合正在添加或删除元素,也可能有另一个线程改变了集合。对这一问题没有简单的解决方案。如果读者想使用迭代器顺序访问一系列数据并删除特定的项目,要当心这种操作的后果。添加或删除一个元素意味着可能会遗漏掉某一个元素或对同一元素访问了两次,这和我们使用的存储机制有关。
2、    迭代器类需要对基本容器类的底层数据结构具有特殊的访问权限,这样才能访问数据。
3、    内部迭代器是能顺序访问整个集合的一些方法,不需要用户发出任何明确的请求就能直接对每个元素进行操作。外部迭代器给我们更多的控制权,因为调用程序可直接访问每个元素,并能决定是否对元素执行操作。
 
《C#设计模式》
中介者作为唯一知道其它类中方法细节的一个类,在类发生变化时通知中介者,中介者再将这些变化传递给其他需要通知的类。
中介者模式的效果:
1、                当一个类的动作要受另一个类的状态影响时,中介者模式可以防止这些类变得混乱。
2、                使用中介者很容易修改一个程序的行为。对多数形式的修改来说,只需要改变或子类化中介者,程序的其它部分保持不表。
3、                可以添加新的控件或类,除了改变中介者外,不用改动任何部分。
4、                中介者解决了每个ICommand对象需要对于接口中的其他对象和方法了解太多的问题。
5、                中介者变成了一个“万能类”,直到太多其它部分程序的信息,这会使它难以修改和维护。有时候可以该上这种状态:将大部分的工能放在各个类中,少部分放在中介者中。每个对象应该完成自己的任务,中介者只需管理对象之间的相互协作。
6、                每个中介者都是一个定制的类,它拥有每个同事要调用的方法,并指导每个同事有那些可用的方法。这使得在不同项目中重用中介者变得困难。从另一方面来说,决大多说中介者都相当简单,编写中介者代码总要比管理复杂的相互协作容易的多。
单接口的中介者:这种中介者模式相当于一种观察者模式,观察每个同事元素的变化,每个元素都对中介者有一个定制的接口。另外一种实现方法是,中介者只有一个接口,通知中介者执行那种操作的对象作为参数传递给该接口方法。在这种方法中,我们不再需要登记运行的组建,只需要创建一个action方法,它针对每个对象具有不同的多态参数。
        public void action(MoveButton mv);
    public void action(clrButton clr);
这样,就不必登记所有的动作对象,如按钮和资源表框,应为可以将其作为参数传递给每隔action方法。
例: 考虑这样一个程序,它有两个按钮(Copy和Clear按钮)、两个列表框和一个文本输入域。程序开始运行时,Copy和Clear按钮都是不可用的。
1 、当选择左编列表框中的一个名字时,它被拷贝到文本域中进行编辑,此时Copy按钮可用。
2 、单击Copy按钮,文本被加到右边列表况钟,此时Clear按钮可用。
3 、如果单击CLear按钮,右边列表框和文本域被清控,列表框时未选中状态,两个按钮都不可用。
控件间的相互协作很复杂,我们可以用中介者模式简化控件的协作。中介者模式让中介者作为唯一了解系统其它类的一个类。每个和中介者通信的控件都称为同事。每个同事收到用户事件时通知中介者,中介者决定应该将该事件通知给其他那一个类。
    //Copy 、Clear等操作封装进Mediator类中
    // 从而实现将复制等操作时可能引起的控件协作全封装在该类中
    // 但对于操作,如ClearClicked,其关联的控件必须全包含在Mediator类中
    public class Mediator
    {
        CpyButton cpButton;    ClrButton clrButton;
        TextBox txKids;
        ListBox leftList;     ListBox klist;
        public Mediator(CpyButton cp, ClrButton clr, ListBox pk) { }
        public void setText(TextBox tx) { txKids = tx; }
        public void clearClicked() { }
        public void copyClicked() { }
    }
    //CpyButton 按钮的Excute方法是通过Mediator类实现的
    // 也就是说将其接口和实现分离开,因此也可以说是个桥接模式。
// 作为一个桥接模式,是因为其将实现分离出;
// 而作为一中介者模式则是因为Mediator类的存在
    //Mediator 管理所有的交互,而Bridger接口则提供不同的服务。
    public class CpyButton : Button, ICommand
    {
        Mediator med;
        public void setMediator(Mediator md) { med = md; }
        public void Execute() { med.CopyClicked(); }
    }
    public class ClrButton : Button, ICommandExecutor
    {
        Mediator med;
        public void setMediator(Mediator md) { med = md; }
        public void Execute() { med.clearClicked(); }
} 
.MODEL SMALL .STACK 100H ; 端口定义 CONTROL_PORT EQU 8006H ; 8255A控制端口 PORT_B EQU 8002H ; PB口(LED输出) PORT_C EQU 8004H ; PC口(开关输入) ; 控制字:方式0,PB口输出,PC口输入 CONTROL_WORD EQU 82H ; 10000010B ; 延时参数(控制循环速度,可通过调整该值修改速度) DELAY_COUNT EQU 0F000H ; 速度参数,值越大速度越慢 .DATA ; 花型初始值 PATTERN1 DB 01H ; 花型1:00000001 PATTERN2 DB 80H ; 花型2:10000000 PATTERN3 DB 01H ; 花型3:初始为00000001 PATTERN4A DB 55H ; 花型4:01010101 PATTERN4B DB 0AAH ; 花型4:10101010 .CODE START: ; 初始化8255A MOV AL, CONTROL_WORD OUT CONTROL_PORT, AL ; 主循环:检测开关状态,执行对应功能 MAIN_LOOP: IN AL, PORT_C ; 读取PC口(开关SW0~SW3状态) AND AL, 0FH ; 只保留低4位(SW0~SW3) CMP AL, 01H ; 检测SW0是否按下(启动) JE START_SYSTEM JMP MAIN_LOOP ; 启动系统后,进入功能选择循环 START_SYSTEM: SYSTEM_LOOP: IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 检测SW1是否按下(暂停) JE MAIN_LOOP CMP AL, 04H ; 检测SW2是否按下(花型1) JE PATTERN1_LOOP CMP AL, 08H ; 检测SW3是否按下(花型2) JE PATTERN2_LOOP CMP AL, 0CH ; 检测SW2+SW3是否按下(花型3) JE PATTERN3_LOOP CMP AL, 00H ; 无开关按下时,默认花型4 JE PATTERN4_LOOP JMP SYSTEM_LOOP ; 花型1:00000001 左循环 PATTERN1_LOOP: MOV AL, [PATTERN1] OUT PORT_B, AL ; 输出到LED CALL DELAY ; 延时 ROL AL, 1 ; 左循环移位 MOV [PATTERN1], AL IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 检测是否暂停 JE MAIN_LOOP CMP AL, 04H ; 检测是否切换花型 JNE PATTERN1_LOOP JMP SYSTEM_LOOP ; 花型2:10000000 右循环 PATTERN2_LOOP: MOV AL, [PATTERN2] OUT PORT_B, AL ; 输出到LED CALL DELAY ; 延时 ROR AL, 1 ; 右循环移位 MOV [PATTERN2], AL IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 检测是否暂停 JE MAIN_LOOP CMP AL, 08H ; 检测是否切换花型 JNE PATTERN2_LOOP JMP SYSTEM_LOOP ; 花型3:00000001→00000011→…→11111111,全亮后闪烁一次循环 PATTERN3_LOOP: MOV BL, [PATTERN3] P3_INCR: MOV AL, BL OUT PORT_B, AL ; 输出到LED CALL DELAY ; 延时 CMP BL, 0FFH ; 判断是否全亮 JE P3_FLASH SHL BL, 1 ; 左移一位,递增亮灯 OR BL, 01H IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 检测是否暂停 JE MAIN_LOOP CMP AL, 0CH ; 检测是否切换花型 JNE P3_INCR JMP SYSTEM_LOOP P3_FLASH: MOV AL, 00H OUT PORT_B, AL ; 全灭 CALL DELAY ; 延时 MOV AL, 0FFH OUT PORT_B, AL ; 全亮 CALL DELAY ; 延时 MOV BL, 01H ; 重置初始值 IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 检测是否暂停 JE MAIN_LOOP CMP AL, 0CH ; 检测是否切换花型 JNE PATTERN3_LOOP JMP SYSTEM_LOOP ; 花型4:01010101与10101010交替闪烁 PATTERN4_LOOP: MOV AL, [PATTERN4A] OUT PORT_B, AL ; 输出01010101 CALL DELAY ; 延时 MOV AL, [PATTERN4B] OUT PORT_B, AL ; 输出10101010 CALL DELAY ; 延时 IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 检测是否暂停 JE MAIN_LOOP CMP AL, 00H ; 检测是否切换花型 JNE PATTERN4_LOOP JMP SYSTEM_LOOP ; 延时子程序(通过循环计数实现速度控制) DELAY: MOV CX, DELAY_COUNT DELAY_LOOP: NOP NOP LOOP DELAY_LOOP RET END START 纠正里面的错误
最新发布
11-04
<think>我们正在处理一段8086汇编代码,这段代码用于控制8255A芯片,通过开关输入控制LED显示不同的花型。 根据代码,我们注意到可能存在一些错误,我们将逐一检查并修正。 主要问题分析: 1. 在花型3的循环中,当全亮后闪烁一次循环,但代码中在闪烁后重置BL为01H,然后继续循环。然而,在闪烁后没有立即跳转回递增部分,而是继续执行了检测开关的代码。此外,在闪烁后应该重置PATTERN3的值,否则下次进入时会从全亮开始。 2. 在花型4循环中,检测切换花型的条件为CMP AL, 00H(无开关按下),但实际上,当有开关按下时(如SW1暂停或SW2/SW3切换),应该退出花型4循环。但是,代码中检测到非00H就会跳出循环,这可能导致在按下其他开关(比如暂停)时,无法正确暂停。 3. 在花型1和花型2循环中,检测切换花型的条件可能不正确。例如,在花型1循环中,检测SW2(04H)按下则跳转到SYSTEM_LOOP,但实际上,如果SW2按下,应该切换到花型1(当前已经是花型1)?这里逻辑需要调整:应该是检测到其他花型的开关才切换,或者暂停开关才暂停。但代码中,暂停键(SW1)已经单独检测。 4. 初始花型数据在循环中被修改,但再次进入该花型时,初始值已经改变。例如,PATTERN1在循环中被ROL修改,当退出该花型再进入时,初始值不是01H,而是上次退出时的值。这可能导致花型不是从头开始。我们需要在进入花型循环前初始化花型数据?或者每次进入时重置?代码中没有重置。 具体修正步骤: 问题1:花型3的数据重置问题。 在P3_FLASH后,将BL重置为01H,然后存储到[PATTERN3]中,这样下次进入花型3时,会从01H开始。 修改:在P3_FLASH部分,闪烁完成后,将BL重置为01H,并保存到PATTERN3。 问题2:花型4的退出条件。 当前花型4循环中,检测到非00H(有开关按下)就退出循环。但实际上,我们只关心暂停键(SW1,02H)和切换花型的键(例如SW2/SW3等)。所以应该改为:检测到暂停键则跳转到MAIN_LOOP(暂停),检测到其他花型键则跳转到SYSTEM_LOOP进行切换。但是,当前代码中,花型4循环中只检测了暂停键(02H)和是否不为00H(即任意键按下)。这样,当有任意开关按下(包括暂停和其他花型)都会退出花型4循环。然而,代码中退出后是跳转到哪里?它检测到暂停就跳转到MAIN_LOOP(暂停),否则如果检测到不是00H(即非无键按下)就跳转回花型4循环?不,代码中: CMP AL, 02H JE MAIN_LOOP CMP AL, 00H JNE PATTERN4_LOOP ; 如果不是00H(有键按下)且不是暂停键,则继续PATTERN4_LOOP?这里逻辑错误。 实际上,当有键按下但不是暂停键时,我们希望继续执行花型4直到有切换花型的键(比如SW2/SW3)按下?但代码中并没有检测具体的切换键。按照设计,花型4是默认花型,当没有键按下时执行。所以,当有键按下时,我们需要退出花型4循环,回到SYSTEM_LOOP去判断按下的键。 因此,我们应该这样:在花型4循环中,如果检测到暂停键(02H)则跳转到MAIN_LOOP(暂停),否则如果有任意键按下(非00H)就跳转到SYSTEM_LOOP(因为SYSTEM_LOOP会根据按键切换到其他花型或者暂停)。但是注意,花型4循环中,每次交替闪烁两次后进行检测。所以,修改如下: 在花型4循环中,检测到暂停键(02H)则跳转到MAIN_LOOP,否则只要不是00H(有键),就跳转到SYSTEM_LOOP(因为可能是其他花型键)。 问题3:花型1和花型2的切换逻辑。 在花型1循环中,检测到SW2(04H)按下则跳转到SYSTEM_LOOP,但此时SW2按下表示要切换到花型1(当前已经是花型1)?这显然不合理。实际上,我们在主循环中已经根据按键进入对应的花型。在花型执行循环中,我们只应该检测两种事件:暂停(SW1)和切换花型(其他花型键)。所以,在花型1循环中,我们应该检测: - 暂停键(02H):跳转到MAIN_LOOP(暂停) - 其他花型键(如SW3(08H)或SW2+SW3(0CH))则退出当前花型循环,回到SYSTEM_LOOP(由SYSTEM_LOOP重新判断) 但代码中,花型1循环只检测了SW2(04H)作为切换花型的条件。这样,当按下SW3(08H)时,它不会退出花型1循环,而是继续执行。因此,我们需要修改为:检测到除了暂停键以外的其他键(非02H)且是有效的花型切换键(即我们在SYSTEM_LOOP中定义的键)就退出,但为了简单,我们可以检测任何非暂停键的按键都退出到SYSTEM_LOOP,由SYSTEM_LOOP重新判断。 然而,为了避免误操作(比如抖动),我们也可以只检测我们关心的键。但代码中已经将按键状态与特定值比较,所以我们可以这样改: 花型1循环中: ... 执行花型 IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 暂停键 JE MAIN_LOOP ; 如果按键状态不是04H(即SW2按下,但当前已经是花型1,所以不应该再切换到自己?),所以我们需要检测其他花型键。 ; 实际上,我们可以检测是否有其他花型键按下(即SW3(08H)或SW2+SW3(0CH)),但是这样需要多个比较。 ; 或者,我们可以认为只要不是暂停键且按键状态有变化(非0)就退出到SYSTEM_LOOP。因为SYSTEM_LOOP会重新解释按键。 CMP AL, 00H ; 无按键,则继续当前花型 JE PATTERN1_LOOP ; 注意,这里应该是继续循环,而不是跳转到SYSTEM_LOOP ; 有按键(非暂停且非无键)则退出到SYSTEM_LOOP JMP SYSTEM_LOOP 但是,原代码中是这样处理的: CMP AL, 02H ; 暂停 JE MAIN_LOOP CMP AL, 04H ; 检测是否切换花型(这里指SW2,即花型1的触发键,但当前就在花型1,所以按SW2不应该切换?但实际上,用户可能想切换到其他花型而按了其他键,所以这里只检测SW2是不合理的) JNE PATTERN1_LOOP ; 如果不是04H(SW2),则继续花型1循环 JMP SYSTEM_LOOP ; 如果是04H(SW2),则跳转到SYSTEM_LOOP(但按SW2不应该退出花型1,因为当前就是花型1) 所以,原逻辑中,在花型1循环中,只有按下SW2才会退出到SYSTEM_LOOP(这不合理,因为按SW2可能用户想重新开始花型1?),而按下其他键(包括SW3)则不会退出。 修正思路:在花型循环中,我们只检测暂停键(02H)和退出键(即其他花型键)。由于花型键有多个,我们可以这样:除了暂停键外,其他按键都视为切换请求,退出到SYSTEM_LOOP。所以: IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 暂停 JE MAIN_LOOP CMP AL, 00H ; 没有按键,继续当前花型 JE PATTERN1_LOOP ; 有按键(非暂停且非无键)则退出到SYSTEM_LOOP(让主循环去处理) JMP SYSTEM_LOOP 这样,在花型1循环中,如果有按键(除了暂停键)就退出到SYSTEM_LOOP,暂停键则暂停。 问题4:花型数据初始化问题。 当前代码中,花型数据(PATTERN1, PATTERN2等)在数据段定义,但在循环中被修改。当退出一个花型再进入时,数据是上次退出时的状态。为了让每次进入花型都从头开始,我们应该在进入花型循环之前重置花型数据。 我们可以这样做: 在进入具体花型循环前,将对应的花型数据重置为初始值。 例如,进入花型1循环前,重置PATTERN1为01H。 修改: 在每个花型的入口处,重置对应的花型数据为初始值。 但是,这样会有一个问题:如果用户想暂停后继续,那么重置会导致从头开始,而不是接着上次暂停的状态。但原设计并没有要求保存状态,所以重置也可接受。 或者,我们可以不重置,这样暂停后继续会接着上次的状态。但这样,当用户切换花型再切换回来时,也是接着上次的状态。而设计要求可能是每次进入花型都应该是初始状态。 根据问题描述,我们按照每次进入花型都从头开始(重置)来修改。 具体修改: 1. 花型1循环前重置: PATTERN1_LOOP: MOV BYTE PTR [PATTERN1], 01H ; 重置为初始值 ... 原代码 2. 花型2循环前重置: PATTERN2_LOOP: MOV BYTE PTR [PATTERN2], 80H ; 重置为10000000 3. 花型3循环前重置: PATTERN3_LOOP: MOV BYTE PTR [PATTERN3], 01H ; 重置为00000001 4. 花型4不需要重置,因为它的两个值都是固定的。 但是,注意花型3中使用了BL寄存器,在循环中BL是从[PATTERN3]取出的,所以重置PATTERN3后,在循环中应该重新从PATTERN3取一次。不过我们在循环开始前重置了PATTERN3,然后进入P3_INCR前将[PATTERN3]赋值给BL,所以没问题。 另外,花型3的闪烁部分(P3_FLASH)执行完后,将BL重置为01H,并且存储到[PATTERN3]中,这样下次进入花型3时,初始值就是01H。但是,如果我们已经在循环入口重置了PATTERN3,那么P3_FLASH中重置PATTERN3的步骤可以省略?不过,为了在花型3循环内部能够正确循环(即一次递增到全亮,闪烁后重新开始递增),我们仍然需要重置。所以,P3_FLASH中重置BL并保存到[PATTERN3]是必要的。 但是,如果我们在花型3循环入口重置了[PATTERN3]为01H,那么进入循环后,BL被赋值为01H,然后进入递增。当完成一次循环(到全亮闪烁)后,P3_FLASH中又将[PATTERN3]重置为01H(通过MOV [PATTERN3], BL? 注意,在P3_FLASH中,我们是将BL重置为01H,然后并没有保存到[PATTERN3]?代码中是: MOV BL, 01H ; 重置初始值 然后后面没有存储到PATTERN3。 所以,这里需要将重置的值存入PATTERN3。修改: P3_FLASH: ... MOV BL, 01H MOV [PATTERN3], BL ; 保存重置后的值,这样下次循环(如果退出再进入)会从01H开始,但如果我们已经在循环入口重置,这一步也可以不要?为了保持循环内部正确,我们保存。 但是,考虑到循环内部:在P3_FLASH之后,会跳转回PATTERN3_LOOP吗?代码中是: JNE PATTERN3_LOOP ; 如果按键不是暂停且不是0CH,则继续PATTERN3_LOOP(即从PATTERN3_LOOP开始,重新取[PATTERN3]给BL,所以需要[PATTERN3]是01H) 因此,在P3_FLASH中,我们需要更新[PATTERN3]为01H。所以,在P3_FLASH中,执行: MOV [PATTERN3], 01H ; 这样,即使退出循环再进入,也是01H。同时,在循环内部,下一次递增也会从01H开始。 但是,如果我们已经在PATTERN3_LOOP入口重置了[PATTERN3]为01H,那么P3_FLASH中更新[PATTERN3]为01H也是可以的,只是重复了。所以,我们可以选择在循环入口重置一次(保证每次进入花型3都是初始状态),而在循环内部重置(保证每次循环都是初始状态)。 为了清晰,我们可以在循环入口重置一次,然后在循环内部(P3_FLASH)中,重置BL为01H(用于内部循环),但不更新[PATTERN3](因为退出循环后,下次进入会由入口重置)。这样,内部循环重置BL为01H,然后继续递增,而[PATTERN3]在退出循环后才会被重置为01H(下次进入时)。 但是,在P3_FLASH后,我们紧接着会回到PATTERN3_LOOP吗?不,代码是: P3_FLASH: ... 闪烁 MOV BL, 01H ; 重置BL,用于下一次递增 ; 然后下面是检测按键 IN AL, PORT_C AND AL, 0FH CMP AL, 02H JE MAIN_LOOP CMP AL, 0CH JNE PATTERN3_LOOP ; 注意:这里跳转到PATTERN3_LOOP,而不是P3_INCR JMP SYSTEM_LOOP PATTERN3_LOOP: MOV BL, [PATTERN3] ; 这里会重新从[PATTERN3]取值,而[PATTERN3]在入口被重置为01H,但在循环过程中并没有更新(除了在入口重置,循环内部没有更新过[PATTERN3]) 问题:在循环内部(递增过程中),[PATTERN3]的值并没有更新,所以当循环到P3_FLASH后,跳回PATTERN3_LOOP,会重新从[PATTERN3]取初始值(01H),而我们在P3_FLASH中重置了BL=01H,但并没有存入[PATTERN3],所以[PATTERN3]还是01H(因为入口重置了,并且循环中没修改)?所以这里没有问题。但是,在递增过程中,[PATTERN3]的值并没有更新为当前值,所以当切换到其他花型再切换回来时,会从01H开始(因为入口重置了)。 因此,为了在花型3循环内部能够正确循环(即闪烁后重新从01H开始递增),我们在P3_FLASH中重置BL=01H,然后跳回PATTERN3_LOOP时,会重新从[PATTERN3]中取01H(因为[PATTERN3]在入口已经重置为01H,而且在循环内部没有改变),所以BL还是01H。 所以,在花型3循环中,我们不需要在循环内部更新[PATTERN3]。但是,在花型1和花型2中,我们在每次移位后都更新了[PATTERN1]和[PATTERN2],这样当切换到其他花型再切换回来时,会接着上次移位后的值继续。而如果我们在花型1和花型2的入口重置了初始值,那么进入时会重置,就不会接着上次了。 综上所述,我们进行如下修正: 修正1:在每个花型循环入口重置对应的花型数据为初始值。 修正2:修改花型4循环中的按键检测逻辑:当有按键(非00H)且不是暂停键(02H)时,退出到SYSTEM_LOOP(即视为切换花型请求)。注意:暂停键单独处理。 修正3:修改花型1、花型2、花型3循环中的按键检测逻辑:只要检测到按键(非00H),如果不是暂停键,就退出到SYSTEM_LOOP(让主循环重新判断)。如果是暂停键,则跳转到暂停。 修正4:在花型3的P3_FLASH部分,重置BL为01H(用于内部循环),并且不要忘记在PATTERN3_LOOP入口重置[PATTERN3]为01H(这样切换回来时是初始状态)。 另外,花型3循环中,在P3_FLASH后,跳转回PATTERN3_LOOP,而PATTERN3_LOOP会重新从[PATTERN3]取BL,所以我们在入口重置[PATTERN3]为01H,就可以保证每次进入花型3(包括内部循环回到PATTERN3_LOOP)都从01H开始。 但是,注意:在递增过程中(P3_INCR),[PATTERN3]并没有更新,所以当我们在递增过程中切换到其他花型,下次再进入花型3时,由于入口重置了[PATTERN3]为01H,所以会从头开始。这样符合要求。 具体代码修改: 1. 在花型1循环入口重置PATTERN1: PATTERN1_LOOP: MOV BYTE PTR [PATTERN1], 01H ; 重置 ... 原代码 2. 在花型2循环入口重置PATTERN2: PATTERN2_LOOP: MOV BYTE PTR [PATTERN2], 80H ; 重置 3. 在花型3循环入口重置PATTERN3PATTERN3_LOOP: MOV BYTE PTR [PATTERN3], 01H ; 重置 ... 原代码 4. 修改花型1循环中的按键检测部分(删除原有的检测SW2的代码,改为检测任意按键(非暂停)): IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 检测是否暂停 JE MAIN_LOOP CMP AL, 00H ; 无按键,继续 JE PATTERN1_LOOP ; 继续循环 JMP SYSTEM_LOOP ; 有按键(非暂停)则退出到主循环 注意:原代码中在CMP AL,02H之后,有CMP AL,04H,我们去掉这部分,直接用上面的逻辑。 5. 同样修改花型2循环中的按键检测部分: IN AL, PORT_C AND AL, 0FH CMP AL, 02H JE MAIN_LOOP CMP AL, 00H JE PATTERN2_LOOP JMP SYSTEM_LOOP 6. 修改花型3循环中的按键检测部分(有两处:递增部分和闪烁部分): 在递增部分(P3_INCR): IN AL, PORT_C AND AL, 0FH CMP AL, 02H JE MAIN_LOOP CMP AL, 0CH ; 这里保留检测0CH(因为花型3是SW2+SW3触发的,但我们在SYSTEM_LOOP中检测花型3是0CH,所以这里可以保留?但为了统一,我们也可以改为任意按键(除暂停)退出) ; 但是,原设计中,在花型3循环中,如果按下SW2+SW3(0CH)表示继续花型3,所以不应该退出。所以,这里我们只检测暂停键,其他键不做处理?不,我们要求按其他花型键可以切换。 ; 所以,我们可以采用和花型1/2一样的处理:非暂停键且非无键则退出。 ; 修改: CMP AL, 00H JE P3_INCR ; 这里原代码是JNE P3_INCR,逻辑不对,应该改为:无键则继续递增,有键则判断? ; 我们重新组织: ; 检测到暂停键,则跳MAIN_LOOP ; 否则,检测到有键(非00H)则退出到SYSTEM_LOOP(因为可能是其他花型键) ; 所以: ; CMP AL, 02H ; JE MAIN_LOOP ; CMP AL, 00H ; JNE SYSTEM_LOOP ; 有键(非暂停)则退出到系统循环 ; ; 否则无键,继续递增 ; 继续执行(SHL BL,1等) 注意:在P3_INCR中,我们是在移位前检测按键。所以修改如下(替换原IN AL之后的代码): IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 暂停键 JE MAIN_LOOP CMP AL, 00H ; 无键则继续递增 JNE SYSTEM_LOOP ; 有键(非暂停)则退出 ; 继续递增 CMP BL, 0FFH JE P3_FLASH SHL BL, 1 OR BL, 01H JMP P3_INCR ; 继续递增循环 但是,这样修改后,递增循环中,如果有按键(非暂停)就会退出到SYSTEM_LOOP,而不会继续本次递增后的显示。这样虽然可以,但可能会在按键后立即切换花型。 在闪烁部分(P3_FLASH): 同样,在闪烁后检测按键,我们修改为: IN AL, PORT_C AND AL, 0FH CMP AL, 02H JE MAIN_LOOP CMP AL, 00H JNE SYSTEM_LOOP ; 有键(非暂停)则退出 ; 无键,则继续循环(即执行下一次递增) JMP PATTERN3_LOOP ; 这里跳转到PATTERN3_LOOP(会重置[PATTERN3]为01H,然后取BL=01H,重新递增) 注意:在P3_FLASH中,我们已经重置了BL=01H(用于下一次递增),但并没有更新[PATTERN3](因为PATTERN3_LOOP入口会重置[PATTERN3]为01H,所以这里不更新也可以)。 7. 修改花型4循环中的按键检测部分: 原代码: IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 检测是否暂停 JE MAIN_LOOP CMP AL, 00H ; 检测是否切换花型 JNE PATTERN4_LOOP JMP SYSTEM_LOOP 修改为: IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 暂停 JE MAIN_LOOP CMP AL, 00H ; 无键,继续花型4 JE PATTERN4_LOOP ; 继续循环 JMP SYSTEM_LOOP ; 有键(非暂停)则退出 8. 注意:花型3的循环中,在P3_INCR部分,我们修改后,如果检测到按键(非暂停)会跳转到SYSTEM_LOOP,但此时BL的值并没有保存到[PATTERN3]。而下次进入花型3时,会在入口重置[PATTERN3]为01H,所以没有关系。 9. 另外,在花型3的P3_FLASH部分,我们跳转到PATTERN3_LOOP,这样会重新从[PATTERN3]取BL(01H)然后开始递增。这符合要求。 由于修改较多,我们给出修改后的完整代码。 注意:由于汇编代码较长,我们只给出修改后的部分,但为了清晰,尽量给出完整代码。 修改后的完整代码: </think>以下是纠正后的代码,解决了原程序中的逻辑错误、切换机制和花型初始化问题: ```assembly .MODEL SMALL .STACK 100H ; 端口定义 CONTROL_PORT EQU 8006H ; 8255A控制端口 PORT_B EQU 8002H ; PB口(LED输出) PORT_C EQU 8004H ; PC口(开关输入) ; 控制字:方式0,PB口输出,PC口输入 CONTROL_WORD EQU 82H ; 10000010B ; 延时参数 DELAY_COUNT EQU 0F000H ; 值越大速度越慢 .DATA ; 花型初始值 PATTERN1 DB 01H ; 花型1:00000001 PATTERN2 DB 80H ; 花型2:10000000 PATTERN3 DB 01H ; 花型3:初始为00000001 PATTERN4A DB 55H ; 花型4:01010101 PATTERN4B DB 0AAH ; 花型4:10101010 .CODE START: ; 初始化8255A MOV AX, @DATA MOV DS, AX ; 初始化数据段 MOV AL, CONTROL_WORD OUT CONTROL_PORT, AL ; 主循环:检测启动开关 MAIN_LOOP: IN AL, PORT_C AND AL, 0FH CMP AL, 01H ; 检测SW0启动 JE START_SYSTEM JMP MAIN_LOOP ; 启动后功能选择 START_SYSTEM: SYSTEM_LOOP: IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; SW1暂停 JE MAIN_LOOP CMP AL, 04H ; SW2花型1 JE PATTERN1_LOOP CMP AL, 08H ; SW3花型2 JE PATTERN2_LOOP CMP AL, 0CH ; SW2+SW3花型3 JE PATTERN3_LOOP CMP AL, 00H ; 无按键花型4 JE PATTERN4_LOOP JMP SYSTEM_LOOP ; 花型1:左循环 (修复1: 入口初始化) PATTERN1_LOOP: MOV [PATTERN1], 01H ; 每次进入重置初始值 PAT1_LOOP: MOV AL, [PATTERN1] OUT PORT_B, AL CALL DELAY ROL AL, 1 ; 左移位 MOV [PATTERN1], AL IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 暂停检测 JE MAIN_LOOP CMP AL, 00H ; 无按键继续本花型 (修复3) JE PAT1_LOOP JMP SYSTEM_LOOP ; 其他按键返回选择 ; 花型2:右循环 (修复1: 入口初始化) PATTERN2_LOOP: MOV [PATTERN2], 80H PAT2_LOOP: MOV AL, [PATTERN2] OUT PORT_B, AL CALL DELAY ROR AL, 1 ; 右移位 MOV [PATTERN2], AL IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 暂停 JE MAIN_LOOP CMP AL, 00H ; 无按键继续 (修复3) JE PAT2_LOOP JMP SYSTEM_LOOP ; 花型3:递增+闪烁 (修复1,2,4) PATTERN3_LOOP: MOV [PATTERN3], 01H ; 重置初始值 MOV BL, [PATTERN3] P3_INCR: MOV AL, BL OUT PORT_B, AL CALL DELAY CMP BL, 0FFH ; 是否全亮 JE P3_FLASH SHL BL, 1 ; 左移递增 OR BL, 01H ; 保证最低位为1 IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 暂停 JE MAIN_LOOP CMP AL, 00H ; 无按键继续递增 (修复4) JE P3_INCR ; 继续递增 JMP SYSTEM_LOOP ; 其他按键退出 P3_FLASH: MOV AL, 00H ; 全灭闪烁 OUT PORT_B, AL CALL DELAY MOV AL, 0FFH ; 全亮 OUT PORT_B, AL CALL DELAY IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 暂停 JE MAIN_LOOP CMP AL, 00H ; 无按键则重启花型 (修复2) JNE SYSTEM_LOOP ; 有按键退出 JMP PATTERN3_LOOP ; 无按键重启花型 ; 花型4:交替闪烁 (修复5) PATTERN4_LOOP: MOV AL, [PATTERN4A] OUT PORT_B, AL CALL DELAY MOV AL, [PATTERN4B] OUT PORT_B, AL CALL DELAY IN AL, PORT_C AND AL, 0FH CMP AL, 02H ; 暂停 JE MAIN_LOOP CMP AL, 00H ; 无按键继续 (修复3) JE PATTERN4_LOOP JMP SYSTEM_LOOP ; 有按键退出 ; 延时子程序 DELAY: MOV CX, DELAY_COUNT DELAY_LOOP: NOP NOP LOOP DELAY_LOOP RET END START ``` ### 错误修复说明: 1. **花型初始化问题** - 在每次进入花型循环前重置初始值(如`PATTERN1_LOOP`开头添加`MOV [PATTERN1], 01H`) - 确保每次执行花型都从头开始,避免残留上次状态 2. **花型3逻辑修复** - 修复递增逻辑:使用`SHL BL,1` + `OR BL,01H`正确实现1→3→7→15→...→255序列 - 闪烁后增加按键检测:无按键时跳回`PATTERN3_LOOP`重新开始,有按键则退出 3. **花型切换机制** - 所有花型循环内: - 检测到暂停键(02H) → 返回主暂停循环 - 检测到无按键(00H) → 继续当前花型 - 检测到其他按键 → 返回功能选择循环(SYSTEM_LOOP) 4. **数据段初始化** 添加`MOV AX, @DATA`和`MOV DS, AX`,确保数据段正确加载 5. **花型4优化** 移除冗余跳转,直接通过循环条件控制闪烁 ### 工作原理: 1. **8255A初始化** 设置控制字82H:A口未使用,B口输出,C口输入 2. **扫描逻辑** - SW0(01H):启动系统 - SW1(02H):暂停并返回待机 - SW2/SW3:选择对应花型 3. **花型实现** - 花型1:ROL左移实现LED左循环 - 花型2:ROR右移实现LED右循环 - 花型3:SHL+OR实现LED数量递增,全亮后闪烁 - 花型4:55H和AAH交替输出实现间隔闪烁 ### 使用建议: 1. 连接8个LED到PB0-PB7 2. 连接4位开关到PC0-PC3(SW0-SW33. 调整DELAY_COUNT改变花型速度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值