抽象與分層,是計算與程序世界裏最根本的思想。邏輯之始。程序員
抽象是對廣泛性的表達,分層則是在適當的語義層次放置抽象。0,1, 1+1=2, 1+1=10,都是抽象;「大人說話小孩別插嘴」,就是一種分層。緩存
抽象與分層能力,是程序員的內功心法。可以細緻思考抽象與分層的開發人員,作出的設計和寫出的代碼每每更加簡潔優雅。而考察程序員,不只僅看其已經掌握的知識和技能,更要看他是怎麼去思考和解決問題的。不一樣人的多樣化的思考和解決方式,或許比標準答案更加有趣。數據結構
本文來談談抽象與分層。併發
有同窗會問:抽象是什麼?看上去真的很難懂啊! 實際上,抽象就隱藏在寫下的每一行代碼裏。app
抽象主要有通用域抽象和領域抽象。通用域抽象是全部軟件都會複用的概念、實體與交互;領域抽象則特定於某個具體的行業領域。抽象一般使用術語來表達。函數
讓咱們來從新看看寫過的代碼,從中找出抽象吧!
性能
主要有六類抽象:大數據
流程型抽象: 表達應用流程,將單一功能構形成實用的服務ui
任務型抽象: 使用有限可控的任務執行者集穩定高效地完成源源不斷來臨的任務spa
數據處理抽象: 任務的實際內容
結構型抽象: 存儲和容納執行任務所須要的資源、數據集
數據模型抽象: 具備語義關聯的數據項聚合體
原子數據抽象: 組成數據的基本數據單位
流程型抽象,是對各類應用流程的表達,控制和引導流程的實體。
流程抽象要解決應用中的請求(及攜帶的數據)在流程及節點裏的處理流轉。這些流轉有分支、循環、回溯、轉發、路由、阻塞、等待、退出等。
Entrance: 入口 ; Exit: 出口
Flow: 流程,具備入口 Entrance 和出口 Exit 的NodePath
Node: Flow 中的節點
NodePath: 節點路徑,由一系列節點及鏈接組成的子流程
Condition: 觸發條件,決定流程的轉向
Request: 請求 ; Response: 響應 ;
Component: 組件,Node 的能力抽象
Dependency: Node 之間的依賴關係
Plugin: 具備不一樣功能的插件,可配置化,Component 的一種形態
Service: 提供某種全局服務,一般是單例,Component 的一種形態
Enginer: 引擎,控制整個流程的執行
Initializer:流程啓動時,元數據、配置依賴及全局服務的初始化,好比Spring進行Bean初始化
Handler: 接收請求 Request , 輸出響應 Response
Filter: 過濾掉不能處理的請求 Request
Goto: 直接跳轉到某個 Node
Dispatcher: 將 Request 轉發給合適的 Handler
Router: 將 Request 路由到適合的地方作下一步處理
Tracer: NodePath 的追蹤與記錄
Switcher: 流程開關,根據匹配結果切換到某個 NodePath
Breaker: 適配某種 Condition 時,退出所在 NodePath
Blocker: 阻塞 NodePath ,一般是某種 Condition 未能知足
Joiner: 動態加入者,能夠是 Node, NodePath , Component 等
ConflictSolver: 當條件適配多種規則或節點時的解決策略
Waiter: 等待某種 Condition 觸發的實體
Notifier: 觸發某種 Condition 的實體
Terminator: Flow 的終結者
功能的實現,能夠抽象爲任務來處理。任務型抽象包含了用於執行任務的實體。
任務型抽象必須解決「有限可控的任務執行者集合」與「任務集合的增減不可控」之間的矛盾。由於應用內存是有限的,不能無限增加,所以必須用可控地執行數量和抵達速率均不可控的任務集合。
Function: 實現某種功能的表達
Task: 基於 Function 的通用任務語義
Job: 一般指不受人工干預的後臺運行的 Task
Resource: 完成任務所須要的資源
TasksHolder: Task 持有者,一般是任務隊列
Worker:從 TasksHolder 取出 Task 並實際執行者
Pool: 池對象,有限 Worker 的持有者,並使之增減可控和合理利用
Executor:將 Worker 和 Pool 組合起來執行任務的管理者
ResultFetcher: Task 執行結果的獲取
Rejecter: 拒絕執行的實體,一般採用某種策略 Strategy
Protector: 保護 Woker 不受干擾正確執行,好比鎖 * Lock * ,同步 Sychronizer 等
Lock: 併發執行下的鎖,Protector 的一種形態
Sychronizer: 併發執行的同步體,一般會使用一個監視器
Cleaner: 清理/釋放 Resource
State: Task 或 Worker 或 Pool 或 Executor 的狀態
Strategy: Task 的執行策略,當沒有足夠 Worker 時要採起的處理策略
數據處理抽象,是對數據或數據集執行特定操做的實體。在程序中尤其多見。
Assembler: 數據組裝,未來自多個源的數據組裝成中間或最終對象
Builder: 參數構建、結果構建。 還專門有個 Builder 模式來構建複雜對象
Checker: 參數校驗、結果校驗, 檢測參數是否合法,結果是否符合預期
Caster: 數據的類型轉換
Converter: 數據轉換,將數據從一個形態轉換爲另外一個形態
Formatter: 按照某種格式聚合和輸出可讀的數據形態
Fetcher: 從某個 DataSource 獲取數據
Cacher:數據的緩存,複用提高性能
Copier:將數據從一個結構拷貝到另外一個結構
Sync: 將大數據集從一個源同步到另外一個源
Extracter : 數據提取,從複雜數據結構解析出所需字段和信息,好比從 JSON 或 XML 中解析出所需信息
Parser: 數據解析,從一種表現形態解析爲另外一種表現形態,數據信息不變,好比 JSON 轉爲 Map,日期轉時間戳
Transfer: 數據類型傳輸,從 DO 到 DTO
Transfomer: 數據變換,從一種形態變換到另外一種
Getter: 從對象中獲取某個字段的值 ; Setter: 設置對象的某個字段的值
Finder: 查找到符合條件的數據或數據的位置
Replacer: 使用某個數據替換掉指定數據
Iterator: 迭代遍歷地訪問某種數據結構而屏蔽內部實現細節
Joiner: 將多個不一樣列的數據集根據關聯字段合併爲一個具備全部列的數據集
Matcher: 將數據匹配某個條件或模式
Merger: 將多個子數據集合併爲一個彙總的數據集
Splitter: 將一個聚合的數據集分割爲多個子數據
Separator: 分割符、關注點隔離
Collector: 從Stream里根據指定操做收集數據並構建出新的數據集
Filter: 根據指定條件從一個數據集中篩選出指定的數據構成新的數據集
Foreach: 遍歷數據集的每一個元素並執行某種操做
Grouper: 對數據集根據某個條件進行分組
Generator: 數據集的生產者
Mapper: 將一個數據集映射爲另外一個數據集
Reducer: 對某個集合的元素反覆執行某種指定操做獲得數據集或單個數據結果
Supplier: 數據集的提供者
Searcher: 從一個結構中搜索某個元素
Sorter:排序,將數據集按照某個順序從新組合成新的數據集,每一個相鄰元素都知足某種指定條件
Add&Delete:在某個結構中添加/刪除數據
Query: 客戶端向服務端請求數據
Selecter: 選擇,從數據集或組合結構中選擇符合某種條件的元素集合,好比DOM Selecter, SQL Selector
Updater:更新,將已有的數據更新爲新的狀態
結構型抽象,用於存儲數據集,便於批量高效地處理。
Buffer: 緩衝
BitMap: 位圖
Empty: 空結構
Enum: 有限可列元素的枚舉
File: 具備文本語義的數據簇的聚合
List:有序、可重複的數據組成的列表
Set:無序、不重複的數據組成的集合
Map: 鍵-值對
Stack : 後進先出的數據結構
Queue:隊列,通常是先進先出
Heap: 前驅元素與後繼元素老是知足某種比較關係的數據結構
Partition:分區,將數據集分區爲多個子數據集進行存儲和處理
Tree: 樹,有分叉的數據結構
Graph: 圖,節點相互有連通的數據結構
Stream: 具備惰性計算能力的、可無界的數據結構
Text: 文本,具備鬆散結果的字符串數據聚合
數據模型,將有關聯的一系列數據組成攜帶領域語義的對象
Class: 類,類別; 具備類似性的實例的廣泛性表達
Config: 配置,通常是 鍵值對,JSON,XML,Yaml 等,來自文件、內存數據等
Context: 上下文,在流程中起串聯全部必要資源的做用
MetaData: 元數據,描述數據及結構的數據
Entity&Object: 通用實體/領域實體
Instance: 實例,實體的某個存在
Field: 字段,包含字段的名稱、類型、修飾符等信息
Property&Attribute: 屬性,包含屬性的名稱、類型、修飾符等信息
State: 實例的狀態
Method: 方法,實例行爲的表達
Factory: 工廠,組件的建立者
Holder:任意對象的持有者,一般與泛型結合使用
Model: 數據模型,定義返回對象包含的數據字段及類型
Pager: 分頁,頁數和頁大小
Result: 結果,一般包含 code, message, data
Placeholder: 佔位符,內含待替換的引用變量
Option: 可選項
Range: 區間,最多見的就是時間區間
Tuple: 元組,若干語義關聯的數據聚合體
Record: 記錄,具備語義關聯的數據聚合而成
Message: 消息
Tag: 標籤,從某個角度標識某個實體
Variable: 變量,臨時可變數據的持有者
原子數據,是組成數據的基本數據單位。
Bit: 位
Byte: 字節
Bool: 真,假
Char:單個字符
Int&Long:整數
Float&Double:浮點數
Number:數值
String:有序字符列表組成的字符串
Constant: 常量
Error&Exception: 錯誤與異常
Pointer: 指針,存儲指向對象的內存地址
Reference: 對象的引用者
從程序中提煉抽象後,如何應用到實際工做中呢?
實際程序中,每每是上述抽象的靈活組合。 各類抽象的關係,在程序中的體現是:
原子數據抽象 -> 聚合爲數據模型抽象 -> 由結構型抽象來存儲 -> 經過數據處理抽象 -> 組合爲任務抽象 -> 經過流程抽象來控制任務執行和流轉 -> 最終流程實現
在命名時,能夠直接使用這些詞彙,凸顯語義,使代碼更容易可讀可理解。
分層,就是將提煉出的抽象置於合適的語義層次。 好比經常使用的Controller, FacadeService, BaseService, DAO, Repository 等,就是不一樣的層次。
分層原則主要有:
高層語義表達意圖,底層語義呈現細節。
上一層依賴下一層的語義抽象而不是實現。
業務層 -> 共享產品層 -> 共享層 -> 基礎層
相同層次通常不相互直接調用,好比 Controller 不要直接調用其餘的 Controller 的東西。
不建議跨層調用,好比 Controller 不直接調用 DAO 的功能。
原子語義的層次,與組合語義的層次不要混雜在一塊兒。
合適的語義層次劃分,會使流程和交互更加清晰可讀容易理解。
最近排查解決一個問題,大概是這樣的:
存儲層: 訂單的贈品的價格數據存儲,沒有存儲相應的商品ID(訂單內的正常商品是必定有的);
中間層: 沒法取到訂單贈品的價格信息(無法對應上);
輸出層: 一些報表字段的輸出沒有考慮到對贈品的處理,致使輸出有誤;要對贈品作一些特殊的邏輯處理。
看上去不存儲贈品的商品ID,也不算是什麼問題;但從抽象角度來看,是不能理解的。贈品雖然與商品在價格上表現有些特殊,但在商品ID存儲上,並無特別之處。若是可以一致性地處理,那麼就不會出現沒法取到贈品信息的價格信息,也不須要針對贈品作一些特殊的兼容(好比聚合價格數據對應到商品時有特殊處理、報表字段輸出的邏輯要過濾贈品等),也就不須要花時間去調試和排查解決這個問題。
忽然想到那個問題:軟件維護成本是怎麼產生的?除新需求外,每每來自以前埋下的坑。那麼坑又是怎麼產生的呢?一般是設計不嚴謹致使。 因爲設計不嚴謹,遇到特定狀況或者擴展就要作特殊處理,要作兼容,一個兼容可能引起一系列兼容,尤爲源頭的不嚴謹,會致使源到端的一整條路徑的兼容,代碼就會比較難看難理解,這樣就埋坑了;解決坑的時候,兼容性的解決方案,在事情的變化中又會埋下新的坑。程序猿媛們就在埋坑和填坑的往復循環中「痛並快樂着」。
那麼設計不嚴謹又是怎麼產生的呢? 一般是抽象和分層作的不夠致使的。開發人員習慣於按照流程順手寫下來,而不細思和提煉出流程中的概念、關聯、實體、交互等,將其構建成一件更有序更柔軟的邏輯裝置。結果就是當有變化或變體的時候,裝置就出毛病罷工,就要作修修補補。所以,建議可以剋制性地退後一步思考,不急於着手,而是先仔細推敲流程,提煉出關鍵因素,進行抽象和分層,在其指引下進行設計和開發。這個建議也是給本身的。
工做夥伴CR個人代碼時,有時會以爲我寫的代碼看起來比較累。這並非由於我寫的函數很長。實際上,我很注重代碼複用,寫的函數都是比較短小的,按理來講不該該有這樣的感覺。
仔細思考後發現,是分層不夠清晰致使。就是寫程序時,每每須要的函數就寫到一個類裏,致使這個類混雜了不少功能,摻雜了不少不一樣的語義,致使理解流程時不夠直觀清晰。
經過抽象和分層,將不一樣功能語義抽離到不一樣的類中實現,並在合適的地方引入依賴,這樣代碼層次就更容易理解可讀了。
抽象與分層,是計算與程序世界裏最根本的思想。邏輯之始。本文從代碼中提煉了許多表達通用抽象語義的詞彙,能夠做爲設計、開發和交流的指引,同時經過案例說明了抽象缺失和分層缺失致使的後果。對於開發人員,可以預先細緻思考抽象與分層,作出的設計和寫出的代碼每每更加簡潔優雅。