解釋器模式 Interpreter 行為型 設計模式(十九)

来源:https://www.cnblogs.com/noteless/archive/2018/12/12/10107675.html
-Advertisement-
Play Games

解釋器模式是一種不很常用的模式,但是比如正則表達式就是一種解釋器模式的思維,所以儘管實際編碼中不常用,理解解釋器模式的含義很重要,本文對解釋器模式進行了簡單的介紹,並且給出了Java代碼示例,介紹瞭解釋器模式的意圖結構。 ...


  解釋器模式(Interpreter)   image_5c108d54_680 考慮上圖中計算器的例子 設計可以用於計算加減運算(簡單起見,省略乘除),你會怎麼做?    你可能會定義一個工具類,工具類中有N多靜態方法 比如定義了兩個方法用於計算a+b 和 a+b-c
public static int add(int a,int b){
return a+b;
}

public static int add(int a,int b,int c){
return a+b-c;
}
但是很明顯,如果形式有限,那麼可以針對對應的形式進行編程 如果形勢變化非常多,這就不符合要求,因為加法和減法運算,兩個運算符與數值可以有無窮種組合方式 比如 a+b+c+d+e+f、a-b-c+d、a-b+c....等等  用有限的方法參數列表組合的形式,怎麼可能表達出無窮的變化?   也可以通過函數式介面,能夠提高一定的靈活性
package function;

@FunctionalInterface
public interface Function1<A,B,C,D,E,F,G, R> {
R xxxxx(A a,B b,C c,D d,E e,F f,G g);
}
image_5c108d54_4451 好處是可以動態的自定義方程式,但是你可能需要定義很多函數式介面 而且,有限的函數式介面也不能解決無限種可能的 上面的方式都是以有限去應對無限,必然有行不通的時候 顯然,你需要一種翻譯識別機器,能夠解析由數字以及+ - 符號構成的合法的運算序列 如果把運算符和數字都看作節點的話,能夠逐個節點的進行讀取解析運算 這就是解釋器模式的思維 解釋器不限定具體的格式,僅僅限定語法,能夠識別遵循這種語法的“語言”書寫的句子 不固定你的形式,也就是不存在強製為a+b的情形,但是你必須遵循固定語法,數字+ - 符號組成 Java編譯器可以識別遵循java語法的表達式和語句,C語言編譯器可以識別遵循C語言語法的表達式和語句。說的就是這個意思  

意圖

給定一個語言,定義他的文法的一種表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。 解釋器模式其實就是編譯原理的思維方式   如果某種特定類型的問題發生的頻率很高,那麼就可以考慮將該問題的各個實例表述為一個簡單語言中的句子,通過解釋器進行識別。 經典的案例就是正則表達式 我們在實際開發中,經常需要判斷郵箱地址、手機號碼是否正確,如果沒有正則表達式 我們需要編寫特定的演算法函數進行判斷,去實現這些規則,比如一個演算法可能用來判斷是否是郵箱,比如要求必須有@符號   正則表達式是用來解決字元串匹配的問題,他是解釋器模式思維的一個運用實例 通過定義正則表達式的語法結構,進而通過表達式定義待匹配字元的集合,然後通過通用的演算法來解釋執行正則表達式 解釋器模式將語法規則抽象出來,設置通用的語法規則,然後使用通用演算法執行 使用正則表達式你不在需要自己手動實現演算法去實現規則,你只需要按照正則表達式的語法,對你需要匹配的字元集合進行描述即可 有現成的通用演算法來幫你實現,而語法相對於演算法的實現,自然是簡單了很多 再比如瀏覽器解析HTML,我們知道HTML頁面是由固定的元素組成的,有他的語法結構 但是一個HTML頁面的標簽的個數以及標簽內容的組合形式卻是千變萬化的,但是瀏覽器可以正確的將他們解析呈現出來 這也是一種解釋器的模型   在解釋器模式中,我們需要將待解決的問題,提取出規則,抽象為一種“語言” 比如加減法運算,規則為:有數值和+- 符號組成的合法序列 加減法運算就不能有乘除,否則就不符合語法 “1+2+3”就是這種語言的一個句子   比如遙控汽車的操作按鈕,規則為:由前進、後退、左轉、右轉四種指令組成 遙控汽車就不能有起飛,否則就是不符合語法的 “前進 左轉 後退 前進 後退”就是這種語言的一個句子   解釋器就是要解析出來語句的含義 既然需要將待解決的問題場景提取出規則,那麼如何描述規則呢?  

語法規則描述

對於語法規則的定義,也有一套規範用於描述 Backus-Naur符號(就是眾所周知的BNF或Backus-Naur Form)是描述語言的形式化的數學方法 叫做範式,此後又有擴展的,叫做EBNF
範式基本規則
::= 表示定義,由什麼推導出 尖括弧 < > 內為必選項; 方括弧 [ ] 內為可選項; 大括弧 { } 內為可重覆0至無數次的項; 圓括弧 ( ) 內的所有項為一組,用來控製表達式的優先順序 豎線 | 表示或,左右的其中一個 引號內為字元本身,引號外為語法(比如 'for'表示關鍵字for )
  有了規則我們就可以對語法進行描述,這是解釋器模式的基礎工作 比如加減法運算可以這樣定義
expression:=value | plus | minus plus:=expression ‘+’ expression minus:=expression ‘-’ expression value:=integer
值的類型為整型數 有加法規則和減法規則 表達式可以是一個值,也可以是一個plus或者minus 而plus和minus又是由表達式結合運算符構成   可以看得出來,有遞歸嵌套的概念
 

抽象語法樹

除了使用文法規則來定義規則,還可以通過抽象語法樹的圖形方式直觀的表示語言的構成 文法規則描述了所有的場景,所有條件匹配的都是符合的,不匹配的都是不符合的 符合語法規則的一個“句子”就是語言規則的一個實例 抽象語法樹正是對於這個實例的一個描述 一顆抽象語法樹對應著語言規則的一個實例   關於抽象語法樹百科中這樣介紹 在電腦科學中,抽象語法樹(abstract syntax tree 或者縮寫為 AST),或者語法樹(syntax tree) 是源代碼的抽象語法結構的樹狀表現形式,這裡特指編程語言的源代碼。 樹上的每個節點都表示源代碼中的一種結構。 之所以說語法是「抽象」的,是因為這裡的語法並不會表示出真實語法中出現的每個細節。   比如 1+2+3+4-5是一個實例 image_5c108d54_58bf 所以說文法規則用於描述語言規則,抽象語法樹描述描述語言的一個實例,也就是一個“句子”

結構

image_5c108d54_6382 抽象表達式角色AbstractExpression 聲明一個抽象的解釋操作,所有的具體表達式操作都需要實現的抽象介面 介面主要是interpret()方法,叫做解釋操作 終結符表達式角色TerminalExpression 這是一個具體角色,實現與文法中的終結符相關聯的解釋操作,主要就是interpret()方法 一個句子中的每個終結符都需要此類的一個實例 非終結符表達式NoneTerminalExpression 這也是一個具體的角色,對文法中的每一條規則R::=R1R2.....Rn都需要一個NoneTerminalExpression 類,註意是類,而不是實例 對每一個R1R2...Rn中的符號都持有一個靜態類型為AbstractExpression的實例變數; 實現解釋操作,主要就是interpret()方法 解釋操作以遞歸的方式調用上面所提到的代表R1R2...Rn中的各個符號的實例變數 上下文角色Context 包含解釋器之外的一些全局信息,一般情況下都會需要這個角色 Client 構建表示該文法定義的語言中的一個特定的句子的抽象語法樹 抽象語法樹由NoneTerminalExpression 和 TerminalExpression的實例組裝而成 調用解釋器的interpret()方法

終結符和非終結符

通俗的說就是不能單獨出現在推導式左邊的符號,也就是說終結符不能再進行推導,也就是終結符不能被別人定義 除了終結符就是非終結符 從抽象語法樹中可以發現,葉子節點就是終結符 除了葉子節點就是非終結符

角色示例解析

回到剛纔的例子
expression:=value | plus | minus plus:=expression ‘+’ expression minus:=expression ‘-’ expression value:=integer 
  上面是我們給加減法運算定義的語法規則,由四條規則組成 其中規則value:=integer 表示的就是終結符 所以這是一個TerminalExpression,每一個數字1+2+3+4-5中的1,2,3,4,5就是TerminalExpression的一個實例對象。   對於plus和minus規則,他們不是非終結符,屬於NoneTerminalExpression 他們的推導規則分別是通過‘+’和‘-’連接兩個expression 也就是角色中說到的“對文法中的每一條規則R::=R1R2.....Rn都需要一個NoneTerminalExpression 類” 也就是說plus表示一條規則,需要一個NoneTerminalExpression類 minus表示一條規則,需要一個NoneTerminalExpression類 expression是value 或者 plus 或者 minus,所以不需要NoneTerminalExpression類了   非終結符由終結符推導而來 NoneTerminalExpression類由TerminalExpression組合而成 所以需要:抽象表達式角色AbstractExpression   在計算過程中,一般需要全局變數保存變數數據 這就是Context角色的一般作用     image_5c108d54_15ff     以最初的加減法為例,我們的句子就是數字和+ - 符號組成 比如 1+2+3+4-5   抽象角色AbstractExpression
package interpret;
public abstract class AbstractExpression {
public abstract int interpret();
}
終結符表達式角色TerminalExpression 內部有一個int類型的value,通過構造方法設置值
package interpret;

public class Value extends AbstractExpression {

private int value;
Value(int value){
this.value = value;
}

@Override
public int interpret() {
return value;
}
}
加法NoneTerminalExpression
package interpret;

public class Plus extends AbstractExpression {

private AbstractExpression left;
private AbstractExpression right;

Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}


@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}
減法 NoneTerminalExpression
package interpret;

public class Minus extends AbstractExpression {

private AbstractExpression left;
private AbstractExpression right;

Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}

@Override
public int interpret() {
return left.interpret() - right.interpret();
}
}
客戶端角色
package interpret;

public class Client {

public static void main(String[] args) {

AbstractExpression expression = new Minus(
new Plus(new Plus(new Plus(new Value(1), new Value(2)), new Value(3)), new Value(4)),
new Value(5));
System.out.println(expression.interpret());
}
}
image_5c108d54_4566   上面的示例中,完成瞭解釋器模式的基本使用 我們通過不斷重覆的new 對象的形式,嵌套的構造了一顆抽象語法樹 只需要執行interpret 方法即可獲取最終的結果   這就是解釋器模式的基本原理 非終結符表達式由終結符表達式組合而來,也就是由非終結符表達式嵌套 嵌套就意味著遞歸,類似下麵的方法,除非是終結符表達式,否則會一直遞歸
int f(int x) {
if (1 == x) {
return x;
} else {
return x+f(x-1);
}
}
上面的示例中,每次使用時,都需要藉助於new 按照抽象語法樹的形式創建一堆對象 比如計算1+2與3+4 是不是可以轉換為公式的形式呢? 也就是僅僅定義一次表達式,不管是1+2 還是3+4還是6+8 都可以計算? 所以我們考慮增加“變數”這一終結符表達式節點 增加變數類Variable  終結符節點 內部包含名稱和值,提供值變更的方法
package interpret;
public class Variable extends AbstractExpression{
    private String name;
    private Integer value;
    Variable(String name,Integer value){
        this.name = name;
        this.value = value;
    }
    public void setValue(Integer value) {
        this.value = value;
    }
    @Override
    public int interpret() {
        return value;
    }
}
package interpret;
public class Client {
public static void main(String[] args) {
        //定義變數X和Y,初始值都為0
        Variable variableX = new Variable("x", 0);
        Variable variableY = new Variable("y", 0);
        //計算公式為: X+Y+X-1
        AbstractExpression expression2 = new Minus(new Plus(new Plus(variableX, variableY), variableX),
        new Value(1));
        variableX.setValue(1);
        variableY.setValue(3);
        System.out.println(expression2.interpret());
        variableX.setValue(5);
        variableY.setValue(6);
        System.out.println(expression2.interpret());
    }
}
image_5c108d54_354f   有了變數類 Variable,就可以藉助於變數進行公式的計算 而且,很顯然,公式只需要設置一次,而且可以動態設置 通過改變變數的值就可以達到套用公式的目的   一般的做法並不是直接將值設置在變數類裡面,變數只有一個名字,將節點所有的值設置到Context類中 Context的作用可以通過示例代碼感受下  

代碼示例

完整示例如下 image_5c108d54_4ce5 AbstractExpression抽象表達式角色 接受參數Context,如有需要可以從全局空間中獲取數據
package interpret.refactor;

public abstract class AbstractExpression {
public abstract int interpret(Context ctx);
}
數值類Value 終結符表達式節點 內部還有int value 他不需要從全局空間獲取數據,所以interpret方法中的Context用不到 增加了toString方法,用於呈現 數值類的toString方法直接回顯數值的值
package interpret.refactor;

public class Value extends AbstractExpression {

private int value;

Value(int value) {
this.value = value;
}

@Override
public int interpret(Context ctx) {
return value;
}

@Override
public String toString() {
return new Integer(value).toString();
}
}
變數類Variable  終結符表達式 變數類擁有名字,使用內部的String name 變數類的真值保存在Context中,Context是藉助於hashMap存儲的 Context定義的類型為Map<Variable, Integer> 所以,我們重寫了equals以及hashCode方法 Variable的值存儲在Context這一全局環境中,值也是從中獲取
package interpret.refactor;

public class Variable extends AbstractExpression {

private String name;
Variable(String name) {
this.name = name;
}


@Override
public int interpret(Context ctx) {
return ctx.getValue(this);
}

@Override
public boolean equals(Object obj) {
if (obj != null && obj instanceof Variable) {
return this.name.equals(
((Variable) obj).name);
}
return false;
}

@Override
public int hashCode() {
return this.toString().hashCode();
}

@Override
public String toString() {
return name;
}
}
加法跟原來差不多,interpret接受參數Context,如有需要從Context中讀取數據
package interpret.refactor;

public class Plus extends AbstractExpression {

private AbstractExpression left;

private AbstractExpression right;

Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}

@Override
public int interpret(Context ctx) {
return left.interpret(ctx) + right.interpret(ctx);
}

@Override
public String toString() {
return "(" + left.toString() + " + " + right.toString() + ")";
}
}
package interpret.refactor;

public class Minus extends AbstractExpression {

private AbstractExpression left;

private AbstractExpression right;

Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}

@Override
public int interpret(Context ctx) {
return left.interpret(ctx) - right.interpret(ctx);
}

@Override
public String toString() {
return "(" + left.toString() + " - " + right.toString() + ")";
}
}
環境類Context 內部包含一個 private Map<Variable, Integer> map,用於存儲變數數據信息 key為Variable 提供設置和獲取方法
package interpret.refactor;

import java.util.HashMap;

import java.util.Map;

public class Context {

private Map<Variable, Integer> map = new HashMap<Variable, Integer>();

public void assign(Variable var, Integer value) {
map.put(var, new Integer(value));
}

public int getValue(Variable var) {
Integer value = map.get(var);
return value;
}
}
package interpret.refactor;


public class Client {

public static void main(String[] args) {

Context ctx = new Context();

Variable a = new Variable("a");
Variable b = new Variable("b");
Variable c = new Variable("c");
Variable d = new Variable("d");
Variable e = new Variable("e");
Value v = new Value(1);

ctx.assign(a, 1);
ctx.assign(b, 2);
ctx.assign(c, 3);
ctx.assign(d, 4);
ctx.assign(e, 5);

AbstractExpression expression = new Minus(new Plus(new Plus(new Plus(a, b), c), d), e);

System.out.println(expression + "= " + expression.interpret(ctx));
}
}

 

image_5c108d54_6b15 上述客戶端測試代碼中,我們定義了a,b,c,d,e 五個變數 通過Context賦值,初始化為1,2,3,4,5 然後構造了公式,計算結果 後續只需要設置變數的值即可套用這一公式 如果需要變動公式就修改表達式,如果設置變數就直接改變值即可 這種模式就實現了真正的靈活自由,只要是加減法運算,必然能夠運算 不再需要固定的參數列表或者函數式介面,非常靈活    另外對於抽象語法樹的生成,你也可以轉變形式 比如下麵我寫了一個簡單的方法用於將字元串轉換為抽象語法樹的Expression
/**
   * 解析字元串,構造抽象語法樹 方法只是為了理解:解釋器模式 方法預設輸入為合法的字元串,沒有考慮演算法優化、效率或者不合法字元串的異常情況
   *
   * @param sInput 合法的加減法字元串 比如 1+2+3
   */
  public static AbstractExpression getAST(String sInput) {
    //接收字元串參數形如 "1+2-3"
    //將字元串解析到List valueAndSymbolList中存放
    List<String> valueAndSymbolList = new ArrayList<>();
    //先按照 加法符號 + 拆分為數組,以每個元素為單位使用 +連接起來存入List
    //如果以+ 分割內部還有減法符號 - 內部以減法符號- 分割
    //最終的元素的形式為 1,+,2,-,3
    String[] splitByPlus = sInput.split("\\+");
    for (int i = 0; i < splitByPlus.length; i++) {
      if (splitByPlus[i].indexOf("-") < 0) {
        valueAndSymbolList.add(splitByPlus[i]);
      } else {
        String[] splitByMinus = splitByPlus[i].split("\\-");
        for (int j = 0; j < splitByMinus.length; j++) {
          valueAndSymbolList.add(splitByMinus[j]);
          if (j != splitByMinus.length - 1) {
            valueAndSymbolList.add("-");
          }
        }
      }
      if (i != splitByPlus.length - 1) {
        valueAndSymbolList.add("+");
      }
    }
    //經過前面處理元素的形式為 1,+,2,-,3
    //轉換為抽象語法樹的形式
    AbstractExpression leftExpression = null;
    AbstractExpression rightExpression = null;
    int k = 0;
    while (k < valueAndSymbolList.size()) {
      if (!valueAndSymbolList.get(k).equals("+") && !valueAndSymbolList.get(k).equals("-")) {
        rightExpression = new Value(Integer.parseInt(valueAndSymbolList.get(k)));
        if (leftExpression == null) {
          leftExpression = rightExpression;
        }
      }
      k++;
      if (k < valueAndSymbolList.size()) {
        rightExpression = new Value(Integer.parseInt(valueAndSymbolList.get(k + 1)));
        if (valueAndSymbolList.get(k).equals("+")) {
          leftExpression = new Plus(leftExpression, rightExpression);
        } else if (valueAndSymbolList.get(k).equals("-")) {
          leftExpression = new Minus(leftExpression, rightExpression);
        }
        k++;
      }
    }
    return leftExpression;
  }
  通過上面的這個方法,我們就可以直接解析字元串了 image_5c108d55_271c

總結

解釋器模式是用於解析一種“語言”,對於使用頻率較高的,模式、公式化的場景,可以考慮使用解釋器模式。 比如正則表達式,將“匹配”這一語法,定義為一種語言 瀏覽器對於HTML的解析,將HTML文檔的結構定義為一種語言 我們上面的例子,將加減運算規則定義為一種語言 所以,使用解釋器模式要註意“高頻”“公式”“格式”這幾個關鍵詞   解釋器模式將語法規則抽象的表述為類 解釋器模式為自定義語言的設計和實現提供了一種解決方案,它用於定義一組文法規則並通過這組文法規則來解釋語言中的句子。   解釋器模式非常容易擴展,如果增加新的運算符,比如乘除,只需要增加新的非終結符表達式即可 改變和擴展語言的規則非常靈活 非終結符表達式是由終結符表達式構成,基本上需要藉助於嵌套,遞歸,所以代碼本身一般比較簡單 像我們上面那樣, Plus和Minus 的代碼差異很小   如果語言比較複雜,顯然,就會需要定義大量的類來處理 解釋器模式中大量的使用了遞歸嵌套,所以說它的性能是很有問題的,如果你的系統是性能敏感的,你就更要慎重的使用   據說解釋器模式在實際的系統開發中使用得非常少,另外也有一些開源工具 Expression4J、MESP(Math Expression String Parser)、Jep 所以不要自己實現   另外還需要註意的是,從我們上面的示例代碼中可以看得出來 解釋器模式的重點在於AbstractExpression、TerminalExpression、NoneTerminalExpression的提取抽象 也就是對於文法規則的映射轉換 而至於如何轉換為抽象語法樹,這是客戶端的責任 我們的示例中可以通過new不斷地嵌套創建expression對象 也可以通過方法解析抽象語法樹,都可以根據實際場景處理 簡言之,解釋器模式不關註抽象語法樹的創建,僅僅關註解析處理   所以個人看法: 但凡你的問題場景可以抽象為一種語言,也就是有規則、公式,有套路就可以使用解釋器模式 不過如果有替代方法,能不用就不用 如果非要用,你也不要自己寫 原文地址:解釋器模式 Interpreter 行為型 設計模式(十九)  
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 來自:https://blog.csdn.net/m0_37068028/article/details/72898154 侵刪 來自:https://segmentfault.com/a/1190000010378259 侵刪 第一種: //date.jsexport function forma ...
  • tofixed方法 四捨五入 toFixed() 方法可把 Number 四捨五入為指定小數位數的數字。例如將數據Num保留2位小數,則表示為:toFixed(Num);但是其四捨五入的規則與數學中的規則不同,使用的是銀行家舍入規則,銀行家舍入:所謂銀行家舍入法,其實質是一種四舍六入五取偶(又稱四舍 ...
  • 示例html代碼: 示例html代碼: 示例html代碼: 示例html代碼: 示例html代碼: <div id="test"> <span style="color:red">test1</span> test2 </div> 獲得id為test的DOM對象,下麵就不一一獲取了。 var tes ...
  • 面向對象的語言有一個標誌,那就是它們都有類的概念,而通過類可以創建任意多個具有相同屬性和方法的對象。 理解對象 創建自定義對象的最簡單的方法就是創建一個Object的實例,然後再為它添加屬性和方法。例如: 同樣上面的例子可以通過對象字面量語法寫成如下: 屬性類型 ECMAScript中有兩種屬性:數 ...
  • Steps步驟條組件源碼: steps.vue step.vue ...
  • 1、Node.js簡介 簡單的說 Node.js 就是運行在服務端的 JavaScript。Node.js 是一個基於 Chrome V8 引擎的 JavaScript 運行環境。Node.js 使用了一個事件驅動、非阻塞式 I/O 的模型,使其輕量又高效。Node.js 的包管理器 npm,是全球 ...
  • 如何學好面向對象? 面向對象雖然只有三個特性,封裝、繼承、多態,但是真正面向對象卻是說的容易做起來困難。但是,還是有一定的規則可尋的, 要學好面向對象,必須掌握設計模式 。 什麼是設計模式? 設計模式(Design pattern):是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結 ...
  • 本文介紹了Java驗證的幾種機制,包括JPA驗證,Bean驗證,實體監聽器和事務監聽器。通過介紹希望可以在Java項目整體的驗證方面提供一些參考。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...