headfirst設計模式(7)—命令模式

来源:https://www.cnblogs.com/skyseavae/archive/2019/02/25/10428133.html
-Advertisement-
Play Games

一、前言 什麼是命令模式? 在軟體系統中,“行為請求者”與“行為實現者”通常呈現一種“緊耦合”。但在某些場合,比如要對行為進行“記錄、撤銷/重做、事務”等處理,這種無法抵禦變化的緊耦合是不合適的。在這種情況下,如何將“行為請求者”與“行為實現者”解耦?將一組行為抽象為對象,實現二者之間的松耦合,這就 ...


一、前言

什麼是命令模式?

在軟體系統中,“行為請求者”與“行為實現者”通常呈現一種“緊耦合”。但在某些場合,比如要對行為進行“記錄、撤銷/重做、事務”等處理,這種無法抵禦變化的緊耦合是不合適的。在這種情況下,如何將“行為請求者”與“行為實現者”解耦?將一組行為抽象為對象,實現二者之間的松耦合,這就是命令模式(Command Pattern)——摘自百度百科

它解決了什麼問題?

解除行為請求者和行為實現者的高耦合。

比如,有一個業務,最開始的時候是:A->B

然後,有一天產品說,B這個資源比較敏感,每次調用B之前要先記錄一下日誌,假如說記錄日誌的業務邏輯是C,然後業務邏輯就會變成下麵這個樣子:

A -> C, A-> B

再然後,產品又說,B這個資源啊,有點不穩定,如果調用失敗的時候,能不能重試3次?然後業務邏輯就變成這個樣子:

A->C, retry 3 :A->B

後來,產品覺得B資源...,不這次不是B資源了,出現了一個新需要,處理的邏輯和B有點像,能不能$%^&?

結果,那塊代碼就變得無法維護了。。。如果有一天,需要去重構它,或者碰到一個類似的需求的時候,如何不讓自己難受,不給別人挖坑?

命令模式買不了吃虧,買不了上當!

 

二、以餐廳點餐,瞭解命令模式

舉個例子:假如你去一個餐廳吃烤魚,從叫服務員點菜,到服務員通知師傅做菜,到最後服務員上菜,這個流程就可以描述成一個命令模式,類圖如下:

 

 

每個角色和命令模式的幾個核心要素對應關係如上圖所示。

Customer(Client)

/**
 * 顧客(Client)
 */
public class Customer {

  public static void main(String[] args) {
      
    Order order = new BakeFishOrder(new Chef("李"));

    Waiter waiter = new Waiter();
    waiter.takeOrder(order);
    waiter.orderUp();
  }
}

顧客需要做3個事情:

1,點一個烤魚

2,把菜單交給服務員

3,服務員上菜

顧客不關心這個烤魚的製作流程,只希望,我點菜以後,能夠按時把菜上上來就OK了,在實際的業務中,甚至可以把 new Chef() 這個操作也放在其他地方實現

比如具體的Command裡面,或者Invoker裡面,讓用戶感知不到,這樣業務的邏輯會變得更加平滑,總的來說就是:只關註自己需要的東西

Waiter(Invoker)

/**
 * 服務員(Invoker)
 */
public class Waiter {

  private Order order;

  public void takeOrder(Order order){
    System.out.println("服務員接到訂單!");
    this.order = order;
  }

  public void orderUp(){
    System.out.println("服務員呼叫後臺準備烹飪訂單中的菜品");
    order.execute();
    System.out.println("訂單菜品已經烹飪完成,服務員上菜!");
  }
}

服務員這裡需要做兩件事情:

1,接單

2,通知廚房做菜並上菜

對於服務員來說,他也不需要關心這道菜誰做的,怎麼做的,只需要給廚房說了以後,過段時間把做好的菜送到客戶的手上就OK了

Order(Command)

/**
 * 訂單(Command)
 */
public interface Order {
  void execute();
}

訂單是一個抽象的東西,具體幹活的是訂單介面實現類,引入訂單的作用是面向抽象編程,以後每加一種菜,只需要增加一個實現類就可以了

BakeFishOrder(ConncereteCommand)

/**
 * 烤魚訂單(具體的訂單對象)
 */
public class BakeFishOrder implements Order{

  private Chef chef;

  public BakeFishOrder(Chef chef){
    this.chef = chef;
  }

  public void execute() {
    chef.bakeFish();
  }
}

烤魚訂單這裡就是一個具體的訂單,在它的execute方法中就需要去調用真正的實現者的一個或一些方法來實現製作烤魚這樣的一個需求。

Chef(Receiver)

/**
 * 廚師(Receiver)
 */
public class Chef {

  private String name;

  public Chef(String name){
    this.name = name;
  }

  public void bakeFish(){
    System.out.println(name + "師傅在製作烤魚...");
  }
}

在這個例子中,廚師就是最後幹活的對應命令模式中的Receiver,繞這麼大一圈,也就是為了顧客(行為請求者)和 廚師(行為實現者)的解耦。

運行結果:

 

顧客和廚師解耦的好處是什麼?

1,在點餐的流程不變的情況下,可以隨意增加新菜(Order的實現類)

2,在點餐的流程不變的情況下,原菜品的製作方式變化,上層是無需做任何修改的

3,在Waiter中,可以做各種日誌記錄,訂單排隊,之類的各種操作,非耦合的情況下,可操作性很大,並且不會影響到顧客和廚師相關類

三、遙控器需求

HeadFirst上,需要實現的是一個遙控器,遙控器用來遙控房間內的各種家電,並且可以實現undo功能。

但是有幾個問題:

1,每個廠商的驅動標準是不一樣的,比如電燈,開關的方法是:on(), off(),弔扇的方法是:high(), medium(),low(),off()

2,遙控器直接調用具體的家電驅動的話,一旦新的廠商載入到遙控器上面的時候,就必須要改代碼,比如:電燈的開關方法變成了:dim(level),通過level來調節燈泡的明暗程度,調到0就是關,調到100就是開。

如何使用命令模式來實現呢?具體的流程如下:

0,驅動類

/**
 * 描述:燈
 */
public class Light {

    public static final int LIGHT_ON = 100;//打開燈時,燈的亮度
    public static final int LIGHT_OFF = 0;//關閉燈時,燈的亮度

    private String location;//燈的位置
    private int level;//亮度

    public Light(String location) {
        this.location = location;
    }

    //開燈
    public void on() {
        level = LIGHT_ON;
        System.out.println(location + ", 燈已經打開!");
    }

    //關燈
    public void off() {
        level = LIGHT_OFF;
        System.out.println(location + ", 燈已經關閉!");
    }

    /**
     * 調節燈的亮度
     * @param level 亮度
     */
    public void dim(int level) {
        this.level = level;

        if (level == LIGHT_OFF) {
            off();
            return;
        }

        if(level == LIGHT_ON){
            on();
            return;
        }

        throw new RuntimeException("無法調節到指定的亮度, level: " + level);
    }

    public int getLevel() {
        return level;
    }
}
View Code
/**
 * 描述:弔扇
 */
public class CeilingFan {

    public static final int HIGH = 3;//3檔
    public static final int MEDIUM = 2;//2檔
    public static final int LOW = 1;//1檔
    public static final int OFF = 0;//0檔
    String location;//弔扇位置
    private int speed;//當前速度
 
    public CeilingFan(String location) {
        this.location = location;
        speed = OFF;
    }
  
    public void high() {
        speed = HIGH;
        System.out.println(location + ",弔扇檔位:" + HIGH);
    } 
 
    public void medium() {
        speed = MEDIUM;
        System.out.println(location + ",弔扇檔位:" + MEDIUM);
    }
 
    public void low() {
        speed = LOW;
        System.out.println(location + ",弔扇檔位:" + LOW);
    }
  
    public void off() {
        speed = OFF;
        System.out.println(location + ",弔扇檔位:" + OFF);
    }
  
    public int getSpeed() {
        return speed;
    }
}
View Code

1,制定標準,每個家電都必須依照這個規範來進行開發,接入遙控器系統(Command介面)

/**
 * 描述:命令介面
 */
public interface Command {
    /**
     * 描述:執行命令
     */
    void execute();

    /**
     * 描述:撤銷命令
     */
    void undo();
}

2,根據需求先實現遙控器,再實現具體的命令(由上而下開發)

/**
 * 遙控器
 */
public class RemoteControl {
    Command[] onCommands;//啟動命令
    Command[] offCommands;//關閉命令
    Command undoCommand;//撤銷命令
 
    public RemoteControl() {
        //初始化遙控器,命令設置為預設實現類NoCommand,減少判斷邏輯,減少NLP概率
        onCommands = new Command[7];
        offCommands = new Command[7];
        Command noCommand = new NoCommand();
        for(int i=0;i<7;i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
        undoCommand = noCommand;
    }

    /**
     * 設置命令
     * @param slot 按鈕位置
     * @param onCommand 啟動命令
     * @param offCommand 關閉命令
     */
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    /**
     * 啟動按鈕按下時的操作
     * @param slot 按鈕位置
     */
    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
        undoCommand = onCommands[slot];
    }

    /**
     * 關閉按鈕按下時的操作
     * @param slot 按鈕位置
     */
    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
        undoCommand = offCommands[slot];
    }

    /**
     * 撤銷按鈕按下時的操作
     */
    public void undoButtonWasPushed() {
        undoCommand.undo();
    }
  
    public String toString() {
        StringBuffer stringBuff = new StringBuffer();
        stringBuff.append("\n------ 遙控器 -------\n");
        for (int i = 0; i < onCommands.length; i++) {
            stringBuff.append("[位置 " + i + "] " + onCommands[i].getClass().getName()
                + "    " + offCommands[i].getClass().getName() + "\n");
        }
        stringBuff.append("[撤銷] " + undoCommand.getClass().getName() + "\n");
        return stringBuff.toString();
    }
}
/**
 * 描述:命令的預設的空實現
 */
public class NoCommand implements Command {
    public void execute() { }
    public void undo() { }
}

3,實現電燈的開關

/**
 * 描述:開燈命令
 */
public class LightOnCommand implements Command {
    private Light light;
    private int lastLightLevel;
    public LightOnCommand(Light light) {
        this.light = light;
    }
    public void execute() {
        lastLightLevel = light.getLevel();
        light.on();
    }

    public void undo() {
        light.dim(lastLightLevel);
    }
}
View Code
/**
 * 描述:關燈命令
 */
public class LightOffCommand implements Command {

    private Light light;
    private int lastLightLevel;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        lastLightLevel = light.getLevel();
        light.off();
    }

    public void undo() {
        light.dim(lastLightLevel);
    }
}
View Code

4,實現風扇的各個檔位

public class CeilingFanHighCommand implements Command {
    CeilingFan ceilingFan;
    int prevSpeed;
  
    public CeilingFanHighCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }
 
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.high();
    }
 
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}
View Code
public class CeilingFanMediumCommand implements Command {
    CeilingFan ceilingFan;
    int prevSpeed;
  
    public CeilingFanMediumCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }
 
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.medium();
    }
 
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}
View Code
public class CeilingFanLowCommand implements Command {
    CeilingFan ceilingFan;
    int prevSpeed;
  
    public CeilingFanLowCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }
 
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.low();
    }
 
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}
View Code
public class CeilingFanOffCommand implements Command {
    CeilingFan ceilingFan;
    int prevSpeed;
  
    public CeilingFanOffCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }
 
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.off();
    }
 
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}
View Code

當然這種並不是最好的方式,undo中的代碼被寫了幾次,這樣,哪裡有重覆,哪裡就有抽象(實際業務中,請根據具體業務選擇是否抽象,如果抽象了發現業務更難做了,就要考慮是不是抽象的方向有問題了)

public abstract class AbstractCeilingFanCommand implements Command{
  private CeilingFan ceilingFan;
  private int prevSpeed;

  public AbstractCeilingFanCommand(CeilingFan ceilingFan) {
    this.ceilingFan = ceilingFan;
  }

  public void execute() {
    prevSpeed = ceilingFan.getSpeed();
    doExecute();
  }

  protected abstract void doExecute();

  public void undo() {
    if (prevSpeed == CeilingFan.HIGH) {
      ceilingFan.high();
    } else if (prevSpeed == CeilingFan.MEDIUM) {
      ceilingFan.medium();
    } else if (prevSpeed == CeilingFan.LOW) {
      ceilingFan.low();
    } else if (prevSpeed == CeilingFan.OFF) {
      ceilingFan.off();
    }
  }

  protected CeilingFan getCeilingFan() {
    return ceilingFan;
  }
}

重新實現各個檔位

public class CeilingFanHighCommand extends AbstractCeilingFanCommand{

    public CeilingFanHighCommand(CeilingFan ceilingFan) {
        super(ceilingFan);
    }

    protected void doExecute() {
        getCeilingFan().high();
    }
}
View Code
public class CeilingFanMediumCommand extends AbstractCeilingFanCommand{

    public CeilingFanMediumCommand(CeilingFan ceilingFan) {
        super(ceilingFan);
    }

    protected void doExecute() {
        getCeilingFan().medium();
    }
}
View Code
public class CeilingFanLowCommand extends AbstractCeilingFanCommand {
    public CeilingFanLowCommand(CeilingFan ceilingFan) {
        super(ceilingFan);
    }
    protected void doExecute() {
        getCeilingFan().low();
    }
}
View Code
public class CeilingFanOffCommand extends AbstractCeilingFanCommand {

    public CeilingFanOffCommand(CeilingFan ceilingFan) {
        super(ceilingFan);
    }

    protected void doExecute() {
        getCeilingFan().off();
    }
}
View Code

最後是測試類:

public class TestClient {

    private static RemoteControl REMOTE_CONTROL = new RemoteControl();
    static{

        //初始化燈也可以是抽象的,因為,Light的命令,並不關心是什麼燈
        Light livingRoomLight = new Light("卧室");
        //設置開燈,關燈命令
        REMOTE_CONTROL.setCommand(0, new LightOnCommand(livingRoomLight), new LightOffCommand(livingRoomLight));

        //初始化弔扇
        CeilingFan ceilingFan = new CeilingFan("卧室");
        //初始化弔扇每個檔位的命令
        CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);
        REMOTE_CONTROL.setCommand(1, new CeilingFanLowCommand(ceilingFan), ceilingFanOff);
        REMOTE_CONTROL.setCommand(2, new CeilingFanMediumCommand(ceilingFan), ceilingFanOff);
        REMOTE_CONTROL.setCommand(3, new CeilingFanHighCommand(ceilingFan), ceilingFanOff);
    }
 
    public static void main(String[] args) {

        System.out.println(REMOTE_CONTROL);

        REMOTE_CONTROL.onButtonWasPushed(0);
        REMOTE_CONTROL.offButtonWasPushed(0);

        System.out.print("撤銷命令執行:");
        REMOTE_CONTROL.undoButtonWasPushed();

        REMOTE_CONTROL.onButtonWasPushed(1);
        REMOTE_CONTROL.onButtonWasPushed(2);
        System.out.print("撤銷命令執行:");
        REMOTE_CONTROL.undoButtonWasPushed();

        REMOTE_CONTROL.onButtonWasPushed(3);
        System.out.print("撤銷命令執行:");
        REMOTE_CONTROL.undoButtonWasPushed();
  
        REMOTE_CONTROL.offButtonWasPushed(3);
        System.out.print("撤銷命令執行:");
        REMOTE_CONTROL.undoButtonWasPushed();
    }
}

測試結果:

梳理一下,命令模式和這裡的對應關係:

Invoker:RemoteControl(遙控器)

Command:Command介面

ConcereteCommand:LightOnCommand,LightOffCommand,CeilingFanLowCommand....

Receiver:Light,CeilingFan

Client:TestClient

對TestClient和具體的驅動類(Light,CeilingFan)實現解耦,Client不直接調用驅動類,而是基於遙控器(Invoker)制定的標準,來調用具體的命令實現類。

這樣,在新增新的家電驅動的時候,只需要新增命令實現類和Client的初始化代碼即可,也可以做成配置文件,這樣就不用去改TestClient代碼了

四、命令模式優缺點

優點:

1,降低了請求者和執行者之間的耦合性

2,新命令的擴展性很強,需要新的命令,只需要加一個實現類即可,對現有代碼影響很小

3,命令之間還可以相互組合形成新的命令,比如在遙控器裡面要實現一個閃光效果,可以用LightOnCommand,LightOffCommand,結合起來做一個巨集命令

缺點:

命令的數量可能會很多,每個指令都必須封裝成一個類,如果指令過多,那麼開發的成本會很高

所以,只有在請求者和執行者真的需要解耦的時候才使用:

比如:A->B的調用中新增了很多操作,日誌,排隊,各種邏輯,需要引入Invoker

比如:有A->C邏輯和B相似,則可以引入Command介面來抽象這一流程

比如:B,C邏輯需要撤銷,或者重做的時候,也可以引入命令模式

總之,根據需要來抽象,沒有最好的,只有當下最合適的

或許,spring aop就很合適呢? 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文由雲+社區發表 作者:廖彩明 在從事前端開發過程中,瀏覽器作為最重要的開發環境,瀏覽器基礎是是前端開發人員必須掌握的基礎知識點,它貫穿著前端的整個網路體系。對瀏覽器原理的瞭解,決定著編寫前端代碼性能的上限。瀏覽器作為JS的運行環境,學習總結下現代瀏覽器的相關知識 前言 經常聽說瀏覽器內核,瀏覽器 ...
  • 對於如此多的不同情形,可以總結如下:- 在不賦值的情形下,在小括弧中的函數或者函數表達式,被阻止聲明為一個全局的變數,同時其內部是可執行的 ...
  • 繼上篇 "文章" 介紹了Webpack的基本概念,完整流程,以及打包過程中廣播的一些事件的作用,這篇文章主要講生成的chunk文件如何輸出成具體的文件。分同步和非同步兩種情況來分析輸出的文件 。 模塊文件show.js 同步載入 生成的bundle文件 非同步載入 經webpack打包會生成兩個文件0. ...
  • 錯誤消息如圖: 如果你看到此錯誤消息,則說明 v-for 指令的 key值 重覆了,只需修改你的 key值 讓其不會重覆即可。 ...
  • Reverse a String 翻轉字元串 先把字元串轉化成數組,再藉助數組的reverse方法翻轉數組順序,最後把數組轉化成字元串。 你的結果必須得是一個字元串 當你完成不了挑戰的時候,記得開大招'Read-Search-Ask'。 這是一些對你有幫助的資源: Global String Obj ...
  • 效果展示: 源碼:https://github.com/ProsperLee/demo-h5-package ...
  • 背景:easyui在做上下佈局的時候,上面是數據列表,下麵是數據圖表。如下圖 需要在上下麵板右上角加上最大化按鈕,以便可以全屏顯示。邏輯就是當上面點擊最大化時候,隱藏下麵,主意:此時需要將下麵的div標題設置為空字元,否則後果就是列表中間有一道人員能力統計圖標題;當點擊恢復按鈕的時候,展示下麵圖表, ...
  • LieBrother原文 : "行為型模式:策略模式" 十一大行為型模式之五:策略模式。 簡介 姓名 :策略模式 英文名 :Strategy Pattern 價值觀 :集計謀於一身 個人介紹 : Define a family of algorithms,encapsulate each one,a ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...