編程模式漫談

4月份遺漏了一篇,這篇算是補充。html

此文並不是對設計模式的總結,而是要談談通常的編碼風格,找設計模式的朋友能夠移步了。git


什麼是模式?我搜到一個簡短的解釋:模式是指從生產經驗和生活經驗中通過抽象和昇華提煉出來的核心知識體系。程序員

模式是多個生活、生產行爲的概括總結,也就是解決某一類問題的方法論。從信息的角度來理解,模式是對信息減少信息熵的過程和結果。github

好比一個 APP 須要組織一個表單,表單裏的部分元素如選項列表由服務器提供,假設是一個行業列表,一般可能定義一個列表:web

[
    {trade_id: 10, trade_name: "xdfsdf"},
    ...
]

看上去接口提供的這個數據沒什麼問題。若是此時表單裏還有選項列表,選擇城市,那你可能很天然想到 {city_id: 25, city_name: "wefel"} 。對於客戶端,問題也不大,MVC 模式嘛,我在 Model 裏分別指定不就好了。算法

嗯,你也許猜到我想說什麼了,沒錯,我想用一個可更簡單的、可複用的結構來進行描述。如:設計模式

[
    {id: 10, name: "xdfsdf"},
    ...
]

甚至:服務器

[
    [10, "xdfsdf"],
    ...
]

這樣對此類控件能夠更容易的封裝使用。按信息熵的觀點這種模式對熵的下降最大。數據結構

但這種轉換您可能嗤之以鼻:『切,這算什麼模式?』框架

我以爲當開始思考模式時,就是在思考事物與事物間的關聯,這纔是一個程序員(Programmer)該作的事情;若是僅僅是爲了寫點代碼實現某個功能,那該叫編碼員(Coder)。

對照模式的解釋,算法也是模式;我念書少,一直對算法掌握的比較弱,因此此文也就不班門弄斧了。在這裏對僅對常規的 MIS(管理信息系統)的接口數據結構提出一些見解,主要針對數據的 CRUD(增刪改查)。


數據模式

假設要爲一家餐館作一個小型 App,需求是構建菜譜,有單品,有套餐。單品的屬性有:名稱、單價、簡介、照片(多個,文件、說明),套餐有:名稱、折扣價、簡介、餐品(多個)。繪製 ERM 以下:

圖片描述

單品和套餐一般能夠出如今一個搜索列表裏,屬性基本一致,故放在同一張表裏(模式出現了)。照片可以複用,好比單品能夠引用,某個套餐也能夠用,甚至系統其餘的地方也能夠用(這就是模式),那就獨立出來好了(沒有 Product_id 而是用 Product_has_Picture 的多對多關係)。

下面咱們開始設計 REST 接口:

GET    /xxx/products
Response Content: [
    {
        id: "商品 ID",
        name: "名稱",
        note: "簡介",
        price: "價格, 格式: RMB ###.##",
        is_package: "是不是套餐",
        main_picture: "主要展現照片 URL"
    }
]

GET    /xxx/products/ID
Response Content: {
    id: "Product ID",
    name: "名稱",
    note: "簡介",
    price: "價格, 格式: RMB ###.##",
    is_package: "是不是套餐",
    pictures: [
        {
            id: "照片 ID",
            name: "名稱",
            note: "簡介"
        }
    ],
    products: [
        {
            id: "商品 ID",
            name: "名稱",
            note: "簡介",
            price: "價格, 格式: RMB ###.##",
            main_picture: "主要展現照片 URL",
            is_free: "是否免費附送"
        }
    ]
}

POST   /www/products
PUT    /www/products/ID
Request Content: {
    name: "名稱",
    note: "簡介",
    price: "價格, 格式: RMB ###.##",
    is_package: "是不是套餐",
    pictures: [
        {
            Picture_id: "照片 ID",
            is_main: "0或1"
        }
    ]
}

DELETE /www/products/ID

看上去沒什麼特殊的呀!細心的話也許發現了,GET 拿到的 pictures 列表與 POST、PUT 發送的 pictures 列表結構不一致,這是根據個人框架能識別的模式所作的精簡,事實上 pictures 的完整結構爲:

Product_has_Picture: [
    {
        Product_id: "商品ID",
        Picture_id: "照片ID",
        is_main: "是不是主照片",
        Picture: {
            id: "照片ID",
            name: "照片名稱",
            note: "照片說明"
        }
    }
]

在進行商品的建立和修改時,因爲照片是從照片列表(接口)選擇的,故 Picture 層是不須要的。若是用相似 Protobuf 的方式描述 Product 的數據結構,應該是:

message Picture {
    required int323 id = 1;
    required string name = 2;
    required string note = 3;
}

message Product {
    required int32 id = 1;
    required string name = 2;
    optional string note = 3;
    required float price = 4;
    optional bool is_package = 5;

    message Pictures {
        required Picture picture = 1;
        optional bool is_main = 2 [default = 0];
    }
    repeated Pictures pictures = 6;

    message Products {
        required Product product = 1;
        optional bool is_main = 2 [default = 0];
    }
    repeated Products products = 7;
}

能夠看出 Pictures 內是單個的 Picture 對象,重複的是 Pictures 對象;若是僅僅按數據結構越簡單越好的模式來評判,假設 Pictures 列表不須要 is_main 屬性,按第一個圖即爲默認圖的方式好了,也就是 Product 內直接定義 repeated Picture pictures。那麼,PUT Product 時 pictures 可定義爲一個由 ID 組成的列表甚至分隔符分隔的字符串。可是,這將增長處理程序的複雜性,程序並不能輕鬆的自動處理,或者要將多對多關聯分解成純粹的和有其餘數據的兩種模式。

這裏涉及到另外一個模式,咱們的數據關聯方式到底有多少種,多數的 ORM 框架會給你這樣幾種:BLONGS_TO HAS_ONE HAS_MANY MANY_TO_MANY,但當從 ERM 轉爲類圖的時候,你會發現其實只有一種關係便可完整覆蓋這 4 種關係,那就是 BELONGS_TO,在類圖裏叫 Dependency (我的以爲對 UML 的 Association Aggregation 等其餘名詞不必太較勁)

爲了顯式的申明當前關聯查詢的表,肯定關聯方向,保留 HAS_ONE、HAS_MANY 便於理解(實際上是我嫌麻煩,Django 就作到了只要申明瞭 ForeignKey 就能夠正、反雙向的關聯查詢),由此可得出 A MANY_TO_MANY C 能夠分解爲 A HAS_MANY B (by A_ID) BELONGS_TO C (by C_ID)

當理清楚了這個結構模式時,就能夠編寫程序『自動』處理請求了,好比你給了 products 數據 {product_id: 31, is_main: 1},就會去寫入 Product_has_Picture 的關聯數據, 外鍵 package_id 能夠自動代入。查詢商品列表時給了 products.product_id 就會提取含有某個(些)商品的套餐。

以此看來,數據自己就成了『查詢語句』和『業務邏輯』。


動做模式

說完告終構再說說行爲。對於常規的 CRUD,我認爲從視覺操做上能夠分解爲兩個組件:列表、表單;列表對應的接口有:獲取列表、更新;表單對應的接口有:獲取信息、保存(增長、修改)。

獲取列表接口可涵蓋:分頁、排序、搜索(篩選)等;
列表的更新有:刪除、更新狀態(批量更新某個字段的值);

在多年之前 WEB 開發中頁面與頁面(組件)間的聯動是靠顯式的告知回跳地址或用 Referer 來回到來源處,好比連貫的行爲:打開列表->添加商品->添加完成->跳回列表。

而獨立的組件模式,組件與組件之間的聯動是採用事件來驅動,A組件完成後廣播本身完成的事件,哪一個組件須要處理哪一個組件監聽該事件就行了,簡單總結爲:誰打開、誰負責。好比現有連貫操做:打開 A 列表->添加 A->進入 A 表單->選擇 B->打開 B 列表->搜索 B->結果裏沒有->添加 B->打開 B 表單->保存 B->返回 A 表單->自動選中剛添加的 B->保存 A->刷新 A 列表。

這看上去好像很麻煩,用獨立組件的方式就很好理解了:

  1. A 列表打開 B 選擇列表,此時監聽 B 的選擇完成事件
  2. B 選擇列表打開 B 表單,此時監聽 B 的保存完成事件
  3. 當 B 列表監聽到打開的 B 表單保存完成時,觸發 B 選擇完成事件
  4. 當 A 列表監聽到打開的 B 列表選擇完成時,將選中數據加入選項並選中

這種方式在 App 和 Web 的 Rich Client 開發中其實很常見。此處表單的選擇是一個封裝了打開、監聽、選中這些行爲的控件;每一個組件、控件都僅僅關心本身打開的組件便可,並不須要關注完整的行爲;其實平時工做中的人未嘗不是如此,問題在於咱們老是把問題搞得很複雜,而以爲花時間去思考它們的共性是在浪費時間

圖片描述
建立一個訂單,挑選幾個商品。

圖片描述
我要的商品不存在,那就建一個好了。純舉例,哪有餐館沒菜顧客本身動手的道理


利用模式,能夠把一系列動做、判斷用一個靜態的描述來定義,好比一組表單驗證,一個表關聯關係描述。

而在行爲模式上,將關注點從一系列動做縮小到二者之間關係的描述上。對程序複用和方便人操做識別都有價值。

模式是廣泛存在的,從 HTTP 到 SQL。不是咱們缺乏模式,而是太多的精力和時間耗費在了模式的轉換上,而忽略了模式與模式間轉換的模式的思考。

模式就是要創建起一個足夠『傻』的描述,如同《阿甘正傳》裏甘去見珍妮時坐在長凳上對路人說:

Mama always said "There's an awful lot you can tell about a person by their shoes." Where they're going. Where they've been.(媽媽常說看一我的的鞋子你就能夠知道他的不少事情。他們要去哪裏,去過哪裏。)

參考資料

模式 http://www.baike.com/wiki/%E6%A8%A1%E5%BC%8F
信息熵 http://baike.baidu.com/view/401605.htm
《Google Protocol Buffers 入門》 http://shitouer.cn/2013/04/google-protocol-buffers-tutorial/
《UML類圖符號 各類關係說明以及舉例》 http://www.cnblogs.com/duanxz/archive/2012/06/13/2547801.html
HongsCORE for Javascript https://github.com/ihongs/HongsCORE/tree/develop/hongs-web/web/common

相關文章
相關標籤/搜索