javascript設計模式——策略模式

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

[1]獎金計算 [2]策略模式 [3]緩動動畫 [4]表單校驗 [5]總結 ...


前面的話

  在程式設計中,常常遇到類似的情況,要實現某一個功能有多種方案可以選擇。比如一個壓縮文件的程式,既可以選擇zip演算法,也可以選擇gzip演算法。這些演算法靈活多樣,而且可以隨意互相替換。這種解決方案就是本文將要介紹的策略模式。策略模式是指定義一系列的演算法,把它們一個個封裝起來,並且使它們可以相互替換

 

獎金計算

  策略模式有著廣泛的應用。以年終獎的計算為例進行介紹。很多公司的年終獎是根據員工的工資基數和年底績效情況來發放的。例如,績效為S的人年終獎有4倍工資,績效為A的人年終獎有3倍工資,而績效為B的人年終獎是2倍工資

  下麵是一個名為calculateBonus的函數來計算每個人的獎金數額。該函數接收兩個參數:員工的工資數額和他的績效考核等級

var calculateBonus = function( performanceLevel, salary ){
    if ( performanceLevel === 'S' ){
        return salary * 4;
    }
    if ( performanceLevel === 'A' ){
        return salary * 3;
    }
    if ( performanceLevel === 'B' ){
        return salary * 2;
    }
};
calculateBonus( 'B', 20000 ); // 輸出:40000
calculateBonus( 'S', 6000 ); // 輸出:24000

  這段代碼十分簡單,但是存在著顯而易見的缺點:該函數比較龐大,包含了很多if-else語句,這些語句需要覆蓋所有的邏輯分支;該函數缺乏彈性,如果增加了一種新的績效等級C,或者想把績效S的獎金繫數改為5,必須深入calculateBonus函數的內部實現,違反開放封閉原則;演算法復用性差,如果在程式其他地方需要重用這些計算獎金的演算法,只能選擇複製粘貼

  下麵使用組合函數來重構代碼,把各種演算法封裝到一個個的小函數裡面,這些小函數有著良好的命名,可以一目瞭然地知道它對應著哪種演算法,它們也可以被覆用在程式的其他地方

var performanceS = function( salary ){
    return salary * 4;
};
var performanceA = function( salary ){
    return salary * 3;
};
var performanceB = function( salary ){
    return salary * 2;
};
var calculateBonus = function( performanceLevel, salary ){
    if ( performanceLevel === 'S' ){
        return performanceS( salary );
    }
    if ( performanceLevel === 'A' ){
        return performanceA( salary );
    }
    if ( performanceLevel === 'B' ){
        return performanceB( salary );
    }
};
calculateBonus( 'A' , 10000 ); // 輸出:30000

  目前,程式得到了一定的改善,但這種改善非常有限,依然沒有解決最重要的問題:calculateBonus函數有可能越來越龐大,而且在系統變化的時候缺乏彈性

  策略模式指的是定義一系列的演算法,把它們一個個封裝起來。策略模式的目的就是將演算法的使用與演算法的實現分離開來

  在這個例子里,演算法的使用方式是不變的,都是根據某個演算法取得計算後的獎金數額。而演算法的實現是各異和變化的,每種績效對應著不同的計算規則

  一個基於策略模式的程式至少由兩部分組成。第一個部分是一組策略類,策略類封裝了具體的演算法,並負責具體的計算過程。第二個部分是環境類Context,Context接受客戶的請求,隨後把請求委托給某一個策略類。要做到這點,說明Context中要維持對某個策略對象的引用

  下麵用策略模式來重構上面的代碼

//定義策略類
var performanceS = function(){};
performanceS.prototype.calculate = function( salary ){
    return salary * 4;
};
var performanceA = function(){};
performanceA.prototype.calculate = function( salary ){
    return salary * 3;
};
var performanceB = function(){};
performanceB.prototype.calculate = function( salary ){
    return salary * 2;
};
//定義獎金類Bonus:
var Bonus = function(){
    this.salary = null; // 原始工資
    this.strategy = null; // 績效等級對應的策略對象
};
Bonus.prototype.setSalary = function( salary ){
    this.salary = salary; // 設置員工的原始工資
};
Bonus.prototype.setStrategy = function( strategy ){
    this.strategy = strategy; // 設置員工績效等級對應的策略對象
};
Bonus.prototype.getBonus = function(){ // 取得獎金數額
    return this.strategy.calculate( this.salary ); // 把計算獎金的操作委托給對應的策略對象
};
var bonus = new Bonus();
bonus.setSalary( 10000 );

bonus.setStrategy( new performanceS() ); // 設置策略對象
console.log( bonus.getBonus() ); // 輸出:40000
bonus.setStrategy( new performanceA() ); // 設置策略對象
console.log( bonus.getBonus() ); // 輸出:30000

 

策略模式

  上面的代碼中,讓strategy對象從各個策略類中創建而來,這是模擬一些傳統面向對象語言的實現。實際上在javascript語言中,函數也是對象,所以更簡單和直接的做法是把strategy直接定義為函數

var strategies = {
    "S": function( salary ){
        return salary * 4;
    },
    "A": function( salary ){
        return salary * 3;
    },
    "B": function( salary ){
        return salary * 2;

    }
};

  同樣,Context也沒有必要必須用Bonus類來表示,用calculateBonus函數充當Context來接受用戶的請求。經過改造,代碼的結構變得更加簡潔

var calculateBonus = function( level, salary ){
    return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S', 20000 ) ); // 輸出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 輸出:30000

 

緩動動畫

  通過使用策略模式重構代碼,消除了原程式中大片的條件分支語句。所有跟計算獎金有關的邏輯不再放在Context 中,而是分佈在各個策略對象中。Context並沒有計算獎金的能力,而是把這個職責委托給了某個策略對象。每個策略對象負責的演算法已被各自封裝在對象內部。當對這些策略對象發出“計算獎金”的請求時,它們會返回各自不同的計算結果,這正是對象多態性的體現,也是“它們可以相互替換”的目的。替換 Context 中當前保存的策略對象,便能執行不同的演算法來得到想要的結果

  有一段時間網頁游戲非常流行,HTML5版本的游戲可以達到不遜於Flash游戲的效果。用javascript實現動畫效果的原理跟動畫片的製作一樣,動畫片是把一些差距不大的原畫以較快的幀數播放,來達到視覺上的動畫效果。在javascript中,可以通過連續改變元素的某個CSS屬性,比如left、top、background-position來實現動畫效果

  目標是編寫一個動畫類和一些緩動演算法,讓小球以各種各樣的緩動效果在頁面中運動。 現在來分析實現這個程式的思路。在運動開始之前,需要提前記錄一些有用的信息,至少包括以下信息:動畫開始時,小球所在的原始位置;小球移動的目標位置;動畫開始時的準確時間點;小球運動持續的時間

  隨後,用setInterval創建一個定時器,定時器每隔19ms迴圈一次。在定時器的每一幀里,把動畫已消耗的時間、小球原始位置、小球目標位置和動畫持續的總時間等信息傳入緩動演算法。該演算法會通過這幾個參數,計算出小球當前應該所在的位置。最後再更新該div對應的CSS屬性,小球就能夠順利地運動起來了

  在實現完整的功能之前,先瞭解一些常見的緩動演算法,這些演算法最初來自Flash,但可以非常方便地移植到其他語言中。這些演算法都接受4個參數,這4個參數的含義分別是動畫已消耗的時間、小球原始位置、小球目標位置、動畫持續的總時間,返回的值則是動畫元素應該處在的當前位置。代碼如下:

var tween = {
    linear: function( t, b, c, d ){
        return c*t/d + b;
    },
    easeIn: function( t, b, c, d ){
        return c * ( t /= d ) * t + b;
    },
    strongEaseIn: function(t, b, c, d){
        return c * ( t /= d ) * t * t * t * t + b;
    },
    strongEaseOut: function(t, b, c, d){
        return c * ( ( t = t / d - 1) * t * t * t * t + 1 ) + b;
    },
    sineaseIn: function( t, b, c, d ){
        return c * ( t /= d) * t * t + b;
    },
    sineaseOut: function(t,b,c,d){
        return c * ( ( t = t / d - 1) * t * t + 1 ) + b;
    }
};

  接下來,開始編寫完整的代碼,首先在頁面中放置一個 div

<div style="position:absolute;background:blue" id="div">我是div</div>

  接下來定義Animate類,Animate的構造函數接受一個參數:即將運動起來的dom節點

var Animate = function( dom ){
    this.dom = dom; // 進行運動的dom 節點
    this.startTime = 0; // 動畫開始時間
    this.startPos = 0; // 動畫開始時,dom 節點的位置,即dom 的初始位置
    this.endPos = 0; // 動畫結束時,dom 節點的位置,即dom 的目標位置
    this.propertyName = null; // dom 節點需要被改變的css 屬性名
    this.easing = null; // 緩動演算法
    this.duration = null; // 動畫持續時間
};

  接下來Animate.prototype.start方法負責啟動這個動畫,在動畫被啟動的瞬間,要記錄一些信息,供緩動演算法在以後計算小球當前位置的時候使用。在記錄完這些信息之後,此方法還要負責啟動定時器。代碼如下:

Animate.prototype.start = function( propertyName, endPos, duration, easing ){
    this.startTime = +new Date; // 動畫啟動時間
    this.startPos = this.dom.getBoundingClientRect()[ propertyName ]; // dom 節點初始位置
    this.propertyName = propertyName; // dom 節點需要被改變的CSS 屬性名
    this.endPos = endPos; // dom 節點目標位置
    this.duration = duration; // 動畫持續事件
    this.easing = tween[ easing ]; // 緩動演算法
    var self = this;
    var timeId = setInterval(function(){ // 啟動定時器,開始執行動畫
        if ( self.step() === false ){ // 如果動畫已結束,則清除定時器
            clearInterval( timeId );
        }
    }, 19 );
};

  Animate.prototype.start方法接受以下4個參數

propertyName:要改變的 CSS 屬性名,比如'left''top',分別表示左右移動和上下移動。
endPos: 小球運動的目標位置。
duration: 動畫持續時間。
easing: 緩動演算法

  再接下來是Animate.prototype.step方法,該方法代表小球運動的每一幀要做的事情。在此處,這個方法負責計算小球的當前位置和調用更新CSS屬性值的方法Animate.prototype.update。代碼如下:

Animate.prototype.step = function(){
    var t = +new Date; // 取得當前時間
    if ( t >= this.startTime + this.duration ){ // (1)
        this.update( this.endPos ); // 更新小球的CSS 屬性值
        return false;
    }
    var pos = this.easing( t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration );
    // pos 為小球當前位置
    this.update( pos ); // 更新小球的CSS 屬性值
};

  在這段代碼中,如果當前時間大於動畫開始時間加上動畫持續時間之和,說明動畫已經結束,此時要修正小球的位置。因為在這一幀開始之後,小球的位置已經接近了目標位置,但很可能不完全等於目標位置。此時要主動修正小球的當前位置為最終的目標位置。此外讓Animate.prototype.step方法返回false,可以通知Animate.prototype.start方法清除定時器

  最後是負責更新小球CSS屬性值的Animate.prototype.update方法

Animate.prototype.update = function( pos ){
    this.dom.style[ this.propertyName ] = pos + 'px';
};

  下麵來進行一些小小的測試:

var div = document.getElementById( 'div' );
var animate = new Animate( div );
animate.start( 'left', 500, 1000, 'strongEaseOut' );

  通過這段代碼,可以看到小球按照期望以各種各樣的緩動演算法在頁面中運動

 

表單校驗

  在一個Web項目中,註冊、登錄、修改用戶信息等功能的實現都離不開提交表單。在將用戶輸入的數據交給後臺之前,常常要做一些客戶端力所能及的校驗工作,比如註冊的時候需要校驗是否填寫了用戶名,密碼的長度是否符合規定,等等。這樣可以避免因為提交不合法數據而帶來的不必要網路開銷。假設正在編寫一個註冊的頁面,在點擊註冊按鈕之前,有如下幾條校驗邏輯:1、用戶名不能為空;2、密碼長度不能少於6位;3、手機號碼必須符合格式

  現在編寫表單校驗的第一個版本

<form action="http://xx.com/register" id="registerForm" method="post">
    請輸入用戶名:<input type="text" name="userName"/ >
    請輸入密碼:<input type="text" name="password"/ >
    請輸入手機號碼:<input type="text" name="phoneNumber"/ >
    <button>提交</button>
</form>
<script>
    var registerForm = document.getElementById( 'registerForm' );
    registerForm.onsubmit = function(){
        if ( registerForm.userName.value === '' ){
            alert ( '用戶名不能為空' );
            return false;
        }
        if ( registerForm.password.value.length < 6 ){
            alert ( '密碼長度不能少於6 位' );
            return false;
        }
        if ( !/(^1[3|5|8][0-9]{9}$)/.test( registerForm.phoneNumber.value ) ){
            alert ( '手機號碼格式不正確' );
            return false;
        }
    }
</script>

  這是一種很常見的代碼編寫方式,它的缺點跟計算獎金的最初版本一模一樣:registerForm.onsubmit函數比較龐大,包含了很多if-else語句,這些語句需要覆蓋所有的校驗規則;registerForm.onsubmit函數缺乏彈性,如果增加了一種新的校驗規則,或者想把密碼的長度校驗從6改成8,都必須深入registerForm.onsubmit函數的內部實現,違反開放封閉原則;演算法的復用性差,如果在程式中增加了另外一個表單,這個表單也需要進行一些類似的校驗,那很可能將這些校驗邏輯複製得漫天遍野

  下麵用策略模式來重構表單校驗的代碼,很顯然第一步要把這些校驗邏輯都封裝成策略對象

var strategies = {
    isNonEmpty: function( value, errorMsg ){ // 不為空
        if ( value === '' ){
            return errorMsg ;
        }
    },
    minLength: function( value, length, errorMsg ){ // 限制最小長度
        if ( value.length < length ){
            return errorMsg;
        }
    },
    isMobile: function( value, errorMsg ){ // 手機號碼格式
        if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){
            return errorMsg;
        }
    }
};

  接下來實現Validator類。Validator類在這裡作為Context,負責接收用戶的請求並委托給strategy對象

var validataFunc = function(){
    var validator = new Validator(); // 創建一個validator 對象
    /***************添加一些校驗規則****************/
    validator.add( registerForm.userName, 'isNonEmpty', '用戶名不能為空' );
    validator.add( registerForm.password, 'minLength:6', '密碼長度不能少於6 位' );
    validator.add( registerForm.phoneNumber, 'isMobile', '手機號碼格式不正確' );
    var errorMsg = validator.start(); // 獲得校驗結果
    return errorMsg; // 返回校驗結果
}

var registerForm = document.getElementById( 'registerForm' );
registerForm.onsubmit = function(){
    var errorMsg = validataFunc(); // 如果errorMsg 有確切的返回值,說明未通過校驗
    if ( errorMsg ){
        alert ( errorMsg );
        return false; // 阻止表單提交
    }
};

  先創建了一個validator對象,然後通過validator.add方法,往validator對象中添加一些校驗規則。validator.add方法接受3個參數,以下麵這句代碼說明:

validator.add(registerForm.password,'minLength:6','密碼長度不能少於6位');

  registerForm.password為參與校驗的input輸入框;'minLength:6'是一個以冒號隔開的字元串。冒號前面的minLength代表客戶挑選的strategy對象,冒號後面的數字6表示在校驗過程中所必需的一些參數;'minLength:6'表示校驗registerForm.password這個文本輸入框的value最小長度為6。如果這個字元串中不包含冒號,說明校驗過程中不需要額外的參數信息,比如'isNonEmpty';第3個參數是當校驗未通過時返回的錯誤信息

  往validator對象里添加完一系列的校驗規則之後,會調用validator.start()方法來啟動校驗。如果validator.start()返回了一個確切的errorMsg字元串當作返回值,說明該次校驗沒有通過,此時需讓registerForm.onsubmit方法返回false來阻止表單的提交。

  最後是 Validator 類的實現:

var Validator = function(){
    this.cache = []; // 保存校驗規則
};

Validator.prototype.add = function( dom, rule, errorMsg ){
    var ary = rule.split( ':' ); // 把strategy 和參數分開
    this.cache.push(function(){ // 把校驗的步驟用空函數包裝起來,並且放入cache
        var strategy = ary.shift(); // 用戶挑選的strategy
        ary.unshift( dom.value ); // 把input 的value 添加進參數列表
        ary.push( errorMsg ); // 把errorMsg 添加進參數列表
        return strategies[ strategy ].apply( dom, ary );
    });
};

Validator.prototype.start = function(){
    for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
        var msg = validatorFunc(); // 開始校驗,並取得校驗後的返回信息
        if ( msg ){ // 如果有確切的返回值,說明校驗沒有通過
            return msg;
        }
    }
};

  使用策略模式重構代碼之後,僅僅通過”配置“的方式就可以完成一個表單的校驗, 這些校驗規則也可以復用在程式的任何地方,還能作為插件的形式,方便地被移植到其他項 目中。在修改某個校驗規則的時候,只需要編寫或者改寫少量的代碼。比如想將用戶名輸入框的校驗規則改成用戶名不能少於10 個字元。可以看到,這時候的修改是毫不費力的。代碼如下:

validator.add( registerForm.userName, 'isNonEmpty', '用戶名不能為空' );
// 改成:
validator.add( registerForm.userName, 'minLength:10', '用戶名長度不能小於 10 位' );

  目前表單校驗實現留有一點小遺憾:一 個文本輸入框只能對應一種校驗規則,比如,用戶名輸入框只能校驗輸入是否為空:

validator.add( registerForm.userName, 'isNonEmpty', '用戶名不能為空' );

  如果既想校驗它是否為空,又想校驗它輸入文本的長度不小於 10 呢?期望以這樣的形式進行校驗:

validator.add( registerForm.userName, [{ strategy: 'isNonEmpty', errorMsg: '用戶名不能為空'}, {
  strategy: 'minLength:6',
  errorMsg: '用戶名長度不能小於 10 位'
}] );

  下麵提供的代碼可用於一個文本輸入框對應多種校驗規則:

<form action="http:// xxx.com/register" id="registerForm" method="post">
    請輸入用戶名:<input type="text" name="userName"/ >
    請輸入密碼:<input type="text" name="password"/ >
    請輸入手機號碼:<input type="text" name="phoneNumber"/ >
    <button>提交</button>
</form>
<script>
    /***********************策略對象**************************/
    var strategies = {
        isNonEmpty: function( value, errorMsg ){
            if ( value === '' ){
                return errorMsg;
            }
        },
        minLength: function( value, length, errorMsg ){
            if ( value.length < length ){
                return errorMsg;
            }
        },
        isMobile: function( value, errorMsg ){
            if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){
                return errorMsg;
            }
        }
    };
    /***********************Validator 類**************************/
    var Validator = function(){
        this.cache = [];
    };
    Validator.prototype.add = function( dom, rules ){
        var self = this;
        for ( var i = 0, rule; rule = rules[ i++ ]; ){
            (function( rule ){
                var strategyAry = rule.strategy.split( ':' );
                var errorMsg = rule.errorMsg;
                self.cache.push(function(){
                    var strategy = strategyAry.shift();
                    strategyAry.unshift( dom.value );
                    strategyAry.push( errorMsg );
                    return strategies[ strategy ].apply( dom, strategyAry );
                });
            })( rule )
        }
    };
    Validator.prototype.start = function(){
        for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
            var errorMsg = validatorFunc();
            if ( errorMsg ){
                return errorMsg;
            }
        }
    };
    /***********************客戶調用代碼**************************/
    var registerForm = document.getElementById( 'registerForm' );
    var validataFunc = function(){
        var validator = new Validator();
        validator.add( registerForm.userName, [{
            strategy: 'isNonEmpty',
            errorMsg: '用戶名不能為空'
        }, {
            strategy: 'minLength:6',
            errorMsg: '用戶名長度不能小於10 位'
        }]);
        validator.add( registerForm.password, [{
            strategy: 'minLength:6',
            errorMsg: '密碼長度不能小於6 位'
        }]);
        var errorMsg = validator.start();
        return errorMsg;
    }
    registerForm.onsubmit = function(){
        var errorMsg = validataFunc();
        if ( errorMsg ){
            alert ( errorMsg );
            return false;
        }

    };
</script>

 

總結

  策略模式是一種常用且有效的設計模式,它利用組合、委托和多態等技術和思想,可以有效地避免多重條件選擇語句。它提供了對開放—封閉原則的完美支持,將演算法封裝在獨立的strategy中,使得它們易於切換,易於理解,易於擴展。策略模式中的演算法也可以復用在系統的其他地方,從而避免許多重覆的複製粘貼工作。.在策略模式中利用組合和委托來讓Context擁有執行演算法的能力,這也是繼承的一種更輕便的替代方案

  當然,策略模式也有一些缺點,但這些缺點並不嚴重。首先,使用策略模式會在程式中增加許多策略類或者策略對象,但實際上這比把它們負責的邏輯堆砌在Context中要好。其次,要使用策略模式,必須瞭解所有的strategy,必須瞭解各個strategy之間的不同點,這樣才能選擇一個合適的strategy

 


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

-Advertisement-
Play Games
更多相關文章
  • 在日常運維工作中,對於mysql資料庫的備份是至關重要的!資料庫對於網站的重要性使得我們對mysql數據的管理不容有失!然後,是人總難免會犯錯誤,說不定哪天大腦短路了來個誤操作把資料庫給刪除了,怎麼辦??? 下麵,就mysql資料庫誤刪除後的恢復方案進行說明。 一、工作場景(1)MySQL資料庫每晚 ...
  • 執行結果: 第二段sql 執行結果: 以上結果表明: exec(@sqlstr) 其中@sqlstr中如果包含變數的運算,是將變數轉換為varchar後再exec操作 ...
  • iOS提供3種不同的定位途徑: 1,WiFi定位,通過查詢一個WiFi路由器的地理位置信息,比較省電;iPhone,iPod touch和iPad都可以採用; 2,蜂窩式行動電話基站定位,通過移動運營商基站定位,只有iPhone,3G版本的iPod touch 和iPad可以採用。 3,GPS衛星定 ...
  • 導入後gradle building 一直到跑,卡住了,一般是gradle沒有下載,又下不下來的原因。 去 http://services.gradle.org/distributions/ 下載 5.6 需要導出的工程的gradle gradle-2.10-all, gradle-2.14.1-a ...
  • 在ios7中有一種扁平風格的控制項叫做分段選擇控制項UISegmentedControl,控制項分為一排,橫放著幾個被簡單線條隔開的按鈕,每次點擊只能選擇其中一個按鈕,他類似於tabbar但是又稍微有點區別,新版的qq手機客戶端就用到了這種控制項。 但是在android中並沒有現成的控制項可用,不過andro ...
  • 經過上一期的破解教程,相信大家跟我一樣都是對破解是初入門,我們破解的目的是什麼? 賺錢嗎?百度上一大堆破解版的應用,破解的人有賺到錢嗎?實實在在的說,其實也是方便自己而已。 玩個游戲,感覺過不去了,來個破解,無限金幣什麼的,通關輕輕鬆松是不?我們也在破解當中賺錢了不是?省去了買金幣的錢 感覺廢話說多 ...
  • 一、JDK配置 Android是基於Java進行開發的,首先需要在電腦上配置JDK(Java Development Kit)。在http://www.androiddevtools.cn/下載對應系統版本的JDK安裝包。 在Windows系統下雙擊下載的安裝包,一路下一步進行安裝(預設安裝路徑即可 ...
  • SpannableString其實和String一樣,都是一種字元串類型,SpannableString可以直接作為TextView的顯示文本,不同的是SpannableString可以通過使用其方法setSpan方法實現字元串各種形式風格的顯示,重要的是可以指定設置的區間,也就是為字元串指定下標區 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...