標籤: 南投搬家費用

  • Express4.x之中間件與路由詳解及源碼分析,Express4.x之API:express,Express4.x之API:express

    Express4.x之中間件與路由詳解及源碼分析,Express4.x之API:express,Express4.x之API:express

    • Application.use()
    • Application.router()
    • express核心源碼模擬

     一、express.use()

    1.1app.use([path,] callback [, callback …])

    通過語法結構可以看到Application.use()參數分別有以下幾種情形:

    app.use(function(){...}); //給全局添加一个中間件
    app.use(path,function(){...}); //給指定路由path添加一个中間件
    app.use(function(){...}, function(){...}, ...); //給全局添加n个中間件
    app.use(path,function(){...},function(){...}, ...); //給指定路由path添加n个中間件

    關於path最簡單也是最常用的就是字符串類型(例:‘/abcd’);除了字符串Express還提供了模板和正則格式(例:’/abc?d‘, ‘/ab+cd‘, ‘/ab\*cd‘, ‘/a(bc)?d‘, ‘/\/abc|\/xyz/‘);除了單個的字符串和模板還可以將多個path作為一個數組的元素,然後將這個數組作為use的path,這樣就可以同時給多個路由添加中間件,詳細內容可以參考官方文檔:https://www.expressjs.com.cn/4x/api.html#path-examples。

    關於callbakc多個或單个中間件程序這已經再語法結構中直觀的體現出來了,這裏重點來看看回調函數的參數:

    app.use(function(req,res,next){...}); //必須提供的參數 
    app.use(function(err,req,res,next){...}); //錯誤中間件需要在最前面添加一個錯誤參數

    關於中間件的簡單應用:

    let express = require('./express');
    let app = express();
    app.use('/',function(req,res,next){
        console.log("我是一個全局中間件");
        next(); //每个中間件的最末尾必須調用next
    });
    
    app.use('/',function(err,req,res,next){
        console.log("我是一個全局錯誤中間,當發生錯誤是調用")    
        console.error(err.stack);
        res.status(500).send('服務出錯誤了!');
        //由於這個錯誤處理直接響應了客戶端,可以不再調用next,當然後面還需要處理一些業務的話也是可以調用next的
    });

    1.2簡單的模擬Express源碼實現Appliction.use()以及各個請求方法的響應註冊方法(這裏個源碼模擬路由概念還比較模糊,所以使用請求方法的響應註冊API,而沒有使用路由描述):

     1 //文件結構
     2 express
     3     index.js
     4 //源碼模擬實現
     5 let http = require("http");
     6 let url = require('url');
     7 function createApplication(){
     8     //app是一個監聽函數
     9     let app = (req,res) =>{
    10         //取出每一個層
    11         //1.獲取請求的方法
    12         let m = req.method.toLowerCase();
    13         let {pathname} = url.parse(req.url,true);
    14 
    15         //通過next方法進行迭代
    16         let index = 0;
    17         function next(err){
    18             //如果routes迭代完成還沒有找到,說明路徑不存在
    19             if(index === app.routes.length) return res.end(`Cannot ${m} ${pathname}`);
    20             let {method, path, handler} = app.routes[index++];//每次調用next就應該取下一個layer
    21             if(err){
    22                 if(handler.length === 4){
    23                     handler(err,req,res,next);
    24                 }else{
    25                     next(err);
    26                 }
    27             }else{
    28                 if(method === 'middle'){ //處理中間件
    29                     if(path === '/' || path === pathname || pathname.startsWith(path+'/')){
    30                         handler(req,res,next);
    31                     }else{
    32                         next();//如果這个中間件沒有匹配到,繼續通過next迭代路由容器routes
    33                     }
    34                 }else{ //處理路由
    35                     if( (method === m || method ==='all') && (path === pathname || path === '*')){ //匹配請求方法和請求路徑(接口)
    36                         handler(req,res);//匹配成功后執行的Callback
    37                     }else{
    38                         next();
    39                     }
    40                 }
    41             }
    42         }
    43         next();
    44     }
    45     app.routes = [];//路由容器
    46     app.use = function(path,handler){
    47         if(typeof handler !== 'function'){
    48             handler = path;
    49             path = '/';
    50         }
    51         let layer = {
    52             method:'middle', //method是middle就表示它是一个中間件
    53             path,
    54             handler
    55         }
    56         app.routes.push(layer);
    57     }
    58     app.all = function(path,handler){
    59         let layer = {
    60             method:'all',
    61             path,
    62             handler
    63         }
    64         app.routes.push(layer);
    65     }
    66     console.log(http.METHODS);
    67     http.METHODS.forEach(method =>{
    68         method = method.toLocaleLowerCase();
    69         app[method] = function (path,handler){//批量生成各個請求方法的路由註冊方法
    70             let layer = {
    71                 method,
    72                 path,
    73                 handler
    74             }
    75             app.routes.push(layer);
    76         }
    77     });
    78     //內置中間件,給req擴展path、qury屬性
    79     app.use(function(req,res,next){
    80         let {pathname,query} = url.parse(req.url,true);
    81         let hostname = req.headers['host'].split(':')[0];
    82         req.path = pathname;
    83         req.query = query;
    84         req.hostname = hostname;
    85         next();
    86     });
    87     //通過app.listen調用http.createServer()掛在app(),啟動express服務
    88     app.listen = function(){
    89         let server = http.createServer(app);
    90         server.listen(...arguments);
    91     }
    92     return app;
    93 }
    94 module.exports = createApplication;

    View Code

    測試模擬實現的Express:

     1 let express = require('./express');
     2 
     3 let app = express();
     4 
     5 app.use('/',function(req,res,next){
     6     console.log("我是一個全局中間件");
     7     next();
     8 });
     9 app.use('/user',function(req,res,next){
    10     console.log("我是user接口的中間件");
    11     next();
    12 });
    13 app.get('/name',function(req,res){
    14     console.log(req.path,req.query,req.hostname);
    15     res.end('zfpx');
    16 });
    17 app.post('/name',function(req,res){
    18     res.end('post name');
    19 });
    20 
    21 
    22 app.all("*",function(req,res){
    23     res.end('all');
    24 });
    25 
    26 app.use(function(err,req,res,next){
    27     console.log(err);
    28     next();
    29 });
    30 
    31 app.listen(12306);

    View Code

    在windows系統下測試請求:

     

     

    關於源碼的構建詳細內容可以參考這個視頻教程:app.use()模擬構建視頻教程,前面就已經說明過這個模式實現僅僅是從表面的業務邏輯,雖然有一點底層的雛形,但與源碼還是相差甚遠,這一部分也僅僅只是想幫助理解Express採用最簡單的方式表現出來。

    1.3如果你看過上面的源碼或自己也實現過,就會發現Express關於中間件的添加方式除了app.use()還有app.all()及app.METHOD()。在模擬源碼中我並未就use和all的差異做處理,都是採用了請求路徑絕對等於path,這種方式是all的特性,use的path實際表示為請求路徑的開頭:

    app.use(path,callback):path表示請求路徑的開頭部分。

    app.all(path,callback):paht表示完全等於請求路徑。

    app.METHOD(path,callback):並不是真的有METHOD這個方法,而是指HTTP請求方法,實際上表示的是app.get()、app.post()、app.put()等方法,而有時候我們會將這些方法說成用來註冊路由,這是因為路由註冊的確使用這些方法,但同時這些方法也是可以用作中間的添加,這在前一篇博客中的功能解析中就有說明(Express4.x之API:express),詳細見過後面的路由解析就會更加明了。

     二、express.router()

    2.1在實例化一個Application時會實例化一個express.router()實例並被添加到app._router屬性上,實際上這個app使用的use、all、METHOD時都是在底層調用了該Router實例上對應的方法,比如看下面這些示例:

     1 let express = require("express");
     2 let app = express();
     3 
     4 app._router.use(function(req,res,next){
     5     console.log("--app.router--");
     6     next();
     7 });
     8 
     9 app._router.post("/csJSON",function(req,res,next){
    10     res.writeHead(200);
    11     res.write(JSON.stringify(req.body));
    12     res.end();
    13 });
    14 
    15 app.listen(12306);

    上面示例中的app._router.use、app._router.post分別同等與app.use、app.post,這裏到這裏也就說明了上一篇博客中的路由與Application的關係Express4.x之API:express。

    2.2Express中的Router除了為Express.Application提供路由功能以外,Express也將它作為一個獨立的路由工具分離了出來,也就是說Router自身可以獨立作為一個應用,如果我們在實際應用中有相關業務有類似Express.Application的路由需求,可以直接實例化一個Router使用,應用的方式如下:

    let express = require('/express');
    let router = express.Router();
    //這部分可以詳細參考官方文檔有詳細的介紹

    2.3由於這篇博客主要是分析Express的中間件及路由的底層邏輯,所以就不在這裏詳細介紹某個模塊的應用,如果有時間我再寫一篇關於Router模塊的應用,這裏我直接上一份模擬Express路由的代碼以供參考:

    文件結構:

    express //根路徑
        index.js //express主入口文件
        application.js //express應用構造模塊
        router //路由路徑
            index.js //路由主入口文件
            layer.js //構造層的模塊
            route.js //子路由模塊

    Express路由系統的邏輯結構圖:

    模擬代碼(express核心源碼模擬):

    1 //express主入口文件
    2 let Application = require('./application.js');
    3 
    4 function createApplication(){
    5     return new Application();
    6 }
    7 
    8 module.exports = createApplication;

    index.js //express主入口文件

     1 //用來創建應用app
     2 let http = require('http');
     3 let url = require('url');
     4 
     5 //導入路由系統模塊
     6 let Router = require('./router');
     7 
     8 const methods = http.METHODS;
     9 
    10 //Application ---- express的應用系統
    11 function Application(){
    12     //創建一個內置的路由系統
    13     this._router = new Router();
    14 }
    15 
    16 //app.get ---- 實現路由註冊業務
    17 // Application.prototype.get = function(path,...handlers){
    18 //     this._router.get(path,'use',handlers);
    19 // }
    20 
    21 methods.forEach(method => {
    22     method = method.toLocaleLowerCase();
    23     Application.prototype[method] = function(path,...handlers){
    24         this._router[method](path,handlers);
    25     }
    26 });
    27 
    28 //app.use ---- 實現中間件註冊業務
    29 //這裏模擬處理三種參數模式:
    30 // -- 1個回調函數:callback
    31 // -- 多個回調函數:[callback,] callback [,callback...]
    32 // -- 指定路由的中間件:[path,] callback [,callback...]
    33 // -- 注意源碼中可以處理這三種參數形式還可以處理上面數據的數組形式,以及其他Application(直接將其他app上的中間件添加到當前應用上)
    34 Application.prototype.use = function(fn){
    35     let path = '/';
    36     let fns = [];
    37     let arg = [].slice.call(arguments);
    38     if(typeof fn !== 'function' && arg.length >= 2){
    39         if(typeof arg[0] !== 'string'){
    40             fns = arg;
    41         }else{
    42             path = arg[0];
    43             fns = arg.slice(1);
    44         }
    45     }else{
    46         fns = arg;
    47     }
    48     this._router.use(path,'use',fns);
    49 }
    50 
    51 Application.prototype.all = function(fn){
    52     let path = '/';
    53     let fns = [];
    54     let arg = [].slice.call(arguments);
    55     if(typeof fn !== 'function' && arg.length >= 2){
    56         if(typeof arg[0] !== 'string'){
    57             fns = arg;
    58         }else{
    59             path = arg[0];
    60             fns = arg.slice(1);
    61         }
    62       }else{
    63         fns = arg;
    64     }
    65     this._router.use(path,'all',fns);
    66 }
    67 
    68 //將http的listen方法封裝到Application的原型上
    69 Application.prototype.listen = function(){
    70     let server = http.createServer((req,res)=>{
    71         //done 用於當路由無任何可匹配項時調用的處理函數
    72         function done(){
    73             res.end(`Cannot ${req.url} ${req.method}`);
    74         }
    75         this._router.handle(req,res,done); //調用路由系統的handle方法處理請求
    76     });
    77     server.listen(...arguments);
    78 };
    79 
    80 module.exports = Application;

    application.js //express應用構造模塊

     1 //express路由系統
     2 const Layer = require('./layer.js');
     3 const Route = require('./route.js');
     4 
     5 const http = require('http');
     6 const methods = http.METHODS;
     7 
     8 const url = require('url');
     9 
    10 
    11 //路由對象構造函數
    12 function Router(){
    13     this.stack = [];
    14 }
    15 
    16 //router.route ---- 用於創建子路由對象route與主路由上層(layer)的關係
    17 //並將主路由上的層緩存到路由對象的stack容器中,該層建立路徑與子路由處理請求的關係
    18 Router.prototype.route = function(path){
    19     let route = new Route();
    20     let layer = new Layer(path,route.dispatch.bind(route));
    21     this.stack.push(layer);
    22     return route;
    23 }
    24 
    25 //router.get ---- 實現路由註冊
    26 //實際上這個方法調用router.route方法分別創建一個主路由系統層、一個子路由系統,並建立兩者之間的關係,詳細見Router.prototype.route
    27 //然後獲取子路由系統對象,並將回調函數和請求方法註冊在這個子路由系統上
    28 
    29 
    30 // Router.prototype.get = function(path,handlers){
    31 //     let route = this.route(path);
    32 //     route.get(handlers);
    33 // }
    34 
    35 methods.forEach(method =>{ 
    36     method = method.toLocaleLowerCase();
    37     //注意下面這個方法會出現內存泄漏問題,有待改進
    38     Router.prototype[method] = function(path, handlers){
    39         let route = this.route(path);
    40         route[method](handlers);
    41     }
    42 });
    43 
    44 //router.use ---- 實現中間件註冊(按照路由開頭的路徑匹配,即相對路由匹配)
    45 Router.prototype.use = function(path,routerType,fns){
    46     let router = this;
    47     fns.forEach(function(fn){
    48         let layer = new Layer(path,fn);
    49         layer.middle = true; //標記這個層為相對路由中間件
    50         layer.routerType = routerType;
    51         router.stack.push(layer);
    52     });
    53 }
    54 
    55 //調用路由處理請求
    56 Router.prototype.handle = function(req,res,out){
    57     let {pathname} = url.parse(req.url);
    58     let index = 0;
    59     let next = () => {
    60         if(index >= this.stack.length) return out();
    61         let layer = this.stack[index++];
    62         if(layer.middle && (layer.path === '/' || pathname === layer.path || pathname.startsWith(layer.path + '/'))){
    63             //處理中間件
    64             if(layer.routerType === 'use'){
    65                 layer.handle_request(req,res,next);
    66             }else if(layer.routerType === 'all' && layer.path === pathname){
    67                 layer.handle_request(req,res,next);
    68             }else{
    69                 next();
    70             }
    71         }else if(layer.match(pathname)){
    72             //處理響應--更準確的說是處理具體請求方法上的中間件或響應
    73             layer.handle_request(req,res,next);
    74         }else{
    75             next();
    76         }
    77     }
    78     next();
    79 }
    80 
    81 module.exports = Router;

    index.js //路由主入口文件

     1 //Layer的構造函數
     2 function Layer(path,handler){
     3     this.path = path; //當前層的路徑
     4     this.handler = handler;  //當前層的回調函數
     5 }
     6 
     7 //判斷請求方法與當前層的方法是否一致
     8 Layer.prototype.match = function(pathname){
     9     return this.path === pathname;
    10 }
    11 
    12 //調用當前層的回調函數handler
    13 Layer.prototype.handle_request = function(req,res,next){
    14     this.handler(req,res,next);
    15 }
    16 
    17 module.exports = Layer;

    layer.js //構造層的模塊

     1 //Layer的構造函數
     2 function Layer(path,handler){
     3     this.path = path; //當前層的路徑
     4     this.handler = handler;  //當前層的回調函數
     5 }
     6 
     7 //判斷請求方法與當前層的方法是否一致
     8 Layer.prototype.match = function(pathname){
     9     return this.path === pathname;
    10 }
    11 
    12 //調用當前層的回調函數handler
    13 Layer.prototype.handle_request = function(req,res,next){
    14     this.handler(req,res,next);
    15 }
    16 
    17 module.exports = Layer;

    route.js //子路由模塊

    測試代碼:

     1 let express = require('./express');
     2 let app = express();
     3 
     4 
     5 app.use('/name',function(req,res,next){
     6     console.log('use1');
     7     next();
     8 });
     9 app.use(function(req,res,next){
    10     console.log('use2-1');
    11     next();
    12 },function(req,res,next){
    13     console.log('use2-2');
    14     next();
    15 });
    16 
    17 app.all('/name',function(req,res,next){
    18     console.log('all-1');
    19     next();
    20 });
    21 app.all('/name/app',function(req,res,next){
    22     console.log('all-2');
    23     next();
    24 });
    25 
    26 app.get('/name/app',function(req,res){
    27     res.end(req.url);
    28 });
    29 // console.log(app._router.stack);
    30 app.listen(12306);

    View Code

    測試結果:

     

     

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • Dart Memo for Android Developers

    Dart Memo for Android Developers

    Dart語言一些語法特點和編程規範.

    本文適合: 日常使用Kotlin, 突然想寫個Flutter程序的Android程序員.

    Dart語言

    完整的請看A tour of the Dart language

    • 創建對象可以不用new. -> 並且規範不讓用new, lint會報錯.
    • 聲明變量可以用var, 也可以用具體類型如String. 不變量用final, 常量用const.
    • 沒有訪問修飾符, 用_來表示私有: 文件級別.
    • 字符串可以用單引號'.
    • 語句結尾要用;.
    • 創建數組可以用: var list = [1, 2, 3];.
    • assert()常用來斷定開發時不可能會出現的情況.
    • 空測試操作符: ??.
    • 過濾操作符: where.
    • 兩個點..表示鏈式調用.
    • dynamic說明類型未指定.
    • 除了throw異常, 還可以throw別的東西, 比如字符串.

    函數

    • 函數返回值在函數最開頭, 可以不標. -> 但是規範會建議標註返回值.
    bool isNoble(int atomicNumber) {
      return _nobleGases[atomicNumber] != null;
    }
    
    • =>箭頭符號, 用來簡化一句話的方法.
    bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
    

    構造函數

    • 構造函數{}表示帶名字, 參數可選, 若要必選加上@required.
    const Scrollbar({Key key, @required Widget child})
    
    • 構造函數名可以是ClassName或者ClassName.identifier.
    • 空構造函數體可以省略, 用;結尾就行:
    class Point {
      double x, y;
      Point(this.x, this.y);
    }
    

    這裡會初始化相應的變量, 也不用聲明具體的參數類型.

    • factory構造, 可以用來返回緩存實例, 或者返回類型的子類:
    factory Logger(String name) {
        return _cache.putIfAbsent(name, () => Logger._internal(name));
    }
    

    異步代碼

    Future<String> lookUpVersion() async => '1.0.0';
    
    Future checkVersion() async {
      var version = await lookUpVersion();
      // Do something with version
    }
    

    編程規範類

    完整的規範在這裏: Effective Dart.

    有一些Good和Bad的舉例, 這裏僅列出比較常用的幾項.

    • 文件名要蛇形命名: lowercase_with_underscores. 類名: UpperCamelCase.
    • 對自己程序的文件, 兩種import都可以(package開頭或者相對路徑), 但是要保持一致.
    • Flutter程序嵌套比較多, 要用結尾的,來幫助格式化.

    本文緣由

    年初的時候學了一陣子Flutter, 寫了各種大小demo. 結果隔了兩個月之後, 突然心血來潮想寫個小東西, 打開Android Studio, 首先發現創建Flutter程序的按鈕都不見了. (估計是Android Studio4.0升級之後Flutter的插件沒跟上).

    接着用命令行創建了工程, 打開之後稍微整理了一下心情, 然後就….懵逼了.

    突然不知道如何下手.
    宏觀的東西還記得, 要用什麼package, 基本常用的幾個Widget都是啥, 但是微觀的, 忘了函數和數組都是咋定義的了.
    這種懵逼的狀態令我很憤怒, 果然是上年紀了嗎, 無縫切換個語言都不行.

    於是就想着還是寫個備忘錄吧.

    References

    • A tour of the Dart language
    • Effective Dart

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 保護全球最美星空 智利環團提告商業大樓「污染天空」

    摘錄自2020年9月28日奇摩新聞報導

    智利北部的阿他加馬沙漠曾獲選BBC全球十大最美暗夜星空,入夜後整片的星空美不勝收,吸引了各種追星者和天文學家,因此聚集大量觀星者的巨型望遠鏡,幾乎半數的世界天文觀測站都在這。但現在都市的擴張和發展伴隨的光污染使星星黯淡許多,甚至使一些關鍵地區的天空退化超過10%。

    智利環保機構表示,將提告用「人造冷光」污染天空的公司,當地政府也打算修法,若業者減少光污染將有特別優惠,希望利用合法的力量和新的保護措施讓天空保持黑暗。但目前收到起訴和修正的公司都還未回覆,其他公司也都還在審理中。

    污染治理
    國際新聞
    智利
    光害
    星空

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※回頭車貨運收費標準

  • 【設計模式】如何用組合替代繼承

    如果問面向對象的三大特性是什麼,多數人都能回答出來:封裝、繼承、多態。

    繼承 作為三大特性之一,近來卻越來越不推薦使用,更有極端的語言,直接語法中就不支持繼承,例如 Go。這又是為什麼呢?

    為什麼不推薦使用繼承?

    假設我們要設計一個關於鳥的類。

    我們將“鳥類”定義為一個抽象類 AbstractBird。所有更細分的鳥,比如麻雀、鴿子、烏鴉等,都繼承這個抽象類。

    大部分鳥都會飛,那我們可不可以在 AbstractBird 抽象類中,定義一個 Fly() 方法呢?

    答案是否定的。儘管大部分鳥都會飛,但也有特例,比如鴕鳥就不會飛。鴕鳥繼承具有 Fly() 方法的父類,那鴕鳥就具有“飛”這樣的行為,這顯然不符合我們對現實世界中事物的認識。

    解決方案一

    在鴕鳥這個子類中重寫 Fly() 方法,讓它拋出異常。

    public class AbstractBird
    {
        public virtual void Fly()
        {
            Console.WriteLine("I'm flying.");
        }
    }
    
    //鴕鳥
    public class Ostrich : AbstractBird
    {
        public override void Fly()
        {
            throw new NotImplementedException("I can't fly.");
        }
    }
    

    這種設計思路雖然可以解決問題,但不夠優美。因為除了鴕鳥之外,不會飛的鳥還有很多,比如企鵝。對於這些不會飛的鳥來說,我們都需要重寫 Fly() 方法,拋出異常。

    這違背了迪米特法則(也叫最少知識原則),暴露不該暴露的接口給外部,增加了類使用過程中被誤用的概率。

    解決方案二

    通過 AbstractBird 類派生出兩個更加細分的抽象類:會飛的鳥類 AbstractFlyableBird 和不會飛的鳥類 AbstractUnFlyableBird,讓麻雀、烏鴉這些會飛的鳥都繼承 AbstractFlyableBird,讓鴕鳥、企鵝這些不會飛的鳥,都繼承 AbstractUnFlyableBird 類。

    此時,繼承關係變成了三層,還行得通。

    如果要再添加一個游泳 Swim() 的方法,那情況就複雜了,要分為四中情況:

    • 會飛會游泳
    • 會飛不會游泳
    • 不會飛會游泳
    • 不會飛不會游泳

    如果再有其他行為加入,抽象類的數量就會幾何級數增長。

    我們要搞清楚某個類具有哪些方法、屬性,必須閱讀父類的代碼、父類的父類的代碼……一直追溯到最頂層父類的代碼。

    使用組合

    針對“會飛”這樣一個行為特性,我們可以定義一個 Flyable 接口,只讓會飛的鳥去實現這個接口。針對會游泳,定義一個 Swimable 接口,會叫定義一個 Tweetable 接口。

    public interface Flyable
    {
        void Fly();
    }
    
    public interface Swimable
    {
        void Swim();
    }
    
    public interface Tweetable
    {
        void Tweet();
    }
    
    //麻雀
    public class Sparrow : Flyable, Tweetable
    {
        public void Fly() => Console.WriteLine("I am flying.");
    
        public void Tweet() => Console.WriteLine("!@#$%^&*……");
    }
    
    //企鵝
    public class Penguin : Swimable, Tweetable
    {
        public void Swim() => Console.WriteLine("I am swimming.");
    
        public void Tweet() => Console.WriteLine("!@#$%^&*……");
    }
    

    麻雀和企鵝都會叫,Tweet 實現了兩遍,這是壞味道。我們可以用組合來消除這個壞味道。

    public interface Flyable
    {
        void Fly();
    }
    
    public interface Swimable
    {
        void Swim();
    }
    
    public interface Tweetable
    {
        void Tweet();
    }
    
    public class FlyAbility : Flyable
    {
        public void Fly() => Console.WriteLine("I am flying.");
    }
    
    public class SwimAbility : Swimable
    {
        public void Swim() => Console.WriteLine("I am swimming.");
    }
    
    public class TweetAbility : Tweetable
    {
        public void Tweet() => Console.WriteLine("!@#$%^&*……");
    }
    
    //麻雀
    public class Sparrow : Flyable, Tweetable
    {
        FlyAbility flyAbility = new FlyAbility();
        TweetAbility tweetAbility = new TweetAbility();
    
        public void Fly() => flyAbility.Fly();
    
        public void Tweet() => tweetAbility.Tweet();
    }
    
    //企鵝
    public class Penguin : Swimable, Tweetable
    {
        SwimAbility swimAbility = new SwimAbility();
        TweetAbility tweetAbility = new TweetAbility();
    
        public void Swim() => swimAbility.Swim();
    
        public void Tweet() => tweetAbility.Tweet();
    }
    

    雖然現在主流的思想都是多用組合少用繼承,但是從上面的例子可以看出,繼承改寫成組合意味着要做更細粒度的類的拆分,要定義更多的類和接口。類和接口的增多也就或多或少地增加代碼的複雜程度和維護成本。所以,在實際的項目開發中,我們還是要根據具體的情況,來具體選擇該用繼承還是組合。

    本文出自極客時間 王爭 老師的課程《設計模式之美》。原文示例為 java,因為我是做 C# 的,所以本文示例代碼我改成了 C# 。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 養生吧,程序員!

    由於寫作的原因,我認識了蠻多天南海北的朋友。空下來的時候,我就會主動找他們聊一聊,一呢,從他們那吸取成功的經驗,開拓一下眼界;二呢,讓自己的社交圈擴大一些,要知道,多一個朋友就多一條路;三呢,看看朋友們有沒有自己可以幫得上的忙;四呢,保持年輕的心態,不至於落伍。

    老讀者都知道了,我在三線城市洛陽,雖然幸福感很足,但整體社交的圈子是有限的,只通過互聯網,很難得到一些不便於公開的信息,和朋友們聊一聊,不僅能夠加深感情,還能夠讓自己和一線城市保持同步。

    昨天,我和一個非常優秀的年輕人聊天,他給我說,身體上出了一些狀況。他比我小七八歲,按理說身體素質倍棒才對,但大城市的生活壓力,讓他只能快馬加鞭。這就導致身體一直處於高負荷的運轉狀態,很容易出問題。

    提起程序員,總免不了和一些段子關聯上,比如說“要變強,必變禿”,再比如說:

    零基礎學編程→某編程語言入門→某編程語言進階→技術專家→頸椎病

    以前,我對這些段子總是一笑而過,但回想起朋友說的那些話,總免不了一陣心酸。就讓我來寫一篇“程序員養生指南”吧,希望能給讀者朋友們一些警醒或者說參考吧。

    心學鼻祖王陽明曾提出過一個觀點,叫做“知行合一”,我的理解就是,當你要做一件事情的話,你必須得對它有一定的認知,否則的話,你根本就不會去做這件事。

    舉個例子來說,如果你不知道學習很重要,哪怕你天資聰慧,你也不可能去學習,你會把心思花在其他地方。

    再比如說,如果你認知不到英語對於程序員的重要性,你永遠也擺脫不了對翻譯軟件的依賴症。

    對吧?如果在你的認知里,意識不到健康的重要性,那你對待身體的態度可就會大有不同。

    大二結束后,我要去蘇州參加培訓,臨走之前,同學們給我送行,結果我一下子喝大了。畢竟一起玩了兩年,又要去人生地不熟的地方,傷感在所難免,於是我把自己喝到了醫院。

    喝完酒感覺沒啥事,結果睡覺前嘔吐的厲害,感覺整個人就要死翹翹了。我的一個好兄弟看我情況不妙,背着我就跑,大晚上也沒啥車,幸好醫院就在我們學校旁邊,算是保住了一條小命。

    從那以後,我就特別討厭喝酒,儘管有的時候不得不喝一些,但都會盡量控制到不上頭的狀態。

    我身邊有不少人,總是把鍛煉掛在嘴上,聊起來感覺特別注重身體。但過一段時間再問他,“夥計,你健身卡辦了沒?”“最近忙,過两天就去辦,感覺身體有點撐不住了。”

    生存的壓力確實大,但如果身體跨了,生存的意義又是什麼呢?

    改變一個人的認知,最壞的方式就是徹底摧毀他之前的認知。極端的例子就是大病初愈,那時候,人會迫切地想要鍛煉身體。

    最好的方式,當然是聽得進去勸,把別人糟糕的經歷當做是自己前進的動力。認識到健康的重要性,然後一點一滴地去改善生活的習慣,最終達到知行合一的境界。

    我曾看過一本書,裏面提到一個觀點,養成習慣的最好方式就是,連續不間斷地做一件事,周期為 21 天。這件事最好是一件小事,比如說,起床后疊被子這件事,它一點也不難,很容易就能做到。

    但能不能堅持 21 天不間斷就需要一點點耐力了,21 天不算長,但絕對算得上是富有意義的考驗了,它對你培養飲食、作息、運動方面的良好行為習慣是一道開胃菜。

    接下來說說飲食吧,我覺得一定不要暴飲暴食,或者餓着肚子。每天的三餐一定要及時,哪怕你早上起晚了,一定也要記得吃點東西,可以準備一些麵包或者牛奶,不要餓着肚子挺到中午,那樣的話,對身體不好,另外,餓的時候,工作狀態也會大打折扣。

    現在天氣熱了,很多讀者的食慾都不會特彆強烈,尤其是到了中午的時候,可以選擇一些清淡的,比如說,我作為北方人,最喜歡的就是甜面片,伴着一點鹹菜吃,再喝點麵湯,感覺特別舒服。

    到了晚上,不要因為餓就要找場子,覺得必須得吃回來,然後睡覺的時候就感覺特別撐,很難入睡,反而影響了睡眠質量。

    我一般是十點半入睡,然後一覺睡到早上五六點。這個作息還是非常規律的,中午如果困的話,就再補個覺。最好不要超過半個小時,否則越睡越困,如果實在睡不着,就閉目養神會,就算是休息休息眼睛吧,畢竟做程序員的,每天對着電腦屏幕和手機屏幕,對眼睛的傷害還是蠻大的。

    有不少讀者都睡得特別晚,一方面是公司上下班時間上的問題,一方面就是好不容易有點自己的自由時間,舍不得睡覺,於是熬一會,打一把遊戲,就到了凌晨一兩點。

    我是怎麼知道的?通過留言的時間就知道了。讓我很心疼的一個事實是,有些留言的時間竟然是凌晨三四點。真的,看到這個時間點的留言,我都會勸讀者能早點睡就早點睡,不要熬夜。

    二十五六歲之前,熬個夜,很快就恢復了,但再往後,真的是非常難,我以前熬夜看英超,結果第二天整個人都處於懵逼的狀態,效率特別差,要兩三天才能恢復正常,索性後來就戒了。

    最後再說說鍛煉吧,年前我辦了一張健身卡,一周能堅持去兩到三次,但後來就中斷了。最近兩個月,我開始了騎自行車,感覺效果還不錯,瘦了七八斤的樣子,有一種返老還童的感覺,真的。

    五六點起來,騎一個小時左右,回到家不耽誤上班。我在朋友圈曬過兩張圖,老讀者應該看到了。清晨,空氣特別好,路上人也不多,車也不多,氣溫也剛剛好,耳邊再聽着周杰倫或者王力宏,感覺真的非常愜意。

    作為程序員,絕大部分時間都要坐在椅子上,再加上有些公司加班特別厲害,就導致久坐的時間特別長,於是頸椎病、肩周炎等等職業病隨着而來。

    除了及時地走動,還是要盡量騰出一些時間去鍛煉一下,哪怕走走路,跑幾步,對身體的調整都卓有成效,業績是公司的,身體是自己的。

    最後,就是堅持了。生活需要用心去對待,如果我們用心去愛她,她就會加倍地愛我們——養生吧,程序員!

    如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。

    本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

    我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • 又要到處浪又要省油,有這種好事?這5款車就行

    又要到處浪又要省油,有這種好事?這5款車就行

    以不到30萬的價格買到一輛混動的大7座SUV,還要什麼入門BBA。再來看看同樣換新顏、尺寸大一號的全新秦pro,新車搭載一套由BYD476ZQA型1。5T發動機、電動機和鋰電池組構成的插電式混動系統后,工信部申報綜合油耗僅為1升/百公里。

    前幾天正巧試駕了朋友剛提不久的雷克薩斯CT200h,除了非常不錯的操控感和車身動態表現之外,讓我印象最深刻的就是油耗,在一頓猛踩和駕駛模式來回切換之後,平均油耗也不過7L/百公里,我相信還有其他車主做出了更漂亮的數據。

    雖然當初一早就布局混動車型市場的CT200h並沒有因此而大賣,但發展到今天,隨着混動技術的不斷成熟和國內市場政策的風雲變幻,已經有越來越多的消費者被混動車型的諸多優勢所打動,比如省油、安靜、駕駛體驗等等。所以今天就給大家盤點一下,北京車展之後都有哪些混動車型是值得消費者密切關注的。

    1、卡羅拉/雷凌pHEV插電混動版

    卡羅拉和雷凌可以說是豐田的兩個“開掛”車型,從燃油版到HEV油電混動版(雙擎)從來都不愁賣,而此次北京車展上豐田終於兌現了兩年前的承諾,在國內正式發布了卡羅拉/雷凌pHEV插電混動版車型。

    這兩款新車可以說是含着金鑰匙出生的“富二代”,畢竟豐田多年以來經營的品牌形象、技術實力和市場口碑為這二者打下了堅實的銷量基礎,所以目測上市后月銷1萬輛是基本操作,想要入手的朋友們可要盯緊了。

    在外形上兩款新車與普通版的差別並不大,僅針對細節進行了微調,比如車頭和車尾部分都加入了藍色元素。此外,由於插電混動版搭載了更大容量的電池組,所以內部空間與普通版車型稍有差異。

    值得一提的是,這兩款車屬於中國特供車,意味着自帶銷量BUG,並且其搭載了1.8L自然吸氣發動機+電動機組,同時還配備容量為9-13kWh的鋰離子電池組,純電動條件下的續航里程可超過100km。

    這是神馬概念?起碼就目前我國市場上銷售的插電混動車型,大部分純電動續航里程都只在50-80km以內,並且考慮到緊湊級車推出pHEV版本的多是自主品牌車型,而它們的老對手軒逸、朗逸等還沒加入這個市場,所以此次亮相的卡羅拉和雷凌pHEV版本只要定價合適,銷量應該不成問題,別忘了它們還可獲得一部分政策補貼哦(雙擎版本沒有)。

    2、領克01 pHEV插電混動版

    說領克01 pHEV版之前必先提一提燃油版領克01,這款車型自去年11月上市以來便憑藉優秀的做工和沃爾沃技術的加持迅速圈粉,獲得市場的好評如潮。本以為領克會按部就班、穩紮穩打地推出新能源產品,不過眼看WEY氣勢洶洶地推出了p8之後,領克怕也是坐不太住了,於是便適時地推出了領克01 pHEV版。

    在外觀和內飾方面,該車和之前的燃油版車型基本相同,只是在前進氣格柵以及前翼子板處增加了藍色元素進行點綴,表明其新能源型的身份。而尾部右下側還貼有“01 pHEV”的字樣。

    動力方面,領克01 pHEV則搭載了1.5T發動機和電動機所組成的插電式混合動力系統,發動機最大凈功率約180馬力,動力電池為鎳鈷錳三元鋰離子電池,官方宣稱該車的百公里油耗僅為1.8L,同時其純電動模式下最大行駛里程為51km。如果想要入手朋友,其量產車型最快會在今年年內上市,所以準備好鈔票就是了。

    3、比亞迪唐DM/秦pro

    我們都知道,自從前奧迪設計師艾格入主比亞迪之後,旗下打頭陣車型瞬間秒變高富帥,就連之前被人嫌棄的“BYD”都多了一個洋氣的註釋–Build Your Dream!如果你還沒什麼畫面感的話,在此貢獻兩張圖讓大家感受一下:

    了解更多點擊視頻:

    全新一代唐从里裡外外都整了個遍,多了幾分國際范。首先在外觀上採用了家族最新的Dragon Face設計語言,整體大氣而又十分吸睛;

    內飾同樣話題性十足,其中最搶眼的部分當屬中控可90°旋轉的超級大屏,同時全液晶儀錶和藍色氛圍燈增加了車內的科技感。

    動力方面,其將搭載2.0T發動機與電動機組成的插電式混動系統,系統綜合最大功率超過500馬力,而高配車型百公里加速僅需4.5秒,咳咳划重點了–奔馳GLC43 AMG的成績為4.9秒。

    值得一提的是,新一代唐標配7座布局,其長寬高分別為4870/1940/1720mm,軸距為2820mm,相比上一代車型在軸距和寬度上有所增加,所以能帶來更寬敞的乘坐體驗。另外在此次北京車展,新一代唐公布預售價–補貼后25萬起步。港真!以不到30萬的價格買到一輛混動的大7座SUV,還要什麼入門BBA!

    再來看看同樣換新顏、尺寸大一號的全新秦pro,新車搭載一套由BYD476ZQA型1.5T發動機、電動機和鋰電池組構成的插電式混動系統后,工信部申報綜合油耗僅為1升/百公里!並且根據車尾標判斷,新車的官方百公里加速時間或為5.9秒,看來是充分體現了什麼叫“雨露均沾”了。

    4、廣汽謳歌CDX混動版

    每每提到謳歌這個品牌,總有兩道跨不過的坎,一個是跟長安LOGO抹不去的姻緣,另一個就是跟隔壁家的雷克薩斯比境遇,很顯然以上兩項都是招黑的。不過也必須承認,痛定思痛的謳歌如今似乎也有點重新步入正軌的意思了。

    在此次北京車展,廣汽謳歌就帶來了CDX的混動版本,並且從售價上看還是蠻有誠意的。新車共推出了三款車型,售價區間為29.98-35.28萬元,同時新車外觀顏色增加磨砂皓灰和混動版專屬的藍紫色車漆,內飾則增加了紅色內飾可選,而頂配版車型還將增配Acura Watch。

    畢竟謳歌也是“本田大法”的受益者,所以混動版車型相比同級別對手的較大優勢就體現在動力系統上。新車將搭載本田的i-MMD混合動力系統,其由一台2.0L的阿特金森循環自然吸氣發動機、由驅動電機和發電機組成的“電動CVT”直驅傳動機構、動力控制單元及鋰電池組等組成。

    這套系統可以根據負載和使用條件的不同,在純電動、混合動力和發動機直驅三種模式之間無縫切換。參數方面,2.0L發動機最大功率146馬力,混動系統綜合最大功率215馬力,而官方公布的綜合油耗為5.0L/百公里。所以三十萬買一台省油的豪華SUV,好像很不錯的樣子!

    5、雷克薩斯ES混動版

    此次北京車展雷克薩斯的展台只放了兩台車,沒錯!只有兩台!在大多數人看來這就是典型的“寒酸”,而在雷粉眼裡這就叫“傲嬌”,因為雷克薩斯選擇把ES混動版的全球首發放在了中國,雖說一直不願意國產,雖說看展的觀眾不一定get到這樣做的用心良苦,但足以說明其對中國市場的看重。

    雷克薩斯全新一代ES基於TNGA平台打造,除搭載全新2.0L發動機外,還配有2.5L多級混合動力系統,也就是全新凱美瑞上用的那一套,沒錯,就是傳說中41%熱效率的那一款名機。同時它還搭載了結構更緊湊的全新一代E-CVT电子無極變速系統,以及布局更為合理的鎳氫電池組。另外,最高純電巡航時速由前一代的75km/h提升到120km/h。

    如想了解更多,點擊下方視頻:

    結語

    從當初雷克薩斯CT200h的“天生異質”,到如今各家廠商混合動力車型的日漸成熟,可以說能活下來的、能在市場上銷售的技術都是好技術。不管是自主品牌的比亞迪、長城、吉利,還是合資品牌的兩田,甚至是進口品牌,都在加緊布局新能源戰線,所以這對我們消費者來說無疑是個大利好。至於混動車型值不值得買這樣俗套的話題,我想就不用多糾結了,畢竟買車從來都是按需購買的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 隨機抽樣一致性(RANSAC)算法詳解

    隨機抽樣一致性(RANSAC)算法詳解

    隨機抽樣一致性(RANSAC)算法能夠有效的剔除特徵匹配中的錯誤匹配點。

    實際上,RANSAC能夠有效擬合存在噪聲模型下的擬合函數。實際上,RANSAC算法的核心在於將點劃分為“內點”和“外點”。在一組包含“外點”的數據集中,採用不斷迭代的方法,尋找最優參數模型,不符合最優模型的點,被定義為“外點”。這就是RANSAC的核心思想。

    RANSAC原理

    OpenCV中濾除誤匹配對採用RANSAC算法尋找一個最佳單應性矩陣H,矩陣大小為3×3。RANSAC目的是找到最優的參數矩陣使得滿足該矩陣的數據點個數最多,通常令h33=1來歸一化矩陣。由於單應性矩陣有8個未知參數,至少需要8個線性方程求解,對應到點位置信息上,一組點對可以列出兩個方程,則至少包含4組匹配點對

     

     

     RANSAC算法從匹配數據集中隨機抽出4個樣本並保證這4個樣本之間不共線,計算出單應性矩陣,然後利用這個模型測試所有數據,並計算滿足這個模型數據點的個數與投影誤差(即代價函數),若此模型為最優模型,則對應的代價函數最小。

    損失函數:

     

     

     也就是通過隨機抽樣求解得到一個矩陣,然後驗證其他的點是否符合模型,然後符合的點成為“內點”,不符合的點成為“外點”。下次依然從“新的內點集合”中抽取點構造新的矩陣,重新計算誤差。最後誤差最小,點數最多就是最終的模型。

    RANSAC算法步驟:

    RANSAC算法步驟: 

              1. 隨機從數據集中隨機抽出4個樣本數據 (此4個樣本之間不能共線),計算出變換矩陣H,記為模型M;

              2. 計算數據集中所有數據與模型M的投影誤差,若誤差小於閾值,加入內點集 I ;

              3. 如果當前內點集 I 元素個數大於最優內點集 I_best , 則更新 I_best = I,同時更新迭代次數k ;

              4. 如果迭代次數大於k,則退出 ; 否則迭代次數加1,並重複上述步驟;

      注:迭代次數k在不大於最大迭代次數的情況下,是在不斷更新而不是固定的;

     

     

     其中,p為置信度,一般取0.995;w為”內點”的比例 ; m為計算模型所需要的最少樣本數=4;
    關於RANSAC算法的思想,可以用下圖表示

     

     也就是RANSAC算法的本質是:在存在噪聲的數據中,我們求解一個模型,使得非噪聲數據可以用該模型表示,而噪聲數據被排除在外。

    分享三個講解RANSAC算法的網址:

    https://www.csdn.net/gather_2d/MtjaMg3sNDAwNS1ibG9n.html

    https://www.cnblogs.com/xrwang/archive/2011/03/09/ransac-1.html

    https://blog.csdn.net/yanghan742915081/article/details/83005442

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 微服務技術棧:常見註冊中心組件,對比分析

    微服務技術棧:常見註冊中心組件,對比分析

    本文源碼:GitHub·點這裏 || GitEE·點這裏

    一、註冊中心簡介

    1、基礎概念

    在分佈式架構的系統中註冊中心這個概念就已經被提出了,最經典的就是Zookeeper中間件。

    微服務架構中,註冊中心是最核心的基礎服務之一,註冊中心可以看做是微服務架構中的通信中心,當一個服務去請求另一個服務時,通過註冊中心可以獲取該服務的狀態,地址等核心信息。

    服務註冊主要關係到三大角色:服務提供者、服務消費者、註冊中心。

    2、流程和原理

    基礎流程

    • 服務啟動時,將自身的網絡地址等信息註冊到註冊中心,註冊中心記錄服務註冊數據。
    • 服務消費者從註冊中心獲取服務提供者的地址,並通過地址和基於特定的方式調用服務提供者的接口。
    • 各個服務與註冊中心使用一定機制通信。如果註冊中心與服務長時間無法通信,就會註銷該實例,這也稱為服務下線,當服務重新連接之後,會基於一定的策略在線上線。
    • 服務地址相關信息發生變化時,會重新註冊到註冊中心。這樣,服務消費者就無需手工維護提供者的相關配置。

    核心功能

    通過上面的基本流程,不難發現一個註冊中心需要具備哪些核心功能:

    • 服務發現

    服務發現是指服務在啟動后,註冊到註冊中心,服務方提供自身的元數據,比如IP地址、端口、運行狀況指標的Uri 、主頁地址等信息。

    • 服務記錄

    記錄註冊中心的服務的信息,例如服務名稱、IP地址、端口等。服務消費方基於查詢獲取可用的服務實例列表。

    • 動態管理服務

    註冊中心基於特定的機制定時測試已註冊的服務,例如:默認的情況下會每隔30秒發送一次心跳來進行服務續約。通過服務續約來告知Server該Client仍然可用。正常情況下,如果Server在90 秒內沒有收到Client 的心跳,Server會將Client 實例從註冊列表中刪除。

    二、基礎組件對比

    1、Zookeeper組件

    1.1基礎描述

    ZooKeeper是非常經典的服務註冊中心中間件,在國內環境下,由於受到Dubbo框架的影響,大部分情況下認為Zookeeper是RPC服務框架下註冊中心最好選擇,隨着Dubbo框架的不斷開發優化,和各種註冊中心組件的誕生,即使是RPC框架,現在的註冊中心也逐步放棄了ZooKeeper。在常用的開發集群環境中,ZooKeeper依然起到十分重要的作用,Java體系中,大部分的集群環境都是依賴ZooKeeper管理服務的各個節點。

    1.2組件特點

    從Zookeeper的數據結構特點看,並不是基於服務註冊而設計的,ZooKeeper提供的命名空間與文件系統的名稱空間非常相似,在數據結構上高度抽象為K-V格式,十分通用,說到這裏不得不提一下Redis,也可以作為註冊中心使用,只是用的不多。

    ZooKeeper組件支持節點短暫存在,只要創建znode的會話處於活動狀態,這些znode就會存在,會話結束時,將刪除znode。Dubbo框架正是基於這個特點,服務啟動往Zookeeper註冊的就是臨時節點,需要定時發心跳到Zookeeper來續約節點,並允許服務下線時,將Zookeeper上相應的節點刪除,同時Zookeeper使用ZAB協議雖然保證了數據的強一致性。

    2、Eureka組件

    2.1基礎描述

    SpringCloud框架生態中最原生的深度結合組件,Eureka是Netflix開發的服務發現框架,基於REST的服務,主要用於服務註冊,管理,負載均衡和服務故障轉移。但是官方聲明在Eureka2.0版本停止維護,不建議使用。

    2.2組件特點

    Eureka包含兩個組件:EurekaServer和EurekaClient。

    EurekaServer提供服務註冊服務,各個節點啟動后,會在EurekaServer中進行註冊,這樣EurekaServer中的服務註冊表中將會存儲所有可用服務節點的信息,服務節點的信息可以在界面中直觀的看到。Eureka允許在註冊服務的時候,自定義實現檢查自身狀態的是否健康的方法,這在服務實例能夠保持心跳上報的場景下,是一種比較好的體驗。

    EurekaClient是一個java客戶端,用於簡化與EurekaServer的交互,客戶端同時也就是一個內置的、使用輪詢(round-robin)負載算法的負載均衡器。

    3、Consul組件

    3.1基礎描述

    Consul是用於服務發現和配置的工具。Consul是分佈式的,高度可用的,並且具有極高的可伸縮性,而且開發使用都很簡便。它提供了一個功能齊全的控制面板,主要特點是:服務發現、健康檢查、鍵值存儲、安全服務通信、多數據中心、ServiceMesh。Consul在設計上把很多分佈式服務治理上要用到的功能都包含在內了。

    3.2組件特點

    Consul提供多個數據中心的支持,基於Fabio做負載均衡,每個數據中心內,都有客戶端和服務端的混合構成。預計有三到五台服務端。可以在失敗和性能的可用性之間取得良好的平衡。數據中心中的所有節點都參与八卦協議。這意味着有一個八卦池,其中包含給定數據中心的所有節點。這有幾個目的:首先,不需要為客戶端配置服務器的地址;發現是自動完成的。其次,檢測節點故障的工作不是放在服務器上,而是分佈式的。這使得故障檢測比天真的心跳方案更具可擴展性。第三,它被用作消息傳遞層,用於在諸如領導者選舉等重要事件發生時進行通知。

    4、Nacos組件

    4.1基礎描述

    Nacos致力於發現、配置和管理微服務。Nacos提供了一組簡單易用的特性集,幫助您實現動態服務發現、服務配置管理、服務及流量管理。Nacos更敏捷和容易地構建、交付和管理微服務平台。 Nacos 是構建以“服務”為中心的現代應用架構(例如微服務範式、雲原生範式)的服務基礎設施。Nacos支持作為RPC註冊中心,例如:支持Dubbo框架;也具備微服務註冊中心的能力,例如:SpringCloud框架。

    4.2組件特點

    Nacos在經過多年生產經驗后提煉出的數據模型,則是一種服務-集群-實例的三層模型。如上文所說,這樣基本可以滿足服務在所有場景下的數據存儲和管理,數據模型雖然相對複雜,但是並不強制使用數據結構的風格,大多數應用場景下,和Eureka數據模型是類似的。

    Nacos提供數據邏輯隔離模型,用戶賬號可以新建多個命名空間,每個命名空間對應一個客戶端實例,這個命名空間對應的註冊中心物理集群是可以根據規則進行路由的,這樣可以讓註冊中心內部的升級和遷移對用戶是無感知的。

    三、組件選擇

    如下註冊中心對比圖。

    綜合上述幾種註冊中心對比,再從現在SpringCloud框架流行趨勢看,個人推薦後續微服務架構體系選擇Nacos組件,大致原因如下,社區活躍,經過大規模業務驗證,不但可以作為微服務註冊中心,也支持作RPC框架Dubbo的註冊中心,且有完善的中文文檔,總結下來就一句話:通用中間件,省時;文檔詳細,省心。

    四、源代碼地址

    GitHub·地址
    https://github.com/cicadasmile/husky-spring-cloud
    GitEE·地址
    https://gitee.com/cicadasmile/husky-spring-cloud
    

    推薦文章:微服務基礎系列

    序號 文章標題
    01 微服務基礎:Eureka組件,管理服務註冊發現
    02 微服務基礎:Ribbon和Feign組件,實現請求負載均衡
    03 微服務基礎:Hystrix組件,實現服務熔斷
    04 微服務基礎:Turbine組件,實現微服務集群監控
    05 微服務基礎:Zuul組件,實現路由網關控制
    06 微服務基礎:Config組件,實現配置統一管理
    07 微服務基礎:Zipkin組件,實現請求鏈路追蹤
    08 微服務基礎:與Dubbo框架、Boot框架對比分析
    09 微服務基礎:Nacos組件,服務和配置管理
    10 微服務基礎:Sentinel組件,服務限流和降級
    11 微服務應用:分庫分表模式下,數據庫擴容方案
    12 微服務應用:Shard-Jdbc分庫分表,擴容方案實現

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 同級最便宜,這款20多萬的超帥氣SUV車主卻說……

    同級最便宜,這款20多萬的超帥氣SUV車主卻說……

    0L混動最大馬力(pS):182/發動機:146 電動機:184最大扭矩(Nm):240/發動機:175 電動機:315變速箱:8DCT/E-CVT百公里加速(s):8。35/8。6百公里油耗(L):6。8/5車主百公里油耗(L):8。71/暫

    謳歌CDX肩負着在國內為謳歌品牌打開市場的重任,它具備極其前衛的設計風格,而且看到實車時你就會感受到其設計的精緻感!

    寬敞的空間,前衛設計讓它具有不錯的競爭力。而且它搭載一款1.5T高功率發動機,匹配一副8擋雙離合變速箱,動力系統匹配是高效的,它換擋平順,降擋的速度也相當快,動力表現可圈可點!下面我們就來看看這款豪華SUV車型的各方面表現怎麼樣吧!

    長寬高:4496*1840*1615mm

    軸距:2660mm

    定位:緊湊型SUV

    設計激進的謳歌CDX可謂是同級車型中的一股清流,前臉造型極其激進,鑽石形中網造型顯得相當有個性。同時車身線條緊湊,但車內卻能營造出足夠的乘坐空間。

    內飾方面它在細節處富有創意,造型類似“如意”的旋鈕擋把設計科技感強,而內飾的用料也是到位的。值得一提的是這套內飾的按鍵布局簡約、合理,方向盤上的按鍵也具備不錯手感,在行車過程中操作便利!

    發動機:1.5T/2.0L混動

    最大馬力(pS):182/發動機:146 電動機:184

    最大扭矩(Nm):240/發動機:175 電動機:315

    變速箱:8DCT/E-CVT

    百公里加速(s):8.35/8.6

    百公里油耗(L):6.8/5

    車主百公里油耗(L):8.71/暫本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • UniRx精講(二):獨立的 Update &UniRx 的基本語法格式

    UniRx精講(二):獨立的 Update &UniRx 的基本語法格式

    獨立的 Update

    在 UniRx 簡介的時候,筆者講了一種比較麻煩的情況:就是在 MonoBehaviour 的 Update 中摻雜了大量互相無關的邏輯,導致代碼非常不容易閱讀。

    這種情況我們平時在項目開發中非常常見,代碼如下:

    private void Update()
    {
    	if (A)
    	{
    		...
    	}
    
    	if (B)
    	{
    		...
    		if (D)
    		{
    			...
    		}
    		else {}
    	}
    
    	switch (C)
    	{
    		...
    	}
    
    	if (Input.GetMouseButtonUp(0))
    	{
    		...
    	}
    }
    

    Update 方法中代碼冗長,而且干擾視線,非常影響閱讀。

    而使用 UniRx 則可以改善這個問題。

    void Start()
    {
    	// A 邏輯,實現了 xx
    	Observable.EveryUpdate()
    			.Subscribe(_ => 
    			{
    				if (A)
    				{
    					...
    				}
    			}).AddTo(this);
    	
    
    	// B 邏輯,實現了 xx
    	Observable.EveryUpdate()
    			.Subscribe(_ =>
    			{
    				if (B)
    				{
    					...
    					if (D)
    					{
    						...
    					}
    				else {}
    				}
    			}).AddTo(this);
    
    	// C 邏輯,實現了 xx
    	Observable.EveryUpdate()
    			.Subscribe(_ =>
    			{
    				switch (C)
    				{
    					...
    				}
    			}).AddTo(this);
    
    	// 鼠標點擊檢測邏輯
    	Observable.EveryUpdate()
    			.Subscribe(_ => {
    			{
    				if (Input.GetMouseButtonUp(0))
    				{
    					...
    				}
    			}).AddTo(this);
    }
    

    雖然在代碼長度上沒有任何改善,但是最起碼,這些 Update 邏輯互相之間獨立了。
    狀態跳轉、延時等等這些經常在 Update 里實現的邏輯,都可以使用以上這種方式獨立。

    使用 UniRx 可以對我們工程中的代碼進行了改善,而筆者接觸 UniRx 之後,就再也沒有使用過 Update 方法了。

    不過以上的這種 UniRx 使用方式,是比較初級的,而這種使用方式,隨着對 UniRx 的深入學習,也會漸漸淘汰,因為等我們入門之後,會學習更好的實現方式。

    今天的內容就這些。

    知識地圖

    UniRx 的基本語法格式

    在之前的兩篇文章中,我們學習了 UniRx 的 Timer 和 Update 這兩個 API,但是對代碼的工作原理還沒有進行過介紹。在這篇文章中,我們就來試着理解一下 UniRx 的代碼工作原理及 UniRx 的基本語法格式。

    先搬出來第一篇文章中 Delay 的實現代碼:

    /****************************************************************************
     * http://liangxiegame.com liangxie
     ****************************************************************************/
     
    using System;
    using UniRx;
    using UnityEngine;
    
    namespace UniRxLesson
    {
    	public class DelayExample : MonoBehaviour
    	{
    		private void Start()
    		{
    			Observable.Timer(TimeSpan.FromSeconds(2.0f)).Subscribe(_ =>
    			{
    				Debug.Log("延時兩秒"); 
    				
    			}).AddTo(this);
    		}
    	}
    }
    

    代碼中的 Observable.XXX().Subscribe() 是非常經典的 UniRx 格式。只要理解了這種格式就可以看懂大部分的 UniRx 的用法了。

    首先解決代碼中的詞彙問題:

    • Observable:可觀察的,是形容詞,它形容後邊的詞(Timer)是可觀察的,我們可以直接把 Observable 後邊的詞理解成發布者。
    • Timer:定時器,名詞,被 Observable 修飾,所以是發布者,是事件的發送方。
    • Subscribe:訂閱,是動詞,它訂閱誰呢?當然是前邊的 Timer,這裏可以理解成訂閱者,也就是事件的接收方。
    • AddTo:添加到,這個我們暫時不用理解得太深刻,只需要知道它是與 MonoBehaviour 進行生命周期綁定即可。

    以上的代碼,連起來則是:可被觀察(監聽)的.Timer().訂閱()
    理順了之後應該是:訂閱可被觀察的定時器。

    其概念關係很容易理解。

    • Timer 是可觀察的。
    • 可觀察的才能被訂閱。
    Observable.XXX().Subscribe();
    

    這行代碼我們可以理解為:可被觀察(監聽)的 XX,註冊。

    以上筆者從發布者和訂閱者這個角度進行了簡單的介紹,以便大家理解。
    但是 UniRx 的側重點,不是發布者和訂閱者這兩個概念如何使用,而是事件從發布者到訂閱者之間的過程如何處理。
    所以這兩個點不重要,重要的是兩點之間的線,也就是事件的傳遞過程。

    這裏先不說得太深入,在入門之後,會用很大的篇幅去深入介紹這些概念的。

    今天的 UniRx 的基本語法格式的介紹就到這裏,我們下一篇再見,拜拜~

    知識地圖

    更多內容
    QFramework 地址:https://github.com/liangxiegame/QFramework
    QQ 交流群:623597263
    涼鞋的主頁:https://liangxiegame.com/zhuanlan
    關注公眾號:liangxiegame 獲取第一時間更新通知及更多的免費內容。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案