學習設計模式《十二》——命令模式

一、基礎概念

????????命令模式的本質是【封裝請求】命令模式的關鍵是把請求封裝成為命令對象,然后就可以對這個命令對象進行一系列的處理(如:參數化配置、可撤銷操作、宏命令、隊列請求、日志請求等)。

????????命令模式的定義:將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支持可撤銷的操作

????????命令模式的說明:

? ? ? ? ? ?1、在命令模式中,會定義一個命令的接口,用來約束所有的命令對象,然后提供具體的命令實現,每個命令實現對象是對客戶端某個請求的封裝
? ? ? ? ? ?2、在命令模式中,命令對象并不知道如何處理命令,會有相應的接收者對象來真正執行命令

認識命令模式
序號命令模式要素說明
1命令模式的關鍵?命令模式的關鍵之處就是【把請求封裝成為對象】(即:命令對象,并定義了統一的執行操作接口)
2命令模式的組裝和調用

在命令模式中經常會有一個命令的組裝者,用它來維護命令的“虛”實現和真實實現之間的關系:如果是超級智能的命令【即:命令對象自己完全實現好了,不需要接收者】那就是命令模式的退回,不需要接收者,自然也就不需要組裝者了)真正的用戶就是具體化請求的內容,然后提交請求進行觸發就可以了(真正的用戶會通過Invoker來觸發命令)【在實際開發過程中,Client和Invoker可以融合在一起,由客戶在使用命令模式的時候,先進行命令對象和接收者的組裝,組裝完成后,就可以調用命令執行請求了】

3命令模式的接收者接收者可以是任意的類,對它沒有什么特殊要求,這個對象知道如何真正執行命令的操作,執行時是從Command的實現類里面轉調過來。?一個接收者對象可以處理多個命令,接收者和命令之間沒有約定的對應關系(接收者提供的方法個數、名稱、功能和命令中的可以不一樣,只要能夠通過調用接受者的方法來實現命令的功能就可以了)
4智能命令在標準的命令模式里面,命令的實現類是沒有真正實現命令要求的功能的【真正執行命令的功能是接收者】;如果命令的實現對象比較智能,它自己就能真正地實現命令要求的功能,不再需要調用接收者,這種情況就稱為智能命令;也可以有半智能的命令,命令對象知道部分實現,其他的還是需要調用接收者來完成
5發起請求的對象和真正實現的對象是解耦的請求究竟是誰處理?如何處理?發起請求的對象是不知道的【即:發起請求的對象和真正?實現的對象是解耦的】發起請求的對象只管發出命令,其他的就不管了?
6命令模式的調用過程

分為兩個階段:

一階段是組裝命令對象和接收者對象;

二階段則是觸發調用Invoker,來讓命令真正執行;

命令模式的優點
序號命令模式的優點說明
1

更松散的耦合

命令模式使得發起命令的對象(客戶端)和具體實現命令的對象(接收者)完全解耦【即:?發起命令的對象完全不知道具體實現對象是誰,也不知道如何實現】

2

更動態的控制

命令模式把請求封裝起來,可以動態地對它進行參數化、隊列化和日志化等操作,使得系統更加靈活

3

很自然的復合命令

命令模式中的命令對象能夠很容易地組合為復合命令(如宏命令)從而使得系統操作簡單,功能更強大

4

更好的擴展性

由于發起命令的對象和具體的實現完全解耦,因此擴展新命令就很容易,只需要實現新的命令對象,然后在裝配的時候,把具體的實現對象設置到命對象中,然后可以使用這個命令對象,已有的實現完全不用變化?

何時選用命令模式:

? ? ? ? ? ? 1、需要抽象出需要執行的動作,并參數化這些對象,可選用命令模式;
? ? ? ? ? ? 2、需要再不同的時刻指定、排列和執行請求,可選用命令模式;
? ? ? ? ? ? 3、需要支持取消操作,可選用命令模式;
? ? ? ? ? ? 4、需支持當系統崩潰時,能將系統的操作功能重新執行一遍,可選用命令模式;
? ? ? ? ? ? 5、需要事物的系統中(如數據庫事務),可選用命令模式。

二、命令模式示例

?2.1、 命令模式之電腦如何開機

????????背景:對于使用電腦的客戶來說,開機確實很簡單,只要按下啟動按鈕等待就可以了但是,當我們按下啟動按鈕以后,誰來處理?如何處理?都經歷了怎樣的過程?才能讓電腦真正的啟動起來,供我們使用】對于電腦的啟動過程我們先有一個簡單的了解:

1、按下啟動按鈕,電源開始向主板和其他設備供電;

2、主板的基本輸入輸出系統[BIOS]開始加電后自檢;

3、主板的BIOS會依次尋找顯卡等其他設備的BIOS,并讓他們自檢或初始化;

4、開始檢測CPU、內存、硬盤、GPU、即插即用設備等;

5、BIOS更新ESCD【擴展系統配置數據】(即:ESCD是BIOS和操作系統交換硬件配置數據的一種手段);

6、以上內容完成后,BIOS按照用戶的配置進行系統引導,進入操作系統里面,等待操作系統裝載并初始化完畢,就會顯示我們熟悉的系統登錄界面。

總結起來就是:加載電源-->設備自檢-->裝載系統;但是這些詳細的工作步驟是誰來完成的?如何完成?【其實真正完成這些工作的是主板;那使用電腦的用戶和主板是如何關聯的呢?在現實生活中,我們是把開關機按鈕的線連接到主板上,這樣當用戶按下按鈕的時候,就相當于給主板發送命令,讓主板去完成電腦啟動前的一系列工作】從使用電腦的角度來看,開機就是按下按鈕,我才不需要管使用什么樣的主板,如何接收命令,接收誰的命令,接下來怎么處理等細節【總結起來用戶就是只用發出命令,不關心請求的真正接收者是誰,也不關心如何實現】。

? 2.1.1、定義主板接口

因為主板是真正接收和實現用戶命令請求的因此先來定義主板接口,約束命令對象:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{/// <summary>/// 主板接口/// </summary>internal interface IMainBoard{//具有開機功能void Open();}//Interface_end
}

? 2.1.2、創建具體的主板對象繼承主板接口實現具體的功能

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{/// <summary>/// 技嘉主板/// </summary>internal class GigaMainBoard : IMainBoard{/// <summary>/// 真正的開機命令實現/// </summary>public void Open(){Console.WriteLine("【技嘉】主板正在開機,請稍等");Console.WriteLine("接通電源。。。。。。");Console.WriteLine("設備檢查。。。。。。");Console.WriteLine("裝載系統。。。。。。");Console.WriteLine("設備正常啟動。。。。");Console.WriteLine("系統已經啟動完成,請登錄");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{internal class MsiMainBoard{/// <summary>/// 真正的開機命令實現/// </summary>public void Open(){Console.WriteLine("【微星】主板正在開機,請稍等");Console.WriteLine("接通電源。。。。。。");Console.WriteLine("設備檢查。。。。。。");Console.WriteLine("裝載系統。。。。。。");Console.WriteLine("設備正常啟動。。。。");Console.WriteLine("系統已經啟動完成,請登錄");}}//Class_end
}

? 2.1.3、定義命令接口

????????因為對于用戶來說,電腦開機我就只用按下開機按鈕就可以了【這個動作抽象出來就是用戶發出開機的命令,其他的就不用管了】為了擴展多種命令,我們的接口就只定義一個方法就是執行。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{/// <summary>/// 命令接口/// </summary>internal interface ICommand{//執行命令的操作void Execute();}//Interface_end
}

? ?2.1.4、創建具體的命令繼承命令接口并實現

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{/// <summary>///開機命令的實現類///擁有真正開機命令的實現(通過調用接受者的方法來實現命令)/// </summary>internal class OpenCommand : ICommand{//持有真正實現命令的接受者——主板對象private IMainBoard mainBoard = null;/// <summary>/// 構造函數/// </summary>/// <param name="mainBoard">主板對象</param>public OpenCommand(IMainBoard mainBoard){this.mainBoard = mainBoard;}public void Execute(){//對于命令對象,根本不知道如何開機,會調用主板對象,讓主板對象完成開機功能this.mainBoard.Open();}}//Class_end
}

? ?2.1.5、創建機箱對象用于傳遞用戶下達的命令給主板操作

????????由于用戶根本不知道主板是什么,且不想直接直接通過操作主板才實現電腦的開機,只希望簡單的按下按鈕就可以啟動,所以我們在用戶與主板之間創建一個中間對象【機箱】來同時對接用戶和主板【由于用戶給機箱下達命令是通過按按鈕的方式,則需要給機箱定義對應的按鈕功能】

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{/// <summary>/// 機箱對象,本身有按鈕,持有按鈕對應的命令對象/// </summary>internal class Box{//開機命令對象private ICommand openCommand = null;/// <summary>/// 設置開機命令對象/// </summary>/// <param name="command">命令對象</param>public void SetOpenCommand(ICommand openCommand){this.openCommand = openCommand;}/// <summary>/// 提供給客戶使用,接收并響應用戶請求,相當于開機按鈕被按下的方法/// </summary>public void OpenButtonPressed(){//按下按鈕,執行命令this.openCommand.Execute();}}//Class_end
}

? ?2.1.6、客戶使用按鈕啟動系統

現在我們都具備了實現功能的主板、命令、及其作為用戶下達命令與主板關聯的機箱;但目前還都是零散的各個配件,無法使用;我們需要將這些配件組成一個整體提供給用戶使用【現實生活中國,這個組裝的工作是由裝機工程師完成的,我們這里為了簡單,就直接在客戶端那里封裝一個方法使用】。

namespace CommandPattern
{internal class Program{static void Main(string[] args){OpenButtonPressedTest();Console.ReadLine();}/// <summary>/// 測試客戶按下機箱的開機按鈕啟動系統/// </summary>private static void OpenButtonPressedTest(){Console.WriteLine("------客戶按下機箱的開機按鈕啟動系統------");/*1-把命令和真正的實現組合起來,相當于組裝機器*/IMainBoard mainBoard = new GigaMainBoard();OpenCommand openCommand = new OpenCommand(mainBoard);/*2-為機箱上的開機按鈕設置對應的命令,讓按鈕知道該做什么*/Box box = new Box();box.SetOpenCommand(openCommand);/*3-模擬用戶按下機箱上的按鈕*/box.OpenButtonPressed();}}//Class_end
}

? ?2.1.7、運行結果

?2.2、命令模式的參數化配置

命令模式的參數化配置是指【可以使用不同的命令對象,去參數化配置客戶的請求】;

????????在電腦如何開機的問題中,客戶按下一個按鈕,到底是開機還是重啟,那就需要看參數化配置的是哪一個具體的按鈕對象(若參數化的是開機命令對象,那就執行開機功能;若參數化的是充氣命令對象,那執行的就是重啟功能)。

? ?2.2.1、定義主板接口

????????新增一個重啟按鈕的方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{/// <summary>/// 主板接口/// </summary>internal interface IMainBoard{//具有開機功能void Open();//2-主板具有重啟功能void Reboot();}//Interface_end
}

? ? ?2.2.2、創建具體的主板對象繼承主板接口實現具體的功能

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{/// <summary>/// 技嘉主板/// </summary>internal class GigaMainBoard : IMainBoard{/// <summary>/// 真正的開機命令實現/// </summary>public void Open(){Console.WriteLine("【技嘉】主板正在開機,請稍等");Console.WriteLine("接通電源。。。。。。");Console.WriteLine("設備檢查。。。。。。");Console.WriteLine("裝載系統。。。。。。");Console.WriteLine("設備正常啟動。。。。");Console.WriteLine("系統已經啟動完成,請登錄");}/// <summary>/// 真正的重啟命令實現/// </summary>public void Reboot(){Console.WriteLine("技嘉主板現在正在重啟機器,請等候...");Console.WriteLine("機器已經正常啟動,請登錄。。。");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{internal class MsiMainBoard{/// <summary>/// 真正的開機命令實現/// </summary>public void Open(){Console.WriteLine("【微星】主板正在開機,請稍等");Console.WriteLine("接通電源。。。。。。");Console.WriteLine("設備檢查。。。。。。");Console.WriteLine("裝載系統。。。。。。");Console.WriteLine("設備正常啟動。。。。");Console.WriteLine("系統已經啟動完成,請登錄");}/// <summary>/// 真正的重啟命令實現/// </summary>public void Reboot(){Console.WriteLine("微星主板現在正在重啟機器,請等候...");Console.WriteLine("機器已經正常啟動,請登錄。。。");}}//Class_end
}

? ? ?2.2.3、定義命令接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{/// <summary>/// 命令接口/// </summary>internal interface ICommand{//執行命令的操作void Execute();}//Interface_end
}

? ? ? 2.2.4、創建具體的命令繼承命令接口并實現

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{/// <summary>///開機命令的實現類///擁有真正開機命令的實現(通過調用接受者的方法來實現命令)/// </summary>internal class OpenCommand : ICommand{//持有真正實現命令的接受者——主板對象private IMainBoard mainBoard = null;/// <summary>/// 構造函數/// </summary>/// <param name="mainBoard">主板對象</param>public OpenCommand(IMainBoard mainBoard){this.mainBoard = mainBoard;}public void Execute(){//對于命令對象,根本不知道如何開機,會調用主板對象,讓主板對象完成開機功能this.mainBoard.Open();}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{/// <summary>/// 重啟機器命令,繼承ICommand接口/// 持有重啟機器命令的真正實現,通過調用接收者方法來實現命令/// </summary>internal class RebootCommand : ICommand{//持有真正實現命令的接收者——主板對象private IMainBoard mainBoard = null;/// <summary>/// 構造方法/// </summary>/// <param name="mainBoard">主板對象</param>public RebootCommand(IMainBoard mainBoard){this.mainBoard = mainBoard;     }public void Execute(){//對于命令對象,根本不知道如何重啟機器,會調用主板對象讓主板去完成重啟機器的功能this.mainBoard.Reboot();}}//Class_end
}

? ? ? 2.2.5、創建機箱對象用于傳遞用戶下達的命令給主板操作

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern
{/// <summary>/// 機箱對象,本身有按鈕,持有按鈕對應的命令對象/// </summary>internal class Box{//開機命令對象private ICommand openCommand = null;/// <summary>/// 設置開機命令對象/// </summary>/// <param name="command">命令對象</param>public void SetOpenCommand(ICommand openCommand){this.openCommand = openCommand;}/// <summary>/// 提供給客戶使用,接收并響應用戶請求,相當于開機按鈕被按下的方法/// </summary>public void OpenButtonPressed(){//按下按鈕,執行命令this.openCommand.Execute();}//重啟機器命令對象private ICommand rebootCommand = null;//設置重啟命令對象public void SetRebootCommand(ICommand rebootCommand){this.rebootCommand = rebootCommand;}/// <summary>/// 提供給客戶使用,接收并響應用戶請求,相當于重啟按鈕被按下的方法/// </summary>public void RebootButtonPressed(){//按下按鈕,執行命令this.rebootCommand.Execute();}}//Class_end
}

? ? ? 2.2.6、客戶使用按鈕啟動系統


using CommandPattern.Macros;namespace CommandPattern
{internal class Program{static void Main(string[] args){ParameterButtonPressedTest();Console.ReadLine();}/// <summary>/// 測試客戶按下機箱的參數化配置按鈕啟動系統/// </summary>private static void ParameterButtonPressedTest(){Console.WriteLine("------測試客戶按下機箱的參數化配置按鈕啟動系統------");/*1-把命令和真正的實現組合起來,相當于組裝機器*/IMainBoard mainBoard = new GigaMainBoard();OpenCommand openCommand = new OpenCommand(mainBoard);RebootCommand rebootCommand = new RebootCommand(mainBoard);/*2-為機箱上的開機按鈕設置對應的命令,讓按鈕知道該做什么*/Box box = new Box();//這里先正確配置開機按鈕對應開機命令,重啟按鈕對應重啟命令box.SetOpenCommand(openCommand);box.SetRebootCommand(rebootCommand);/*3-模擬用戶按下機箱上的按鈕*/Console.WriteLine("------正確按鈕命令配置------");Console.WriteLine(">>>按下開機按鈕>>>");box.OpenButtonPressed();Console.WriteLine(">>>按下重啟按鈕>>>");box.RebootButtonPressed();Console.WriteLine("\n");//這里錯誤配置參數命令box.SetOpenCommand(rebootCommand);box.SetRebootCommand(openCommand);Console.WriteLine("------錯誤按鈕命令配置------");Console.WriteLine(">>>按下開機按鈕>>>");box.OpenButtonPressed();Console.WriteLine(">>>按下重啟按鈕>>>");box.RebootButtonPressed();}}//Class_end
}

? ?2.2.7、運行結果

?2.3、命令模式之可撤銷操作

可撤銷的含義就是:放棄當前的操作,回到未執行該操作的狀態;實現撤銷的操作有兩種思路:

????????思路一:補償式操作(也稱反操作式)【即:如被撤銷的操作是加的功能,那撤銷的實現就變為了減的功能;同理若被撤銷的是打開功能,那撤銷的實現就是關閉功能】;

????????思路二:存儲是恢復【即:把操作前的狀態記錄下來,然后要撤銷操作的時候直接恢復回去就可以了】。

比如:我們現在需要實現一個最簡單的加減法運算,且需要實現可撤銷操作。

? ?2.3.1、定義加減法運算的接口

該接口定義了可以執行加法、減法操作、設置計算初始值、獲取計算結果方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.UndoOPC
{/// <summary>/// 操作運算的接口/// </summary>internal interface IOperation{//獲取計算完成后的結果int GetResult();//設置計算開始的初始值void SetResult(int result);//執行加法void Addition(int num);//執行減法void Subtraction(int num);}//Interface_end
}

? ?2.3.2、定義對象繼承接口實現具體的加法減法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.UndoOPC
{/// <summary>/// 運算類,真正實現加減法運算/// </summary>internal class Opreation : IOperation{//記錄運算結果private int result = 0;public void Addition(int num){//實現加法功能result += num;}public int GetResult(){return result;}public void SetResult(int result){this.result = result;}public void Subtraction(int num){//實現減法功能result -= num;}}//Class_end
}

? ?2.3.3、定義命令接口

? 正常來說命令接口只需要定義執行方法就可以,但這里還需要撤銷功能,則還需要定義撤銷方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.UndoOPC
{/// <summary>/// 命令接口/// </summary>internal interface ICommand{//執行命令對應的操作void Execute();//執行撤銷命令void Undo();}//Interface_end
}

? ?2.3.4、具體的加法、減法命令實現

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.UndoOPC
{/// <summary>/// 具體的加法命令實現對象/// </summary>internal class AddCommand : ICommand{//持有具體執行計算的對象private IOperation operation = null;//需操作的數據private int num = 0;/// <summary>/// 構造函數/// </summary>/// <param name="operation">操作對象</param>/// <param name="num">需操作的數據</param>public AddCommand(IOperation operation, int num){this.operation = operation;this.num = num;}public void Execute(){//轉調接收者去真正執行功能,此處的命令是做加法this.operation.Addition(num);}public void Undo(){//轉調接收者去真正執行功能,命令本身是做加法,那么撤銷的時候就是做減法了this.operation.Subtraction(num);}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.UndoOPC
{/// <summary>/// 具體的減法命令實現對象/// </summary>internal class SubCommand : ICommand{//持有真正執行計算的對象private IOperation operation = null;//操作的數據private int num = 0;/// <summary>/// 構造函數/// </summary>/// <param name="operation">操作對象</param>/// <param name="num">需操作的數據</param>public SubCommand(IOperation operation,int num){this.operation = operation;this.num = num;}public void Execute(){//轉調接收者去真正執行功能,此處的命令是做減法this.operation.Subtraction(num);}public void Undo(){//轉調接收者去真正執行功能,命令本身是做減法,那么撤銷的時候就是做加法了this.operation.Addition(num);}}//Class_end
}

? ?2.3.5、定義計算器

????????計算器相當于Invoker,持有多個命令對象,計算器是可以實現可撤銷操作的地方(要想實現操作的可撤銷操作,就需要把操作過的命令都記錄下來,形成命令的歷史列表,撤銷的時候從最后一個開始執行撤銷)。

什么時候向命令歷史列表添加值呢?(在每次操作加法、減法按鈕按下的時候添加)。

什么時候移除命令列表里的值呢?(在撤銷的時候移除命令歷史列表的值,同時給恢復歷史命令表里增加值);

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.UndoOPC
{internal class Calculator{//持有執行加法的命令對象private ICommand addCommand = null;//持有執行減法的命令對象private ICommand SubCommand = null;//命令的操作歷史記錄,在撤銷的時候使用private List<ICommand> undoCommands = new List<ICommand>();//命令的操作記錄,在恢復時使用private List<ICommand> redoCommands = new List<ICommand>();//設置執行加法的命令對象public void SetAddCommand(ICommand addCommand){this.addCommand = addCommand;}/// <summary>/// 提供給客戶使用,執行加法功能/// </summary>public void AddPressed(){this.addCommand.Execute();//把操作記錄到歷史記錄里面undoCommands.Add(this.addCommand);}//設置減法命令對象public void SetSubCommand(ICommand subCommand){this.SubCommand = subCommand;}/// <summary>/// 提供給客戶使用,執行減法功能/// </summary>public void SubPressed(){this.SubCommand.Execute();//把操作記錄到歷史記錄里面undoCommands.Add(this.SubCommand);}/// <summary>/// 提供給客戶使用,執行撤銷功能/// </summary>public void UndoPressed(){if (this.undoCommands.Count > 0){//1-取出撤銷記錄表里的最后一個命令來撤銷ICommand command = this.undoCommands[this.undoCommands.Count - 1];command.Undo();//2-如果還有恢復的功能,那就把這個命令記錄到恢復的歷史記錄里面this.redoCommands.Add(command);//3-然后把撤銷記錄表里的最后一個命令刪除this.undoCommands.Remove(command);}else{Console.WriteLine("很抱歉,沒有可撤銷的命令了!");}}/// <summary>/// 提供給客戶使用,執行恢復功能/// </summary>public void RedoPressed(){if (this.redoCommands.Count>0){//1-取出恢復命令記錄表的最后一條記錄來恢復ICommand command = this.redoCommands[this.redoCommands.Count-1];command.Execute();//2-把這個命令記錄到可撤銷的歷史記錄里面this.undoCommands.Add(command);//3-把恢復命令記錄表里面的最后一個命令刪除掉this.redoCommands.Remove(command);}}}//Class_end
}

? ?2.3.6、組裝命令和接收者并測試

namespace CommandPattern
{internal class Program{static void Main(string[] args){UndoAndRedoOfAddSubTest();Console.ReadLine();}/// <summary>/// 測試加減法的撤銷和恢復功能/// </summary>private static void UndoAndRedoOfAddSubTest(){Console.WriteLine("------測試加減法的撤銷和恢復功能------");/*1-組裝命令和接收者*///創建接收者UndoOPC.IOperation operation = new UndoOPC.Opreation();//創建命令對象,并組裝命令和接收者UndoOPC.AddCommand addCommand = new UndoOPC.AddCommand(operation,6);UndoOPC.SubCommand subCommand = new UndoOPC.SubCommand(operation, 2);/*2-把命令設置到持有者【計算器里面】*/UndoOPC.Calculator calculator = new UndoOPC.Calculator();calculator.SetAddCommand(addCommand);calculator.SetSubCommand(subCommand);/*3-模擬按下按鈕*/calculator.AddPressed();Console.WriteLine($"一次加法運算后的結果是【{operation.GetResult()}】");calculator.SubPressed();Console.WriteLine($"一次減法運算后的結果是【{operation.GetResult()}】");/*4-測試撤銷*/calculator.UndoPressed();Console.WriteLine($"撤銷一次后的結果是【{operation.GetResult()}】");calculator.UndoPressed();Console.WriteLine($"再撤銷一次后的結果是【{operation.GetResult()}】");/*4-測試恢復*/calculator.RedoPressed();Console.WriteLine($"恢復一次后的結果是【{operation.GetResult()}】");calculator.RedoPressed();Console.WriteLine($"再恢復一次后的結果是【{operation.GetResult()}】");}}//Class_end
}

? ?2.3.7、運行結果

? 2.4、命令模式之宏命令

什么是宏命令?(簡單的說就是包含多個命令的命令,是一個命令的組合)

需求場景:回憶一下你去飯店吃飯的過程:

1、你進入一家飯店,找到座位坐下;

2、服務員走過來,遞給你菜譜;

3、你開始點菜,服務員記錄菜單(菜單是三聯的,你菜點完后,服務員就會把菜單分為三份,一份給后廚、一份給收銀臺、一份保留備查);

4、點完才厚,你只用在座位上等候,后廚會按照菜單做菜;

5、每做好一份菜,就會由服務員送到你的桌上;

6、然后你就可以大快朵頤了。

通過以上的步驟可以清楚的看到,到飯店點餐是一個典型的命令模式應用,作為客戶的你,只需要發出命令(你需要吃什么菜,每道菜相當于一個命令對象)服務員會記錄你點的每道菜,然后把菜傳遞給后廚,后廚拿到菜單,會按照菜單進行飯菜的制作,后廚就相當于接收者是真正命令執行者,廚師菜知道每道菜的具體實現;而服務員就比較特殊,在不考慮更復雜的管理(如:后廚管理時,負責命令和接收者的組裝就是服務員【比如:你點了涼菜、熱菜,你其實不知道這些菜到底是誰來完成的,你只管發出命令,但是具體涼菜到哪里做?由誰做?是由服務員將菜單根據菜品不同分別將涼菜菜單送到涼菜部、熱菜送到熱菜部,然后再由對應的涼菜、熱菜廚師現做的】服務員就是一個組裝者)。

????????在前面的實現的命令模式中都是客戶發出一個命令,然后就立馬執行了;但是我們在飯店點餐,并不是你點一個菜廚師就開始做;而是服務員會等你點完菜,當你說“點完了”的時候,服務員才會啟動命令執行(此時,執行命令的時候就不止一個命令了,而是一堆命令,這堆命令就是你點的多個菜)這個點好的各個菜品就是宏命令

? ?2.4.1、定義廚師接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 廚師接口/// </summary>internal interface ICooker{//示意做菜的方法void Cook(string name);}//Interface_end
}

? ?2.4.2、定義具體類型的廚師繼承廚師接口實現具體的烹飪方法

廚師分為兩類:一類是做熱菜的師傅,另一類是做涼菜的師傅:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 做熱菜的廚師對象/// </summary>internal class HotCooker : ICooker{public void Cook(string name){Console.WriteLine($"熱菜廚師正在做【{name}】");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 做涼菜的廚師/// </summary>internal class CoolCooker : ICooker{public void Cook(string name){Console.WriteLine($"涼菜師傅在做【{name}】");}}//Class_end
}

? ?2.4.3、定義命令接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 命令接口/// </summary>internal interface ICommand{//執行命令對應的操作void Execute();}//Interface_end
}

? ?2.4.4、定義具體的菜品命令繼承接口實現具體的菜

? ? ? ? 注意:如下的具體菜品命令對象與標準的命令模式實現有一點區別,即標準命令模式實現的時候是通過構造方法傳入接收者對象;我們這邊菜品實現的時候改為了通過Set方法的方法來設置接收者對象,這樣可以動態的切換接收者對象,而不用重構對象。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 蘿卜燉排骨命令/// </summary>internal class CarrotRribsCommand : ICommand{//持有具體操作的廚師對象private ICooker cooker = null;/// <summary>/// 設置具體做菜的廚師對象/// </summary>/// <param name="cook">做菜的廚師</param>public void SetCooker(ICooker cooker){this.cooker = cooker;}public void Execute(){this.cooker.Cook("蘿卜燉排骨");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 北京烤鴨對象/// </summary>internal class PekingDuckCommand : ICommand{//持有真正做菜的廚師對象private ICooker cooker = null;/// <summary>/// 設置具體做菜的廚師/// </summary>/// <param name="cooker">做菜的廚師</param>public void SetCooker(ICooker cooker){this.cooker = cooker;   }public void Execute(){this.cooker.Cook("北京烤鴨");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 蒜泥白肉對象/// </summary>internal class GarlicMeatCommand : ICommand{//持有真正做菜的廚師對象private ICooker cooker = null;/// <summary>/// 設置做菜的廚師對象/// </summary>/// <param name="cooker">做菜的廚師</param>public void SetCooker(ICooker cooker){this.cooker = cooker;}public void Execute(){this.cooker.Cook("蒜泥白肉");}}//Class_end
}

? ?2.4.5、定義菜單對象

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 菜單對象【是宏命令對象(包含多個命令)】/// </summary>internal class MenuCommand : ICommand{//用來記錄組合本菜單的多道菜品(即多個命令對象)private List<ICommand> commands = new List<ICommand>();/// <summary>/// 點菜,把菜品加入到菜單中/// </summary>/// <param name="dishCommand">菜品</param>public void AddCommand(ICommand command){commands.Add(command);}public void Execute(){//執行菜單其實就是循環執行菜單里面的每個菜foreach (var command in commands){command.Execute();}}}//Class_end
}

? ?2.4.6、定義服務員對象

????????這里的服務員對象相當于標準命令模式中的Client【創建具體的命令對象,并設置命令對象的接收者】加上Invoker【要求命令對象執行請求、通常會持有命令對象,可以持有多個命令對象;這是客戶端真正觸發命令并要求命令執行對應操作的地方,相當于使用命令對象的入口】。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 服務員對象/// </summary>internal class Waiter{//持有一個宏命令對象【菜單】private MenuCommand menuCommand = new MenuCommand();/// <summary>/// 客戶點菜/// </summary>/// <param name="command">菜品</param>public void OrderDish(ICommand command){//客戶傳過來的命令對象是沒有接收者組裝的【需要服務員自己組裝】ICooker hotCooker = new HotCooker();ICooker coolCooker=new CoolCooker();//判斷到底是組合涼菜師傅還是熱菜師傅if (command is CarrotRribsCommand){((CarrotRribsCommand)command).SetCooker(hotCooker);}if (command is PekingDuckCommand){((PekingDuckCommand)command).SetCooker(hotCooker);}if (command is GarlicMeatCommand){((GarlicMeatCommand)command).SetCooker(coolCooker);}//添加菜單中menuCommand.AddCommand(command);}/// <summary>/// 客戶點菜完畢,表示需要執行命令了【即廚師真正開始做菜了】/// </summary>public void OrderOver(){this.menuCommand.Execute();}}//Class_end
}

? ?2.4.7、客戶點菜測試

namespace CommandPattern
{internal class Program{static void Main(string[] args){CustomerOrderDishTest();Console.ReadLine();}/// <summary>/// 測試客戶點菜/// </summary>private static void CustomerOrderDishTest(){//1-創建服務員Macros.Waiter waiter=new Macros.Waiter();//2-創命令對象【即需要點的菜品】Macros.ICommand carrotRribs = new Macros.CarrotRribsCommand();Macros.ICommand pekingDuck = new Macros.PekingDuckCommand();Macros.ICommand GarlicMeat = new Macros.GarlicMeatCommand();//3-點菜waiter.OrderDish(carrotRribs);waiter.OrderDish(pekingDuck);waiter.OrderDish(GarlicMeat);//4-點菜完畢waiter.OrderOver();}}//Class_end
}

? ?2.4.8、運行結果

? 2.5、命令模式之隊列請求

????????所謂的隊列請求,就是命令對象進行排隊,組成工作隊列,然后依次取出命令對象來執行。

繼續我們在飯店點餐的例子,其實子后廚,會收到很多菜單,一般是按照菜單傳遞到后廚的先后順序來進行處理,對每張菜單,假定也是按照菜品的先后順序進行制作,那么在后廚就形成了一個菜品隊列(即:很多個用戶點的菜品命令隊列);后廚有很多廚師,每個廚師都從這個命令隊列里面取出一個命令,然后按照命令做出菜來,就相當于多個線程在同時處理一個隊列請求【后廚就是一個典型的隊列請求例子】(廚師只是負責從隊列里面取出一個菜品處理,然后在取下一個在處理,僅此而已,廚師并不關心顧客是誰)。

? ?2.5.1、定義命令接口和實現具體的菜品命令

????????命令接口規范了命令的行為:除了執行命令外,還需要為命令對象設置接收者方法,還增加一個返回發出命令的桌號(我們這里為了簡單就只做熱菜)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 命令接口/// </summary>internal interface ICommand{//執行命令對應的操作void Execute();//設置命令的接收者void SetCooker(ICooker cooker);//返回發起請求的桌號【即;點菜的桌號】int GetTableNumber();}//Interface_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 蘿卜燉排骨命令/// </summary>internal class CarrotRribsCommand : ICommand{//持有具體操作的廚師對象private ICooker cooker = null;/// <summary>/// 設置具體做菜的廚師對象/// </summary>/// <param name="cook">做菜的廚師</param>public void SetCooker(ICooker cooker){this.cooker = cooker;}//點菜的桌號private int tableNum = 0;/// <summary>/// 構造函數/// </summary>/// <param name="tableNum">點菜的桌號</param>public CarrotRribsCommand(int tableNum){this.tableNum = tableNum;   }/// <summary>/// 獲取點菜的桌號/// </summary>/// <returns></returns>public int GetTableNumber(){return this.tableNum;}public void Execute(){this.cooker.Cook("蘿卜燉排骨",tableNum);}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 北京烤鴨對象/// </summary>internal class PekingDuckCommand : ICommand{//持有真正做菜的廚師對象private ICooker cooker = null;/// <summary>/// 設置具體做菜的廚師/// </summary>/// <param name="cooker">做菜的廚師</param>public void SetCooker(ICooker cooker){this.cooker = cooker;   }public void Execute(){this.cooker.Cook("北京烤鴨",tableNum);}//點菜的桌號private int tableNum = 0;/// <summary>/// 構造函數/// </summary>/// <param name="tableNum">點菜的桌號</param>public PekingDuckCommand(int tableNum){this.tableNum = tableNum;}/// <summary>/// 獲取點菜的桌號/// </summary>/// <returns></returns>public int GetTableNumber(){return this.tableNum;}}//Class_end
}

? ?2.5.2、廚師接口

????????廚師接口的烹飪菜品方法需要添加發出命令的桌號,這樣在多線程出書信息的時候,才知道到底是在給哪個桌子做菜:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 廚師接口/// </summary>internal interface ICooker{/// <summary>//示意做菜的方法/// </summary>/// <param name="name">菜品名稱</param>/// <param name="tableNum">點菜的桌號</param>void Cook(string name,int tableNum);}//Interface_end
}

? ?2.5.3、構建命令對象隊列

????????我們這的命令對象隊列不使用Queue,直接使用List來模擬隊列實現:

        private static void ListQueue(){List<string> strings=new List<string>();for (int i = 0; i < 5; i++){strings.Add(i.ToString());Console.WriteLine(i);}Console.WriteLine();int count = strings.Count;for (int i = 0; i < count; i++){string str = strings.First();Console.WriteLine($"當前讀取的值是【{str}】");string str2 = strings[0];Console.WriteLine($"當前需要移除的值是【{str2}】");strings.RemoveAt(0);}}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 命令隊列類/// </summary>internal class CommondQueue{//用來存儲命令對象的隊列private static List<ICommand> commands = new List<ICommand>();/// <summary>/// 服務員傳過來一個新的菜單,需要同步(因為同時會有很多的服務員傳入菜單,而同時又有很多廚師從隊列里取菜單)/// </summary>/// <param name="menuCommand">菜單命令</param>public static void AddMenu(MenuCommand menuCommand){//一個菜單對象包含很多命令對象foreach (var command in menuCommand.GetCommandList()){lock (command){commands.Add(command);}}}//廚師從命令隊列里面獲取命令對象進行處理,也需要同步public static ICommand GetOneCommand(){ICommand command = null;if (commands.Count>0){lock (commands.First()){//模擬隊列的先進先出command = commands.First();//同時從隊列里面去掉這個命令對象commands.RemoveAt(0);}}return command;}}//Class_end
}

? ?2.5.4、菜單向命令隊列傳遞菜品命令,服務員點菜形成菜單

????????我們有了命令隊列,此時就是需要服務員記錄菜品為菜單,等待顧客點完菜品;現在執行菜單就相當于把菜品之間傳遞給后廚(也就是要把菜單里面的所有命令對象加入到命令隊列里面)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 菜單對象【是宏命令對象(包含多個命令)】/// </summary>internal class MenuCommand : ICommand{//用來記錄組合本菜單的多道菜品(即多個命令對象)private List<ICommand> commands = new List<ICommand>();/// <summary>/// 點菜,把菜品加入到菜單中/// </summary>/// <param name="dishCommand">菜品</param>public void AddCommand(ICommand command){commands.Add(command);}public void SetCooker(ICooker cooker){//什么也不用做}public int GetTableNumber(){//什么也不做return 0;}/// <summary>/// 獲取菜單中的多個命令對象/// </summary>/// <returns></returns>public List<ICommand> GetCommandList(){return this.commands;}public void Execute(){//執行菜單就是把菜單傳遞給后廚(即添加到命令隊列中,供后廚廚師自取)CommondQueue.AddMenu(this);}}//Class_end
}

????????由于后面考慮了后廚管理,此時服務員不知道菜單的真正接收者是誰了(到底是哪個廚師做菜),所以現在服務員的職責就很簡單了給顧客點菜,知道顧客點菜完成后將菜單通過菜單對象傳遞給菜單隊列。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 服務員對象/// </summary>internal class Waiter{//持有一個宏命令對象【菜單】private MenuCommand menuCommand = new MenuCommand();/// <summary>/// 客戶點菜/// </summary>/// <param name="command">菜品</param>public void OrderDish(ICommand command){//添加到菜單中menuCommand.AddCommand(command);}/// <summary>/// 客戶點菜完畢,表示需要執行命令了,這里執行這個菜單的組合命令【即廚師真正開始做菜了】/// </summary>public void OrderOver(){this.menuCommand.Execute();}}//Class_end
}

? ?2.5.5、廚師從命令隊列中取菜單去做菜

????????現在有了命令隊列,且有人負責向命令隊列里面添加菜單;那么此時真正做菜的人就是廚師了,廚師從命令菜單里面取菜單,并開始做菜(且在做菜錢會把自己設置到命令對象中去當接收者,表示這個菜有我來做);為了更好的體現命令隊列的用法,我們使用多線程來模擬多個廚師同事做菜(即他們可以同時從命令隊列里面獲取菜單,然后開始做菜,做好一道菜后又取下一道菜,如此循環);為了需要知道菜品是由哪個廚師制作的,所以需要再廚師對象初始化的時候就傳入廚師姓名。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 做熱菜的廚師對象/// </summary>internal class HotCooker : ICooker{//廚師姓名private string cookerName;/// <summary>/// 構造函數/// </summary>/// <param name="name">廚師姓名</param>public HotCooker(string cookerName){this.cookerName = cookerName;}public void Cook(string name,int tableNum){//每次做菜的時間都是不一定的,用隨機數來模擬Random random = new Random(Guid.NewGuid().GetHashCode());int cookTime = random.Next(5,20);Console.WriteLine($"熱菜廚師【{this.cookerName}】正在給【{tableNum}】桌做【{name}】");try{//讓線程休息一下,表示正在做菜Thread.Sleep(cookTime);}catch (Exception ex){throw ex;}finally{Console.WriteLine($"熱菜廚師【{cookerName}】為【{tableNum}】桌做好了【{name}】,共花費【{cookTime}】分鐘");}}public void Run(){//new Thread(new ThreadStart(()=>//{//    while (true)//    {//        Thread.Sleep(1000);//        //從命令隊列里面獲取命令對象//        ICommand command = CommondQueue.GetOneCommand();//        if (command!=null)//        {//            //說明去到命令對象了,這個命令對象還沒有設置接收者(因為前面還不知道//            //到底哪一個廚師來真正執行這個命令;現在知道了,就是當前的廚師實例,設置命令到命令對象中)//            command.SetCooker(this);//            command.Execute();//        }//    }//})).Start();Task task = new Task(() =>{while (true){Thread.Sleep(1000);//從命令隊列里面獲取命令對象ICommand command = CommondQueue.GetOneCommand();if (command != null){//說明去到命令對象了,這個命令對象還沒有設置接收者(因為前面還不知道//到底哪一個廚師來真正執行這個命令;現在知道了,就是當前的廚師實例,設置命令到命令對象中)command.SetCooker(this);command.Execute();}}});task.Start();}}//Class_end
}

? ?2.5.6、實現后廚管理

????????現在由于后廚由很多廚師需要管理,我們專門定義一個后廚管理類(定義廚師是哪些人,且安排他們做菜)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CommandPattern.Macros
{/// <summary>/// 后廚管理類/// </summary>internal class CookerManager{//用來控制是否需要創建廚師,如果已經創建就不要執行了private static bool runFlag = false;//運行后廚管理,創建廚師對象并啟動他們相應的線程(無論運行多少次,創建廚師對象和啟動線程的工作只做一次)public static void RunCookerManager(){if (!runFlag){runFlag = true;//創建三位廚師HotCooker hotCooker1 = new HotCooker("張三");HotCooker hotCooker2 = new HotCooker("李四");HotCooker hotCooker3 = new HotCooker("王五");//啟動各位廚師做菜hotCooker1.Run();hotCooker2.Run();hotCooker3.Run();}}}//Class_end
}

? ?2.5.7、客戶端測試

namespace CommandPattern
{internal class Program{static void Main(string[] args){TestQueue();Console.ReadLine();}/// <summary>/// 測試隊列/// </summary>public static void TestQueue(){Console.WriteLine("測試隊列");//1-先啟動后臺,讓整個程序運行起來Macros.CookerManager.RunCookerManager();//2-為了簡單,直接使用循環模擬桌號點菜過程for (int i = 0; i <3; i++){//創建服務員Macros.Waiter waiter = new Macros.Waiter();//創建命令對象(即需要點的菜品)Macros.ICommand carrotRribs = new Macros.CarrotRribsCommand(i);Macros.ICommand pekingDuck = new Macros.PekingDuckCommand(i);//點菜(就是服務員把這些菜讓服務員記錄下來)waiter.OrderDish(carrotRribs);waiter.OrderDish(pekingDuck);//點菜完畢waiter.OrderOver();}}}//Class_end
}

? ?2.5.8、運行結果

????????由于我們使用了多線程在處理請求隊列,可能每次運行的效果不一樣;在多線程環境下,我們雖然保證了隊列對象獲取的先進先出,但究竟是哪個廚師做菜,做多長時間都不是固定的。?

三、項目源碼工程

kafeiweimei/Learning_DesignPattern: 這是一個關于C#語言編寫的基礎設計模式項目工程,方便學習理解常見的26種設計模式https://github.com/kafeiweimei/Learning_DesignPattern

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/83760.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/83760.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/83760.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Webpack的基本使用 - babel

Mode配置 Mode配置選項可以告知Webpack使用相應模式的內置優化 默認值是production&#xff08;什么都不設置的情況下&#xff09; 可選值有&#xff1a;none | development | production; 這幾個選項有什么區別呢&#xff1f; 認識source-map 我們的代碼通常運行在瀏覽器…

「基于連續小波變換(CWT)和卷積神經網絡(CNN)的心律失常分類算法——ECG信號處理-第十五課」2025年6月6日

一、引言 心律失常是心血管疾病的重要表現形式&#xff0c;其準確分類對臨床診斷具有關鍵意義。傳統的心律失常分類方法主要依賴于人工特征提取和經典機器學習算法&#xff0c;但這些方法往往受限于特征選擇的主觀性和模型的泛化能力。 隨著深度學習技術的發展&#xff0c;基于…

C++.OpenGL (11/64)材質(Materials)

材質(Materials) 真實感材質系統 #mermaid-svg-NjBjrmlcpHupHCFQ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-NjBjrmlcpHupHCFQ .error-icon{fill:#552222;}#mermaid-svg-NjBjrmlcpHupHCFQ .error-text{fill:…

P1345 [USACO5.4] 奶牛的電信Telecowmunication

P1345 [USACO5.4] 奶牛的電信Telecowmunication 突然發現 USACO 好喜歡玩諧音梗。 題意就是給定一個無向圖&#xff0c;問你要刪多少點才能使 s , t s,t s,t 不連通。 注意是刪點而不是刪邊&#xff0c;所以不能直接使用最小割來求。所以考慮變換一下題目模型。 經典 tric…

EXCEL如何快速批量給兩字姓名中間加空格

EXCEL如何快速批量給姓名中間加空格 優點&#xff1a;不會導致排版混亂 缺點&#xff1a;無法輸出在原有單元格上&#xff0c;若需要保留原始數據&#xff0c;可將公式結果復制后“選擇性粘貼為值” 使用場景&#xff1a;在EXCEL中想要快速批量給兩字姓名中間加入空格使姓名對…

使用vtk8.2.0加載dicom圖像

1 上一篇文章我們已經編譯好了VTK的dll&#xff0c;下面我們就來加載他。 2 在Pro里面加載dll #------------------------------------------------- # # Project created by QtCreator 2024-02-04T14:39:07 # #-------------------------------------------------QT …

使用vsftpd搭建FTP服務器(TLS/SSL顯式加密)

安裝vsftpd服務 使用vsftpd RPM安裝包安裝即可&#xff0c;如果可以訪問YUM鏡像源&#xff0c;通過dnf或者yum工具更加方便。 yum -y install vsftpd 啟動vsftpd、查看服務狀態 systemctl enable vsftpd systemctl start vsftpd systemctl status vsftpd 備份配置文件并進…

鴻蒙OSUniApp集成WebGL:打造跨平臺3D視覺盛宴#三方框架 #Uniapp

UniApp集成WebGL&#xff1a;打造跨平臺3D視覺盛宴 在移動應用開發日新月異的今天&#xff0c;3D視覺效果已經成為提升用戶體驗的重要手段。本文將深入探討如何在UniApp中集成WebGL技術&#xff0c;實現炫酷的3D特效&#xff0c;并特別關注鴻蒙系統(HarmonyOS)的適配與優化。 …

前端文件下載常用方式詳解

在前端開發中&#xff0c;實現文件下載是常見的需求。根據不同的場景&#xff0c;我們可以選擇不同的方法來實現文件流的下載。本文介紹三種常用的文件下載方式&#xff1a; 使用 axios 發送 JSON 請求下載文件流使用 axios 發送 FormData 請求下載文件流使用原生 form 表單提…

MacOS解決局域網“沒有到達主機的路由 no route to host“

可能原因&#xff1a;MacOS 15新增了"本地網絡"訪問權限&#xff0c;在 APP 第一次嘗試訪問本地網絡的時候會請求權限&#xff0c;可能順手選擇了關閉。 解決辦法&#xff1a;給想要訪問本地網絡的 APP &#xff08;例如 terminal、Navicat、Ftp&#xff09;添加訪問…

中英文實習證明模板:一鍵生成標準化實習證明,助力實習生職場發展

中英文實習證明模板&#xff1a;一鍵生成標準化實習證明&#xff0c;助力實習生職場發展 【下載地址】中英文實習證明模板 這份中英文實習證明模板專為實習生設計&#xff0c;內容簡潔專業&#xff0c;適用于多種場景。模板采用中英文對照格式&#xff0c;方便國際交流與使用。…

RocketMQ運行架構和消息模型

運?架構 nameServer 命名服務 NameServer 是 RocketMQ 的 輕量級注冊中心&#xff0c;負責管理集群的路由信息&#xff08;Broker 地址、Topic 隊列分布等&#xff09;&#xff0c;其核心作用是解耦 Broker 與客戶端&#xff0c;實現動態服務發現。broker 核?服務 RocketMQ最…

C++學習-入門到精通【11】輸入/輸出流的深入剖析

C學習-入門到精通【11】輸入/輸出流的深入剖析 目錄 C學習-入門到精通【11】輸入/輸出流的深入剖析一、流1.傳統流和標準流2.iostream庫的頭文件3.輸入/輸出流的類的對象 二、輸出流1.char* 變量的輸出2.使用成員函數put進行字符輸出 三、輸入流1.get和getline成員函數2.istrea…

OpenCV 圖像像素的邏輯操作

一、知識點 1、圖像像素的邏輯操作&#xff0c;指的是位操作bitwise&#xff0c;與、或、非、異或等。 2、位操作簡介: 位1 位2 與and 或or 異或xor0 0 0 0 00 1 0 1 11 0 0 …

【AAOS】【源碼分析】用戶管理(二)-- 整體架構

整體介紹 Android多用戶功能作為 Android Automotive 的重要組成部分,為不同駕駛員和乘客提供了一個更加定制化、隱私保護的使用環境。Android 多用戶的存在,它可以讓多個用戶使用同一臺設備,同時保持彼此的數據、應用和設置分隔開來。 各用戶類型的權限 能力SystemAdminS…

Redis最佳實踐——電商應用的性能監控與告警體系設計詳解

Redis 在電商應用的性能監控與告警體系設計 一、原子級監控指標深度拆解 1. 內存維度監控 核心指標&#xff1a; # 實時內存組成分析&#xff08;單位字節&#xff09; used_memory: 物理內存總量 used_memory_dataset: 數據集占用量 used_memory_overhead: 管理開銷內存 us…

多模態大語言模型arxiv論文略讀(109)

Math-PUMA: Progressive Upward Multimodal Alignment to Enhance Mathematical Reasoning ?? 論文標題&#xff1a;Math-PUMA: Progressive Upward Multimodal Alignment to Enhance Mathematical Reasoning ?? 論文作者&#xff1a;Wenwen Zhuang, Xin Huang, Xiantao Z…

web3-以太坊智能合約基礎(理解智能合約Solidity)

以太坊智能合約基礎&#xff08;理解智能合約/Solidity&#xff09; 無需編程經驗&#xff0c;也可以幫助你了解Solidity獨特的部分&#xff1b;如果本身就有相應的編程經驗如java&#xff0c;python等那么學起來也會非常的輕松 一、Solidity和EVM字節碼 實際上以太坊鏈上儲存…

D2-基于本地Ollama模型的多輪問答系統

本程序是一個基于 Gradio 和 Ollama API 構建的支持多輪對話的寫作助手。相較于上一版本&#xff0c;本版本新增了對話歷史記錄、Token 計數、參數調節和清空對話功能&#xff0c;顯著提升了用戶體驗和交互靈活性。 程序通過抽象基類 LLMAgent 實現模塊化設計&#xff0c;當前…

傳統業務對接AI-AI編程框架-Rasa的業務應用實戰(2)--選定Python環境 安裝rasa并初始化工程

此篇接續上一篇 傳統業務對接AI-AI編程框架-Rasa的業務應用實戰&#xff08;1&#xff09;--項目背景即學習初衷 1、Python 環境版本的選擇 我主機上默認的Python環境是3.12.3 &#xff08;我喜歡保持使用最新版本的工具或框架&#xff0c;當初裝python時最新的穩定版本就是…