javascript設計模式——模板方法模式

来源:http://www.cnblogs.com/xiaohuochai/archive/2017/12/15/8039243.html
-Advertisement-
Play Games

[1]定義 [2]咖啡與茶 [3]創建子類 [4]抽象類 [5]鉤子方法 ...


前面的話

  在javascript開發中用到繼承的場景其實並不是很多,很多時候喜歡用mix-in的方式給對象擴展屬性。但這不代表繼承在javascript里沒有用武之地,雖然沒有真正的類和繼承機制,但可以通過原型prototype來變相地實現繼承。本文將詳細介紹一種基於繼承的設計模式——模板方法(TemplateMethod)模式

 

定義

  模板方法模式是一種只需使用繼承就可以實現的非常簡單的模式。模板方法模式由兩部分結構組成,第一部分是抽象父類,第二部分是具體的實現子類。通常在抽象父類中封裝了子類的演算法框架,包括實現一些公共方法以及封裝子類中所有方法的執行順序。子類通過繼承這個抽象類,也繼承了整個演算法結構,並且可以選擇重寫父類的方法

  假如有一些平行的子類,各個子類之間有一些相同的行為,也有一些不同的行為。如果相同和不同的行為都混合在各個子類的實現中,說明這些相同的行為會在各個子類中重覆出現。但實際上,相同的行為可以被搬移到另外一個單一的地方,模板方法模式就是為解決這個問題而生的。在模板方法模式中,子類實現中的相同部分被上移到父類中,而將不同的部分留待子類來實現。這也很好地體現了泛化的思想

 

咖啡與茶

  咖啡與茶是一個經典的例子,經常用來講解模板方法模式,這個例子的原型來自《HeadFirst設計模式》。下麵用javascript來實現這個例子

  首先,先來泡一杯咖啡,如果沒有什麼太個性化的需求,泡咖啡的步驟通常如下:1、把水煮沸;2、用沸水沖泡咖啡;3、把咖啡倒進杯子;4、加糖和牛奶

  通過下麵這段代碼,可以得到一杯香濃的咖啡

var Coffee = function(){};
Coffee.prototype.boilWater = function(){
    console.log( '把水煮沸' );
};
Coffee.prototype.brewCoffeeGriends = function(){
    console.log( '用沸水沖泡咖啡' );
};
Coffee.prototype.pourInCup = function(){
    console.log( '把咖啡倒進杯子' );
};
Coffee.prototype.addSugarAndMilk = function(){
    console.log( '加糖和牛奶' );
};
Coffee.prototype.init = function(){
    this.boilWater();
    this.brewCoffeeGriends();
    this.pourInCup();
    this.addSugarAndMilk();
};
var coffee = new Coffee();
coffee.init();

  接下來,開始準備茶,泡茶的步驟跟泡咖啡的步驟相差並不大:1、把水煮沸;2、用沸水浸泡茶葉 3、把茶水倒進杯子;4、加檸檬

  下麵用一段代碼來實現泡茶的步驟:

var Tea = function(){};
Tea.prototype.boilWater = function(){
    console.log( '把水煮沸' );
};
Tea.prototype.steepTeaBag = function(){
    console.log( '用沸水浸泡茶葉' );
};
Tea.prototype.pourInCup = function(){
    console.log( '把茶水倒進杯子' );
};
Tea.prototype.addLemon = function(){
    console.log( '加檸檬' );
};
Tea.prototype.init = function(){
    this.boilWater();
    this.steepTeaBag();
    this.pourInCup();
    this.addLemon();
};
var tea = new Tea();
tea.init();

  現在分別泡好了一杯咖啡和一壺茶,經過思考和比較,發現咖啡和茶的沖泡過程是大同小異的

designPattern2

  泡咖啡和泡茶主要有以下不同點:

  1、原料不同。一個是咖啡,一個是茶,但可以把它們都抽象為“飲料”

  2、泡的方式不同。咖啡是沖泡,而茶葉是浸泡,可以把它們都抽象為“泡”

  3、加入的調料不同。一個是糖和牛奶,一個是檸檬,但可以把它們都抽象為“調料”

  經過抽象之後,不管是泡咖啡還是泡茶,都能整理為下麵四步:

  1、把水煮沸

  2、用沸水沖泡飲料

  3、把飲料倒進杯子

  4、加調料

  所以,不管是沖泡還是浸泡,都能給它一個新的方法名稱,比如說brew()。同理,不管是加糖和牛奶,還是加檸檬,都可以稱之為addCondiments()

  現在可以創建一個抽象父類來表示泡一杯飲料的整個過程。不論是Coffee,還是Tea,都用Beverage來表示,代碼如下:

var Beverage = function(){};
Beverage.prototype.boilWater = function(){
    console.log( '把水煮沸' );
};
Beverage.prototype.brew = function(){}; // 空方法,應該由子類重寫
Beverage.prototype.pourInCup = function(){}; // 空方法,應該由子類重寫
Beverage.prototype.addCondiments = function(){}; // 空方法,應該由子類重寫
Beverage.prototype.init = function(){
    this.boilWater();
    this.brew();
    this.pourInCup();
    this.addCondiments();
};

 

創建子類

  現在創建一個Beverage類的對象沒有意義,因為世界上能喝的東西沒有一種真正叫“飲料”的,飲料在這裡還只是一個抽象的存在。接下來要創建咖啡類和茶類,並讓它們繼承飲料類:

var Coffee = function(){};
Coffee.prototype = new Beverage();

  接下來要重寫抽象父類中的一些方法,只有“把水煮沸”這個行為可以直接使用父類Beverage中的boilWater方法,其他方法都需要在Coffee子類中重寫,代碼如下:

Coffee.prototype.brew = function(){
    console.log( '用沸水沖泡咖啡' );
};
Coffee.prototype.pourInCup = function(){
    console.log( '把咖啡倒進杯子' );
};
Coffee.prototype.addCondiments = function(){
    console.log( '加糖和牛奶' );
};
var Coffee = new Coffee();
Coffee.init();

  至此Coffee類完成了,當調用coffee對象的init方法時,由於coffee對象和Coffee構造器的原型prototype上都沒有對應的init方法,所以該請求會順著原型鏈,被委托給Coffee的“父類”Beverage原型上的init方法。而Beverage.prototype.init方法中已經規定好了泡飲料的順序,所以能成功地泡出一杯咖啡

  接下來照葫蘆畫瓢,來創建Tea類:

var Tea = function(){};
Tea.prototype = new Beverage();
Tea.prototype.brew = function(){
    console.log( '用沸水浸泡茶葉' );
};
Tea.prototype.pourInCup = function(){
    console.log( '把茶倒進杯子' );
};
Tea.prototype.addCondiments = function(){
    console.log( '加檸檬' );
};
var tea = new Tea();
tea.init();

  本文討論的是模板方法模式,那麼在上面的例子中,到底誰才是所謂的模板方法呢?答案是Beverage.prototype.init。Beverage.prototype.init被稱為模板方法的原因是,該方法中封裝了子類的演算法框架,它作為一個演算法的模板,指導子類以何種順序去執行哪些方法。在Beverage.prototype.init方法中,演算法內的每一個步驟都清楚地展示在眼前

 

抽象類

  模板方法模式是一種嚴重依賴抽象類的設計模式。javascript在語言層面並沒有提供對抽象類的支持,也很難模擬抽象類的實現

  在Java中,類分為兩種,一種為具體類,另一種為抽象類。具體類可以被實例化,抽象類不能被實例化。要瞭解抽象類不能被實例化的原因,可以思考“飲料”這個抽象類

  想象這樣一個場景:口渴了去便利店想買一瓶飲料,不能直接跟店員說:“來一瓶飲料”。如果這樣說了,那麼店員接下來肯定會問:“要什麼飲料?”飲料只是一個抽象名詞,只有當真正明確了的飲料類型之後,才能得到一杯咖啡、茶或者可樂

  由於抽象類不能被實例化,如果有人編寫了一個抽象類,那麼這個抽象類一定是用來被某些具體類繼承的。抽象類表示一種契約。繼承了這個抽象類的所有子類都將擁有跟抽象類一致的介面方法,抽象類的主要作用就是為它的子類定義這些公共介面。如果在子類中刪掉了這些方法中的某一個,那麼將不能通過編譯器的檢查,這在某些場景下是非常有用的

  Beverage類的init方法里規定了沖泡一杯飲料的順序如下:

this.boilWater();    //把水煮沸
this.brew();    //用水泡原料
this.pourInCup();    //把原料倒進杯子
this.addCondiments();        //添加調料

  如果在Coffee子類中沒有實現對應的brew方法,那麼百分之百得不到一杯咖啡。既然父類規定了子類的方法和執行這些方法的順序,子類就應該擁有這些方法,並且提供正確的實現

  抽象方法被聲明在抽象類中,抽象方法並沒有具體的實現過程,是一些“啞”方法。比如Beverage類中的brew方法、pourInCup方法和addCondiments方法,都被聲明為抽象方法。當子類繼承了這個抽象類時,必須重寫父類的抽象方法

  除了抽象方法之外,如果每個子類中都有一些同樣的具體實現方法,那這些方法也可以選擇放在抽象類中,這可以節省代碼以達到復用的效果,這些方法叫作具體方法。當代碼需要改變時,只需要改動抽象類里的具體方法就可以了。比如飲料中的boilWater方法,假設沖泡所有的飲料之前,都要先把水煮沸,那自然可以把boilWater方法放在抽象類Beverage中

  下麵嘗試著把Coffee和Tea的例子換成Java代碼,這有助於理解抽象類的意義

//Java代碼
public abstract class Beverage{    //飲料抽象類
  final void init(){    //模板方法
    boilWater();
    brew();
    pourInCup();
    addCondiments();
  }

  void boilWater(){    //具體方法
    boilWaterSystem.out.println("把水煮沸");
  }

  abstract void brew();    //抽象方法brew
  abstract void addCondiments();        //抽象方法addCondiments
  abstract void pourInCup();    //抽象方法pourInCup
}

public class Coffee extends Beverage{    //Coffee類
  @Override
  void brew(){    //子類中重寫brew方法
    System.out.println("用沸水沖泡咖啡");
  }
  @Override
  void pourInCup(){    //子類中重寫pourInCup方法
    System.out.println("把咖啡倒進杯子");
  }
  @Override
  void addCondiments(){    //子類中重寫addCondiments方法
    System.out.println("加糖和牛奶");
  }
}

public class Tea extends Beverage{    //Tea類
  @Override
  voidbrew(){    //子類中重寫brew方法
    System.out.println("用沸水浸泡茶葉");
  }

  @Override
  voidpourInCup(){    //子類中重寫pourInCup方法
    System.out.println("把茶倒進杯子");
  }

  @Override
  voidaddCondiments(){    //子類中重寫addCondiments方法
    System.out.println("加檸檬");
  }
}

public class Test{
  private static void prepareRecipe(Beveragebeverage){
    beverage.init();
  }

  public static void main(Stringargs[]){
    Beverage coffee = new Coffee();    //創建coffee對象
    prepareRecipe(coffee);    //開始泡咖啡
    //把水煮沸
    //用沸水沖泡咖啡
    //把咖啡倒進杯子
    //加糖和牛奶
  Beverage tea = new Tea();    //創建tea對象
  prepareRecipe(tea);    //開始泡茶
    //把水煮沸
    //用沸水浸泡茶葉
    //把茶倒進杯子
    //加檸檬
  }
}

  javascript並沒有從語法層面提供對抽象類的支持。抽象類的第一個作用是隱藏對象的具體類型,由於javascript是一門“類型模糊”的語言,所以隱藏對象的類型在javascript中並不重要。另一方面,在javascript中使用原型繼承來模擬傳統的類式繼承時,並沒有編譯器幫助進行任何形式的檢查,沒有辦法保證子類會重寫父類中的“抽象方法”

  Beverage.prototype.init方法作為模板方法,已經規定了子類的演算法框架,代碼如下:

Beverage.prototype.init=function(){
  this.boilWater();
  this.brew();
  this.pourInCup();
  this.addCondiments();
};

  如果Coffee類或者Tea類忘記實現這4個方法中的一個呢?拿brew方法舉例,如果忘記編寫Coffee.prototype.brew方法,那麼當請求coffee對象的brew時,請求會順著原型鏈找到Beverage“父類”對應的Beverage.prototype.brew方法,而Beverage.prototype.brew方法到目前為止是一個空方法,這顯然是不符需要的

  在Java中編譯器會保證子類會重寫父類中的抽象方法,但在javascript中卻沒有進行這些檢查工作。在編寫代碼的時候得不到任何形式的警告,完全寄托於程式員的記憶力和自覺性是很危險的,特別是當使用模板方法模式這種完全依賴繼承而實現的設計模式時

  下麵提供兩種變通的解決方案:第1種方案是用鴨子類型來模擬介面檢查,以便確保子類中確實重寫了父類的方法。但模擬介面檢查會帶來不必要的複雜性,而且要求程式員主動進行這些介面檢查,這就要求在業務代碼中添加一些跟業務邏輯無關的代碼;第2種方案是讓Beverage.prototype.brew等方法直接拋出一個異常,如果因為粗心忘記編寫Coffee.prototype.brew方法,那麼至少會在程式運行時得到一個錯誤

Beverage.prototype.brew = function(){
    throw new Error( '子類必須重寫brew 方法' );
};
Beverage.prototype.pourInCup = function(){
    throw new Error( '子類必須重寫pourInCup 方法' );
};
Beverage.prototype.addCondiments = function(){
    throw new Error( '子類必須重寫addCondiments 方法' );
};

  第2種解決方案的優點是實現簡單,付出的額外代價很少;缺點是得到錯誤信息的時間點太靠後。一共有3次機會得到這個錯誤信息,第1次是在編寫代碼的時候,通過編譯器的檢查來得到錯誤信息;第2次是在創建對象的時候用鴨子類型來進行“介面檢查”;而目前不得不利用最後一次機會,在程式運行過程中才知道哪裡發生了錯誤

【使用場景】

  從大的方面來講,模板方法模式常被架構師用於搭建項目的框架,架構師定好了框架的骨架,程式員繼承框架的結構之後,負責往裡面填空

  在Web開發中能找到很多模板方法模式的適用場景,比如在構建一系列的UI組件,這些組件的構建過程一般如下所示:

  1、初始化一個div容器

  2、通過ajax請求拉取相應的數據;

  3、把數據渲染到div容器裡面,完成組件的構造;

  4、通知用戶組件渲染完畢

  任何組件的構建都遵循上面的4步,其中第(1)步和第(4)步是相同的。第(2)步不同的地方只是請求ajax的遠程地址,第(3)步不同的地方是渲染數據的方式。於是可以把這4個步驟都抽象到父類的模板方法裡面,父類中還可以順便提供第(1)步和第(4)步的具體實現。當子類繼承這個父類之後,會重寫模板方法裡面的第(2)步和第(3)步

 

鉤子方法

  通過模板方法模式,在父類中封裝了子類的演算法框架。這些演算法框架在正常狀態下是適用於大多數子類的,但如果有一些特別“個性”的子類呢?比如在飲料類Beverage中封裝了飲料的沖泡順序:

  1、把水煮沸

  2、用沸水沖泡飲料

  3、把飲料倒進杯子

  4、加調料

  這4個沖泡飲料的步驟適用於咖啡和茶,在飲料店裡,根據這4個步驟製作出來的咖啡和茶,一直順利地提供給絕大部分客人享用。但有一些客人喝咖啡是不加調料(糖和牛奶)的。既然Beverage作為父類,已經規定好了沖泡飲料的4個步驟,那麼有什麼辦法可以讓子類不受這個約束呢?

  鉤子方法(hook)可以用來解決這個問題,放置鉤子是隔離變化的一種常見手段。在父類中容易變化的地方放置鉤子,鉤子可以有一個預設的實現,究竟要不要“掛鉤”,這由子類自行決定。鉤子方法的返回結果決定了模板方法後面部分的執行步驟,也就是程式接下來的走向,這樣一來,程式就擁有了變化的可能

  在下麵這個例子里,把掛鉤的名字定為customerWantsCondiments,接下來將掛鉤放入Beverage類,看看如何得到一杯不需要糖和牛奶的咖啡,代碼如下:

var Beverage = function(){};
Beverage.prototype.boilWater = function(){
    console.log( '把水煮沸' );
};
Beverage.prototype.brew = function(){
    throw new Error( '子類必須重寫brew 方法' );
};
Beverage.prototype.pourInCup = function(){
    throw new Error( '子類必須重寫pourInCup 方法' );
};
Beverage.prototype.addCondiments = function(){
    throw new Error( '子類必須重寫addCondiments 方法' );
};
Beverage.prototype.customerWantsCondiments = function(){
    return true; // 預設需要調料
};
Beverage.prototype.init = function(){
    this.boilWater();
    this.brew();
    this.pourInCup();
    if ( this.customerWantsCondiments() ){ // 如果掛鉤返回true,則需要調料
        this.addCondiments();
    }
};

var CoffeeWithHook = function(){};
CoffeeWithHook.prototype = new Beverage();
CoffeeWithHook.prototype.brew = function(){
    console.log( '用沸水沖泡咖啡' );
};
CoffeeWithHook.prototype.pourInCup = function(){
    console.log( '把咖啡倒進杯子' );
};
CoffeeWithHook.prototype.addCondiments = function(){
    console.log( '加糖和牛奶' );
};
CoffeeWithHook.prototype.customerWantsCondiments = function(){
    return window.confirm( '請問需要調料嗎?' );
};
var coffeeWithHook = new CoffeeWithHook();
coffeeWithHook.init();

【好萊塢原則】

  下麵引入一個新的設計原則——“好萊塢原則”。好萊塢無疑是演員的天堂,但好萊塢也有很多找不到工作的新人演員,許多新人演員在好萊塢把簡歷遞給演藝公司之後就只有回家等待電話。有時候該演員等得不耐煩了,給演藝公司打電話詢問情況,演藝公司往往這樣回答:“不要來找我,我會給你打電話。”

  在設計中,這樣的規則就稱為好萊塢原則。在這一原則的指導下,允許底層組件將自己掛鉤到高層組件中,而高層組件會決定什麼時候、以何種方式去使用這些底層組件,高層組件對待底層組件的方式,跟演藝公司對待新人演員一樣,都是“別調用我們,我們會調用你”

  模板方法模式是好萊塢原則的一個典型使用場景,它與好萊塢原則的聯繫非常明顯,用模板方法模式編寫一個程式時,就意味著子類放棄了對自己的控制權,而是改為父類通知子類,哪些方法應該在什麼時候被調用。作為子類,只負責提供一些設計上的細節。除此之外,好萊塢原則還常常應用於其他模式和場景,例如發佈——訂閱模式和回調函數

  在發佈—訂閱模式中,發佈者會把消息推送給訂閱者,這取代了原先不斷去fetch消息的形式。例如假設乘坐計程車去一個不瞭解的地方,除了每過5秒鐘就問司機“是否到達目的地”之外,還可以在車上美美地睡上一覺,然後跟司機說好,等目的地到了就叫醒你。這也相當於好萊塢原則中提到的“別調用我們,我們會調用你”

  在ajax非同步請求中,由於不知道請求返回的具體時間,而通過輪詢去判斷是否返回數據,這顯然是不理智的行為。所以通常會把接下來的操作放在回調函數中,傳入發起ajax非同步請求的函數。當數據返回之後,這個回調函數才被執行,這也是好萊塢原則的一種體現。把需要執行的操作封裝在回調函數里,然後把主動權交給另外一個函數。至於回調函數什麼時候被執行,則是另外一個函數控制的

【基於繼承】

  模板方法模式是基於繼承的一種設計模式,父類封裝了子類的演算法框架和方法的執行順序,子類繼承父類之後,父類通知子類執行這些方法,好萊塢原則很好地詮釋了這種設計技巧,即高層組件調用底層組件

  模板方法模式是為數不多的基於繼承的設計模式,但javascript語言實際上沒有提供真正的類式繼承,繼承是通過對象與對象之間的委托來實現的。也就是說,雖然在形式上借鑒了提供類式繼承的語言,但本文的模板方法模式並不十分正宗。而且在javascript這般靈活的語言中,實現這樣一個例子,是否真的需要繼承這種重武器呢?在好萊塢原則的指導之下,下麵這段代碼可以達到和繼承一樣的效果

var Beverage = function( param ){
    var boilWater = function(){
        console.log( '把水煮沸' );
    };
    var brew = param.brew || function(){
        throw new Error( '必須傳遞brew 方法' );
    };
    var pourInCup = param.pourInCup || function(){
        throw new Error( '必須傳遞pourInCup 方法' );
    };
    var addCondiments = param.addCondiments || function(){
        throw new Error( '必須傳遞addCondiments 方法' );
    };
    var F = function(){};
    F.prototype.init = function(){
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    };
    return F;
};
var Coffee = Beverage({
    brew: function(){
        console.log( '用沸水沖泡咖啡' );
    },
    pourInCup: function(){
        console.log( '把咖啡倒進杯子' );
    },
    addCondiments: function(){
        console.log( '加糖和牛奶' );
    }
});

var Tea = Beverage({
    brew: function(){
        console.log( '用沸水浸泡茶葉' );
    },
    pourInCup: function(){
        console.log( '把茶倒進杯子' );
    },
    addCondiments: function(){
        console.log( '加檸檬' );
    }
});
var coffee = new Coffee();
coffee.init();
var tea = new Tea();
tea.init();

  在這段代碼中,把brew、pourInCup、addCondiments這些方法依次傳入Beverage函數,Beverage函數被調用之後返回構造器F。F類中包含了“模板方法”F.prototype.init。跟繼承得到的效果一樣,該“模板方法”里依然封裝了飲料子類的演算法框架

  模板方法模式是一種典型的通過封裝變化提高系統擴展性的設計模式。在傳統的面向對象語言中,一個運用了模板方法模式的程式中,子類的方法種類和執行順序都是不變的,所以把這部分邏輯抽象到父類的模板方法裡面。而子類的方法具體怎麼實現則是可變的,於是把這部分變化的邏輯封裝到子類中。通過增加新的子類,便能給系統增加新的功能,並不需要改動抽象父類以及其他子類,這也是符合開放——封閉原則的。但在javascript中,很多時候都不需要依樣畫瓢地去實現一個模版方法模式,高階函數是更好的選擇

 


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

-Advertisement-
Play Games
更多相關文章
  • 至此已完成NodeJsInputFileSysten模塊的講解,下一步就是實際實用的模塊: 掛載到compiler對象上的輸入模塊其實是帶有緩存的輸入模塊,源碼整理如下(用ES6的class重寫): 這裡的核心是利用Storage來生成一個緩存容器,緩存對應的讀操作。 有兩個需要註意的地方。 一個是 ...
  • 在cachedInput、output、watch三大文件系統中,output非常簡單,沒有必要講,其餘兩個模塊依賴於input模塊,而input主要是引用了graceful-fs的部分API,所以這節來講講graceful-fs。 上一節整理的源碼如下: 內容包含: 1、工具方法 2、patch引 ...
  • [1]享元模式初識 [2]文件上傳 [3]適用性 [4]對象池 ...
  • 相同: 1、兩者都能隱藏元素。 不同: 1、display:none 不占頁面空間,visiblity:hidden 占據原先頁面空間。 這裡必須說明的是,元素不占頁面空間後,取該元素或其內部元素的寬高值永遠是0。如果想隱藏又想取到寬高值,那就得用visiblity:hidden。 2、displa ...
  • 最近遇到這方面知識,就自己找了一些資料,進行了一些總結 什麼是分段傳輸? 當引入了一個http首部。這個首部標識了實體採用chunked編碼傳輸,chunked編碼可以將實體分塊兒進行傳輸,並且chunked編碼的每一塊內容都會自標識長度。這給了web開發者一個啟示,如果需要多個數據,而多個數據均返 ...
  • string對象 1.str.length () 輸出字元串長度 2.charAt()返回指定位置的字元串 返回指定位置的字元串 參數為 下標 0 開始 如果參數不在0-str.length範圍內則返回空字元串 3.concat()鏈接字元串 鏈接字元串 用於鏈接兩個或者多個字元串 4.indexO ...
  • 前言 這幾天在看《javascript高級程式設計》,看到執行環境和作用域鏈的時候,就有些模糊了。書中還是講的不夠具體。通過上網查資料,特來總結,以備回顧和修正。 目錄: EC(執行環境或者執行上下文,Execution Context) ECS(執行環境棧Execution Context Sta ...
  • 作為第一篇技術乾貨,來寫哪個方面的內容,我著實考慮了很久。 經過了整整30秒的深思熟慮,我決定就我第一次發現新大陸一樣的內容,來進行一次討論。 1 <div id='ancestor'> 2 <div id="father"> 3 <div id="son"> 4 </div> 5 </div> 6 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...