iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 3
0
Modern Web

《你的地圖會說話? WebGIS與JavaScript的情感交織》系列 第 3

[1-2] 地圖的工廠 - 以 簡單工廠模式 Simple Factory Design Pattern 產出地圖

本篇文章請搭配
[1-1] 該選哪種地圖API?小孩子才做選擇。

還沒看過的人,傳送門: https://ithelp.ithome.com.tw/articles/10238282


相信大家都有一種經驗,曾經很熟悉的東西,一陣子沒用之後,回來看彷彿恍如隔世。
抑或是曾經的自己寫出來的東西,盯著老半天,卻看不懂到底再寫什麼?

會不會後悔當時的自己沒認真寫註解?或者是懺悔自己命名亂命?

如果程式有好好寫,不用看懂細節,也能夠reuse的話該有多好。
地圖API參數怎麼下,方法怎麼呼叫,忘記怎麼用也沒關係,
人生只要懂過一次,然後記得封裝好會用就好。(誤人子弟

簡單工廠模式 Simple Factory Design Pattern

  • Step1

↓ 還記得昨天寫的初始化地圖的API範例嗎?
隨便取一個Google API的初始化地圖,然後包在一個function裡面。

        function GMap() {
            var GMap = new google.maps.Map(document.getElementById('gmap'), {
                center: { lat: 23.5, lng: 121 },
                zoom: 7
            });
        }

↓ 然後設計一個工廠,它可以為我們建立地圖。

        function MapFactory() {
            this.Build = function (mapType) {
                var map;
                map = new mapType();  // 地圖的種類可以由參數 mapType 決定
                return map;
            };
        }

↑ MapFactory是一個類別,以這個類別建立的物件會有Build這個方法可以呼叫。

        var mapFactory = new MapFactory();  //建立工廠
        var gmap = mapFactory.Build(GMap);  //新建 GMap 類的地圖

↑ 如此一來就可以順利用我們建立的工廠來新建地圖了,並且可以指定地圖的種類(GMap)。

  • Step2

可是中心點座標、縮放層級等等參數我想要每次呼叫都不一樣耶!那就把參數傳進GMap裡吧!

        function GMap(option) {  // 傳入一個 option 物件
            this.x = option.x;  // this 指向當下由 GMap 建立的物件
            this.y = option.y;
            this.coordinate = option.coordinate;
            this.zoom = option.zoom;
            this.id = option.id;
            this.Init = function () {  // 建立一個 Init 方法來初始化地圖
                return new google.maps.Map(document.getElementById(this.id), {
                    center: { lat: this.y, lng: this.x },
                    zoom: this.zoom
                });
            };
        }
  • this的指向
    JavaScript中this的指向是一門很大的學問,
    this指向跟當下的執行環境有關,在呼叫該function的時候才會被決定。
    如果是在瀏覽器全域環境中呼叫function,this會指向window;
    如果是由某個物件去呼叫function,this則指向該物件;
    在node.js中的全域環境,this則是指向global。
        function MapFactory() {
            this.Build = function (mapType, option) {  // 加入 option 參數
                var map;

                map = new mapType(option);
                return map;
            };
        }

↑ 工廠建造地圖時加入option參數

        var mapFactory = new MapFactory();
        var gmap = mapFactory.Build(GMap, {
            x: 121,
            y: 23.5,
            coordinate: 'EPSG3857',
            zoom: 7,
            id: 'map'
        });
        gmap.Init();

↑ 呼叫時以物件方式傳入參數

  • Step3

看膩了Google Map,換Leaflet來寫寫看吧!

         function LMap(option) {
            this.Init = function () {
                var LMap = L.map(document.getElementById(this.id), {
                    center: [this.y, this.x],
                    zoom: this.zoom,
                    crs: L.CRS[this.coordinate],
                });
                L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
                    maxZoom: 18,
                    id: 'mapbox.streets'
                }).addTo(LMap);

                return LMap;
            };
        }

這次我們把option在MapFactory呼叫Build時賦予屬性,這樣就不會在GMap寫一次、LMap也寫一次,
造成 重複的程式碼(Duplicate Code)

        function MapFactory() {
            this.Build = function (mapType, option) {
                var map;

                if (!(mapType instanceof Function)) {
                    console.error(`${mapType} is not constructor.`);
                    return;
                }

                map = new mapType(option);
                map.x = option.x;
                map.y = option.y;
                map.coordinate = option.coordinate;
                map.zoom = option.zoom;
                map.id = option.id;

                return map;
            };
        }
  • instanceof
    這裡有一個小技巧!如果mapType傳入的並不是一個function怎麼辦呢?
    可以用instanceof這個語法判斷物件是否為類別的實例。
    <<物件>> instanceof <<類別>>
    如果這個物件是由這個類別創造出來的則會回傳true,反之,則為false。
    注意!instanceof 只適用於JS物件型別,原始型別請使用typeof判斷。

  • 樣板語法 template literal
    ${mapType}
    反斜線是ES6才有的樣板語法,並且可以用${...}的方式插入變數,
    在ES5以前必須用單引號跟雙引號的組合,並且用字串相加的方式。

  • Step4

如果今天你要建立的地圖物件很多,每一次建立地圖物件的時候,每一次都要賦予該物件Init方法,
可不可以只賦予一次?當然可以!我們把方法建立在它的原型上!
這次換換口味,用ArcGIS API。

        function AMap(option) { }

        AMap.prototype.Init = function () {
            var el = this;  // this 指向呼叫 Init 方法的物件
            var mapObject = {};

            require(["esri/Map", "esri/views/MapView"], function (Map, MapView) {
                var myMap = new Map({
                    basemap: "streets-vector"
                });
                // 2D viewing
                var view = new MapView({
                    container: el.id,
                    map: myMap,
                    zoom: el.zoom,
                    center: [el.x, el.y]
                });
                console.log(this);  // this 指向 window
                mapObject = view;
            });

            return mapObject;
        }

  • 原型(prototype)是JavaScript很難理解的觀念之一。因為JS並非傳統物件導向程式語言,
    沒有類別的概念,就必須靠function為主體一步一腳印仿造建構子、類別、物件等等。
    其中,「繼承」就是靠原型鍊(prototype chain)來實踐。
    JS物件在呼叫屬性或函式的時候,會先找自身,如果自身沒有,就會沿著原型鍊往上搜尋。
    物件型別的原型鍊頂層是Object,如果依舊沒有找到該屬性或函式就會回傳undefined。
  • ES6以後,JavaScript新增了很多保留字,class、constructor、static等等,
    都是為了與物件導向程式語言更為貼近。但是,其原理依舊是由prototype所實踐,
    ES6的這些方法只是提供語法糖,可以更方便開發。
        var XXCompany = new MapFactory();  // XXCompany 填你公司的名子
        
        // 以後只需要這樣呼叫,由第一個參數決定要呼叫的地圖API,第二個參數作地圖設定        
        var map = XXCompany.Build(AMap, {
            x: 121,
            y: 23.5,
            coordinate: 'EPSG3857',
            zoom: 7,
            id: 'map'
        });
        map.Init();

然後一樣的呼叫方式,把變數名稱換成你的名子或是公司的名子,
看起來很像某某公司建立的地圖,假裝很厲害?! /images/emoticon/emoticon30.gif

小結

講了那麼多,利用簡單工廠模式封裝地圖究竟有什麼優點?

  1. 對多種地圖API進行封裝,只要換個參數,就可以隨意切換想用的地圖API

  2. 把地圖設定藉由物件的型式傳入function,而不是寫死在程式中,統一介面,更方便進行修改。

  3. 每個建立的物件如果有不同的屬性或方法則寫在建構函式(GMap、LMap、AMap)中,相同的屬性或方法則寫在原型(prototype)上。

    你可以

  4. 在客戶很GY地說要改圖台底層的時候,向老闆解釋有多難多難,向客戶解釋要花多大的人力與時間來修改,結果你只是去改個設定就解決!
    你可以

  5. 在不懂WebGIS的同事請你幫忙寫圖台一堆功能的時候,呼叫幾個寫好的函式new MapFactory(); mapFactory.Build(); map.Init();,三兩下解決同事的請求!
    你還可以

  6. 在IT邦幫忙發發文章,誤人子弟 (X


下一篇,會開始介紹WebGIS的點、線、面資料,
想要知道怎麼用瀏覽器定位嗎? 想要知道怎麼用地址定位搜尋嗎?
請不要錯過。/images/emoticon/emoticon15.gif


上一篇
[1-1] 該選哪種地圖API?小孩子才做選擇。
下一篇
[2-1] 點資料圖徵 X 瀏覽器定位 X 地址定位
系列文
《你的地圖會說話? WebGIS與JavaScript的情感交織》30

尚未有邦友留言

立即登入留言