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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...