天天看點

子產品化群組件化

當你進行使用者需求調研後,往往收集到的都是一個個的使用者需求點,而一個軟體需求分析員要做的是最終将這些需求實作為一個完整的業務系統。這裡面就涉 及到業務子產品的劃分,子產品間的分析,需求層面的複用能力分析,各種性能,可靠性,安全等非功能性需求。這些更加已經是一個完全的系統分析方面的内容,或者 說軟體需求已經會兼顧部分軟體架構設計的内容,是以作為一個軟體需求人員更加需要去了解業務元件化,服務化,軟體子產品內建,複用等方面的技術内容。也需要 去了解涉及到互動設計方面的内容,這些都是形成一個高品質的軟體業務系統的重要輸入。

子產品化是個一般概念,這一概念也适用于軟體開發,可以讓軟體按子產品單獨開發,各子產品通常都用一個标準化的接口來進行通信。除了規模大小有差別外,面向對象語言中對象之間的關注點分離與子產品化的概念基本一緻。通常,把系統劃分外多個子產品有助于将耦合減至最低,讓代碼維護更加簡單。任何一個類庫實際上都是一個子產品,無論其是Log4J、React還是Node。通常,開源和非開源的應用都會依賴于一個或多個外部類庫,而這種依賴關系又有可能傳遞到其他類庫上。任何語言都有子產品化的思想,比如java的 package, es6的 import/export 等,而js恰好經曆了從無到有,而且js子產品化規範比較多,AMD,CMD,UMD,以及es6官方的import/export,是以我以js為切入點,講解子產品化是什麼。

js誕生的時候是沒有子產品化規範的,所有的代碼都是在一個個script中,依賴管理也完全是全局變量+順序加載的模式。那時候代碼大概是這樣的:

<code>&lt;script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"&gt;&lt;/script&gt;</code>

<code>&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"&gt;&lt;/script&gt;</code>

<code>&lt;script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"&gt;&lt;/script&gt;</code>

最初js就是靠這種浏覽器從上到下加載執行的這種特性完成依賴管理和子產品化的。可以看出,如果項目依賴非常多,維護這種依賴關系将會非常痛苦。而且全局變量 增多也經常會碰到沖突。

随後各種子產品化規範出現,比如commonjs,amd,umd。又出現了很多包管理工具,比如bower,npm,browserify。前端子產品化眼花缭亂, 随後tc39組織提出的esma推出了子產品化标準import/export, 至此js正式有了子產品化官方規範。

有了子產品化,我們通常會這樣組織代碼:

子產品化群組件化

我們上層子產品總是複用一些小的子產品,小的子產品依賴更小的子產品。構成了一個精妙的層級網絡,這很美妙。讓我們享受子產品的好處吧。

面向服務架構(Service-Oriented Architecture,SOA)又稱“面向服務的體系結構”,是Gartner于2O世紀9O年代中期提出的面向服務架構的概念。面向服務架構,從語義上說,它與面向過程、面向對象、面向元件一樣,是一種軟體組建及開發的方式。與以往的軟體開發、架構模式一樣,SOA隻是一種體系、一種思想,而不是某種具體的軟體産品。包括最近流行的micro service其實也是一種面向元件化和子產品化的思想。

SOA和micro service 的基本理念是将應用程式的不同功能單元(稱為服務)通過這些服務之間定義良好的接口和契約聯系起來。接口是采用中立的方式進行定義的,它應該獨立于實作服務的硬體平台、作業系統和程式設計語言。微服務通過将服務拆分成原子性,并通過服務治理完成系統的功能。它有很多好處,比如不限定語言和實作,大家可以選擇合适的技術棧。比如簡單性,通過這種拆解,系統從一個複雜系統,程式設計了若幹簡單的子系統,無疑降低了系統的複雜度。但是同樣有很多缺點,但這并不是本篇文章的重點。一個典型的微服務系統大概是這樣的:

子產品化群組件化

元件的概念在前端用的比較大多。元件和子產品表達的意思比較相近。我這裡講的元件,是比較狹隘的元件,專指前端中建構頁面的基本組成機關。元件是對業務邏輯的封裝,一個頁面由多個元件組成,元件又可以由其他元件組成。是以對元件進行分類很有必要。一般而言,元件分為下面四種類型:

展示型元件

給定的輸入,輸出一定。沒有互動邏輯

容器型元件

通常是作為資料容器,并将資料分發到子元件。

互動性元件

大多數元件都是互動元件,滿足使用者的互動需求。比如輸入框,下拉框等。

功能性元件

不在頁面中展示,不與使用者直接接觸。通常以高階元件的形式存在,給現有的元件注入功能。比如我需要實作一個可以拖動的功能,這個東西是抽象的,不是具體的元件。大概是這樣的用法:

<code>&lt;Drag &gt;</code>

<code>&lt;Modal /&gt;</code>

<code>&lt;/Drag&gt;</code>

一個典型的系統是這樣的:

子產品化群組件化

元件化就是基于可重用的目的,将一個大的軟體系統按照分離關注點的形式,拆分成多個獨立的元件,主要目的就是減少耦合。一個獨立的元件可以是一個軟體包、web服務、web資源或者是封裝了一些函數的子產品。這樣,獨立出來的元件可以單獨維護和更新而不會影響到其他的元件。提到元件化,不得不提web-component。有了web-component代碼就可以這麼寫:

<code>&lt;link rel="import" href="banner.html"&gt;</code>

<code>&lt;link rel="import" href="body.html"&gt;</code>

<code>&lt;link rel="import" href="footer.html"&gt;</code>

<code>&lt;template name="t-list"&gt;</code>

<code>&lt;t-banner&gt;&lt;/t-banner&gt;</code>

<code>&lt;t-body&gt;&lt;/t-body&gt;</code>

<code>&lt;t-footer&gt;&lt;/t-footer&gt;</code>

<code>&lt;/template&gt;</code>

這樣就可以通過分工提高開發效率,同時可以複用代碼。代碼也非常清爽。但是web-component 是一個新的标準,目前還沒有很好的實作,浏覽器支援性并不是很好。然後像react和 vue 這種 元件化思想的庫可以實作類似web-compoennt的效果,尤其是vuejs。

元件化的開發方式可以給我們顯著減少開發時間,我們可以根據自己的業務場景沉澱一些基礎元件和業務元件。使用者不必關心元件内部的實作,隻需要關心元件對外暴露的接口即可,可以說将開發者的關注點集中在業務邏輯本身。這也就是像ant-design,elementUI等元件庫火爆的原因。同時公司内部應該沉澱一些業務元件,供各個子系統使用。這樣如果元件需要修改,隻需要修改一處,其他系統隻需要更改依賴版本即可。在這裡要感謝各大公司提供的開源元件庫,比如阿裡的antd,滴滴的魔方,有贊的zent,餓了麼的elementUI,它們提供了數十種的基礎元件,幾乎覆寫了所有的非複雜業務場景,不僅如此,它們還提供了諸多行業解決方案,幫助大家更快更好的搭建系統。同時它們也有很多業務元件,比如滴滴會有呼叫車輛的元件,有贊有sku元件等等,這些都是項目業務場景沉澱下來的,雖然不具有廣泛适用性,但是其獨特的業務特點,導緻其稀缺性。如果小公司自己開發一套這樣的系統的化,可以說是難度非常大。因為一套被廣泛的系統不僅要元件豐富,文檔清晰,還要品質高(就是要經過充分測試,并且有足夠高的單元測試覆寫率),同時各大公司的元件庫通常都是和設計師反複溝通産出的,其設計品質也是蠻高的。

我們已經知道了子產品化的概念和子產品的好處,那麼如何劃分子產品層次就是一個很重要的問題。毫不誇張的說,如果元件劃分不得當, 子產品之間職責重疊或者覆寫不均勻會給使用者帶來不便,給系統帶啦額外負擔,甚至會誤導使用者,造成潛在的問題。那麼如何劃分子產品群組件系統呢?下一節為大家揭曉。

子產品群組件的劃分小到目錄結構大到資料流動,狀态管理,大大小小,内容繁雜。

什麼叫架構?揭開架構神秘的面紗,無非就是:分層+子產品化。任意複雜的架構,你也會發現架構師也就做了這兩件事。

前端的工作其實就是和DOM打交道的工作,隻不過是直接和間接的關系。為什麼這麼說呢?我們先來看下前端架構的發展。

就前端架構發展角度來看,前端發展大概經曆了四個階段。

直接操作DOM年代。

在這個年代,也就是原生操作DOM和Jquery等DOM操作封裝庫的時代,所有的效果都是直接操作DOM(前端直接操作或者架構封裝了操作DOM的過程)。這個時代,業務邏輯通常比較簡單,對于比較複雜的邏輯通常都是落地到後端。

MVC模式

這個時代,并沒有本質的差别,隻不過将操作DOM的代碼換了位置而已。

MV*

這個年代可以說是真正的将前端從直接和DOM打交道中解脫了出來。MV* 包括 react + 類flux架構, 都将DOM做了一層抽象,并不是簡單的封裝。它們的理念是資料驅動DOM,同時借助一定的手段(可以是虛拟DOM)将DOM操作最小化(DOM操作非常昂貴)。

為什麼DOM操作比較昂貴?一方面DOM API 比較多,大家可以看下DOM的API就知道了,非常的多。另一方面,DOM操作通常伴随這浏覽器的重排和重繪。大家可以這麼了解,操作DOM就好像計算機操作硬碟。執行JS就好像計算機操作記憶體。

拿react來舉例,react的核心思路可以用下面的僞代碼表示:

<code>const oldTree = render(oldState, oldProps);</code>

<code>const newTree = render(newState, newProps);</code>

<code>const patches = diff(oldTree, newTree) // 比較兩棵樹,計算patches</code>

<code>doPatch(patches) // 根據patches應用更新檔,将dom以”最小化“更新</code>

可以看出react 封裝了 dom操作, 将dom 建立(render)和 dom修改(doPactches)封裝了起來,對開發者來說是透明的。react 的思想是給定的state 和 props 渲染出相同或者相似的dom。是以react 開發者的核心關注點從DOM 移到了 狀态, 也是以誕生了很多狀态管理架構, 比如官方的flux,以及類flux 架構, reflux 和redux。以及vuejs 配套的vuex。這也是衆多資料管理架構越來越火爆的原因,比如mobx(MobX 是一個經過戰火洗禮的庫,它通過透明的函數響應式程式設計(transparently applying functional reactive programming - TFRP)使得狀态管理變得簡單和可擴充)和 rxjs(RxJS 是使用 Observables 的響應式程式設計的庫,它使編寫異步或基于回調的代碼更容易)

jquery 将 開發者從DOM 複雜的API 和浏覽器相容性泥潭中拉了出來。而資料管理架構将開發者徹底從DOM 關注 點中解放出來了。

MNV*

前面說了,前端的工作就是和DOM打交道。這一說法在MNV 面前顯得不是那麼确切。MNV真正将開發者脫離DOM,從令人诟病的DOM操作中完全解脫,并且不借助DOM實作原生的體驗。目前很多大公司的核心APP 都是基于這種混合式開發完成,比如支付寶,微信,釘釘等等。這種開發方式的好處結合了H5開發的速度和原生開發的性能的優勢。這種開發模式的原理很簡單,在原生APP中嵌入webview并基于一定的協定,完成用戶端和H5頁面的雙向通信。做過flex的人可能知道,flex可以将flex内部應用通過接口暴漏給window對象,js通過window對象通路flex中的方法。同時flex可以調用window上的方法,進而實作flex和js的雙向綁定。而MVN*的互動模式有點類似,H5和Native直接的互動基于的是名字叫JSBridge的東西。

記得在15年大學沒畢業的時候,我的講師跟我們介紹過一個名字叫PhoneGap的東西,其實它就是在web基礎上包了一層Native,然後通過Bridge技術使得js可以調用視訊,音頻,GPS,拍照等原生的功能。要進一步了解JSBridge,需要大家懂一點IOS和Android的相關知識。我給大家普及一點基礎的IOS和Android的知識。

IOS有一個叫UIWebView的東西,這個東西就像是一個浏覽器一樣,有浏覽器的基本功能,同時還可以調用一些原生的API,比如GPS定位。需要特别注意的是Safari浏覽器使用的浏覽器的控件和UIwebView并不是同一個,兩者在性能上有一定的差距。IOS調用webviewjs代碼是通過這樣實作的:

<code>webview.stringByEvaluatingJavaScriptFromString</code>

可以看出它和flex如出一轍,都是通過暴漏在window上的方法操作webview。實際項目中通常将window上挂在一個對象專門存在此類操作,比如window.webview

上面講了Native調用js,那麼js如何調用Native呢?其實js調用Native是基于一個事件代理。webview中所有的請求都會經過native中一個代理類,代理類進行攔截,如果是約定的格式(如調用原生方法)就解析,并調用原生方法。如果是其他的請求則放行。通常是下面的格式:

<code>jsbridge://class?callback/method?param1=value1&amp;param2=value2</code>

比如H5要實作一個原生的toast功能,可以這麼去寫:

<code>var url = 'jsbridge://feedback?onSuccess=xxx&amp;onFail=xxxx/doToast?icon=圖示&amp;title=文字';</code>

<code>var iframe = document.createElement('iframe');</code>

<code>iframe.style.width = '0px';</code>

<code>iframe.style.height = '0px';</code>

<code>iframe.style.display = 'none';</code>

<code>iframe.src = url;</code>

<code>document.body.appendChild(iframe);</code>

<code>setTimeout(function() {</code>

<code>iframe.remove();</code>

<code>}, 0);</code>

安卓的互動模式大同小異。知道了互動的具體原理,那麼對于不同端(IOS,Android)需要不同的方法。那麼這時候去建立一個中間層做統一,通常是前端的js-api形式存在。比如釘釘的做法是引入 dingtalk-promise-lwp.js 将native 調用方法封裝成一個簡單的類庫,并且屏蔽了不同端的差異。

還是以toast為例,代碼如下:

<code>dd.device.notification.toast({</code>

<code>type: "information", //toast的類型 alert, success, error, warning, information, confirm</code>

<code>text: '這裡是個toast', //提示資訊</code>

<code>duration: 3, //顯示持續時間,機關秒,最短2秒,最長5秒</code>

<code>delay: 0, //延遲顯示,機關秒,預設0, 最大限制為10</code>

<code>onSuccess : function(result) {</code>

<code>/*{}*/</code>

<code>},</code>

<code>onFail : function(err) {}</code>

<code>})</code>

Native

上面講述了hybrid app實作了js調用原生子產品的功能。本質上H5應用還是運作在webview中,通常一些子產品還是基于DOM的,即使是原生子產品也是基于代理的方式将js和 native聯系在一起。而Native 的方式則大又不同,它本質上就是Native,是以性能上又比hybrid好很多。比如廣為人知的react native(以下簡稱RN) 和 weex,RN本質上是以React的方式書寫代碼,然後通過RN這一個中間層,将React轉化并調用為原生Native代碼,反之亦然。當然RN也可以退化到hybrid的實作方式,即以webview作為橋接層,很多為了相容性都是這麼做的。

RN 将原生子產品通過導出供React使用,以下是RN官網示例代碼:

<code>@Override</code>

<code>public List&lt;NativeModule&gt; createNativeModules(</code>

<code>ReactApplicationContext reactContext) {</code>

<code>List&lt;NativeModule&gt; modules = new ArrayList&lt;&gt;();</code>

<code>modules.add(new ToastModule(reactContext));</code>

<code>return modules;</code>

<code>}</code>

<code>import { NativeModules } from 'react-native';</code>

<code>module.exports = NativeModules.ToastExample;</code>

<code>import ToastExample from './ToastExample';</code>

<code>ToastExample.show('Awesome', ToastExample.SHORT);</code>

技術架構在不斷的演進,我們的目錄結構,代碼層次,公共邏輯抽離都不一樣,我們看待問題的方式就要不斷演進,架構也要不斷演進。那麼架構發展中不變量是什麼?如何根據不變量來規劃軟體架構?要回答這個問題,首先要明白軟體開發的核心關注點,這個問題比較寬泛。那麼 我以前端開發的角度來描述下,作為前端開發的核心關注點有哪些。

頁面結構

如何劃分結構,如何組織頁面。前端發展前期,頁面結構劃分的方式就是div,”前端“将一個個元件通過div建構出來,複用性幾乎不存在。随着技術進一步發展,元件的思想深入人心,人們通過元件描述應用的結構,将程式看作是一個個元件組成,進而有了一大批關于元件化開發的best pratice。web-components就是元件化思想的官方表現。

頁面樣式

如何實作設計稿的效果。早期大家通過css2實作基本的網站布局。随着網站發展,互動越來越複雜,動畫的要求越來越高。諸如css3, canvas動畫技術浮出水面,這些技術都是為了更好的實作網站的樣式。為了提高書寫效率有了一批css預處理和後處理引擎,這些東西的出現都是為了提高樣式産出的效率。為了解決css全局覆寫的問題,又出現了css module的方案。為了使元件和樣式更加複用,又出現了style compoent技術等等,不勝枚舉。

頁面行為互動

如何完成産品的互動需求。網站早期頁面的互動就是簡單的登陸和送出留言等最基礎的功能。現在網頁做的越來越像一個app。複雜性可想而至,同一時刻,存在十幾種狀态更是家常便飯(已經請求了A資源,已經做了實名認證,已經通過了測驗,已經送出了留言等等等,如果我願意,我可以一直說下去)。那麼如何維持越來越複雜的狀态呢?由于狀态之間不是獨立存在的,某些狀态可以依賴于别的狀态,各種狀态交織在一起,令人頭疼。是以為了解決這個問題,出現了諸如redux, vuex, mobx等資料管理軟體,它們的目标就是做狀态管理。

除此之外,還有很多關注點,但是最基本的就是這三個。這三點也是大多數前端必須要關注的事情。大家往往需要根據自己項目的實際情況對三者進行某種程度的取舍。對于其他的崗位,比如後端工程師,一樣需要将後端的核心關注點找到,然後根據自己項目實際情況對關注點的實踐進行一定的取舍。

首先要明白,技術是服務業務的,業務依托于技術而存在。任何脫離業務的技術都是意淫。是以子產品群組件的劃分首先要站在業務角度劃分,其次才是技術角度。複雜的系統,最好先按業務領域橫向拆分成可獨立部署的子系統,每個子系統内部再按技術(主要是業務層和Web層)縱向拆分成不同的子產品。

舉個簡單電商的例子, 可能這樣劃分子產品:訂單、會員、商品、優惠、支付。每個子產品都隻負責自己的一塊業務,同時對其他子產品開放必要的接口。這種情況下,哪個子產品有變動,隻要接口不變完全不影響其他應用。而商品上架就不适合劃分為子產品, 因為其本身屬于不基礎服務,不是給系統使用的,而是面向使用者的,面向使用者的就會有頻繁的變動,就會不停比對使用者的需求。是以一個原則就是根據業務劃分出子產品, 是面向系統的,還是面向使用者的。

我們已經根據業務劃分了子產品,這時候需要将這些子產品組合起來,對外提供服務。因為最終産出的是産品,是面向使用者的。如果組織子產品,又落地到了技術問題。一個好的思路是分層,将基礎服務下沉,将業務服務上浮。實作了分層,但是子產品間通信又是問題,目前業界比較好的解決方案是通過MQ 和 配置中心解耦。子產品之間 并不知道自己依賴的系統或者被誰依賴,所有需要的東西動過MQ注冊,并通過配置中心管理,解決了子產品之間的通信問題。是以子產品劃分的另一個原則是單一職能原則。

其他的劃分原則還有高内聚低偶合,子產品大小規模适當,子產品的依賴關系适當等。又一個實體學名詞熵,是一種測量在動力學方面不能做功的能量總數,也就是當總體的熵增加,其做功能力也下降,熵的量度正是能量退化的名額。熵亦被用于計算一個系統中的失序現象,也就是計算該系統混亂的程度。軟體開發本質上是處理複雜度的過程,軟體總是趨向無序混亂發展。但是架構得當,子產品劃分合适,可以将軟體腐敗速度降低。

前面講述了子產品群組件劃分的依據。假設我們已經正确合理地劃分了子產品。是否就認為我們的代碼結構就足夠好,足夠複用,足夠友善調試呢?也不一定!原因在于元件内部通常也是有很多細小的邏輯的,由于這些邏輯沒有必要(或者我們認為沒有必要)抽離出來,在加上子產品群組件内部狀态複雜多變,各自牽扯就會造成子產品内部代碼難以複用和修改。

為什麼我們的代碼難以維護這邊文章講述了為什麼我們的代碼難以維護,其中講了一個重要原因就是 系統抽象級别不夠。那麼如何編寫抽象級别合适的代碼呢?其中一個原則就是我們的代碼描述的應該是一般邏輯,而将寫死和分支政策做成可配置。其中寫死部分和分支政策可以稱之為程式的中繼資料。

為一般邏輯編碼,為特殊邏輯配置。

我們平時工作中會使用一些設計模式,這些設計模式就是對具體業務邏輯進行抽象和提煉的結果。比如我們經常碰到同一時刻,同類型的Modal隻能出現一個的需求。我們對這樣的需求進行提煉,發現還有很多類似的邏輯。雖然它們本身所處的環境各不相同,但是我們可以将其中共性提煉出來,這就形成了單例模式。再比如我們現在要做 一個社保計算的功能,每一個省份的計算方式都不同,我們如何設計這個系統 ?先看下一般的寫法:

<code>function getHenanSocialInsurance(salary){</code>

<code>return salary * .4;</code>

<code>function getHebeiSocialInsurance(salary){</code>

<code>return salary * .2;</code>

<code>const calSocialInsurance = (province, salary) =&gt; {</code>

<code>let socialInsurance = 0;</code>

<code>if (province === 'henan') {</code>

<code>socialInsurance = getHenanSocialInsurance(salary);</code>

<code>} else if (province === 'hebei') {</code>

<code>// do something</code>

<code>socialInsurance = getHebeiSocialInsurance(salary);</code>

<code>// ....</code>

<code>return socialInsurance;</code>

再來看下政策模式:

<code>// 封裝的政策類</code>

<code>var strategies={</code>

<code>"henan":function (salary) {</code>

<code>"hebei":function (salary) {</code>

<code>};</code>

<code>// 調用方</code>

<code>var calSocialInsurance= (province, salary) =&gt; {</code>

<code>return strategies[province](salary);</code>

其實生活中還有很多類似的場景,我們對這樣的場景進一步抽象,提煉出政策模式。為通用過程先編碼,具體算法設計不同的實作,由調用方決定使用哪種政策(算法)。還有很多設計模式就不列舉了,大家可以私底下自己去看。其實抽象是很重要的一個概念,linux作業系統将一切都抽象為檔案,而檔案又是對位元組流的抽象。

cat /dev/urandom &gt; /dev/dsp

包括很多的程式設計方法,比如面向對象程式設計,是将生活中的物體抽象成擁有屬性和方法的對象,并通過封裝繼承等完成複雜的邏輯。函數式程式設計是對代碼邏輯運用數學的方法進行抽象,進而可以将數學中的定律(結合律,交換律,集合論等)運用到代碼中。正是因為抽象,我們才能一步步建構出堅實的上層系統。

純函數

提到純粹性,不提到另一個重要的概念-純函數。純函數其實就是數學中的函數。

函數是不同數值之間的特殊關系:每一個輸入值傳回且隻傳回一個輸出值。

它有一個重要的特征就是給定輸入,輸出是一定的。前面這句話可以從兩方面了解,一是給定輸入,輸出一定,一對一。另一點是沒有副作用(side effect),副作用指的是函數經過運算不會對外界産生影響,當然也不依賴外界。其實上面的兩點是一點,沒有副作用正是給定輸入輸出一定的前提。

換句話說,函數隻是兩種數值之間的關系:輸入和輸出。盡管每個輸入都隻會有一個輸出,但不同的輸入卻可以有相同的輸出。下圖展示了一個合法的從 x 到 y 的函數關系;

子產品化群組件化

(http://www.mathsisfun.com/sets/function.html)

這也就要求我們的代碼不依賴外部環境且不會對外界有影響,并且給定輸入,輸出一定(比如不可以是Math.random 這樣的邏輯)。

單一職責

純粹性除了上面的特征之外,還有一個重要特征是單一職責。即一個功能函數隻完成原子性功能,不要讓函數做非常多的事情,保證其功能的純粹。軟體設計原則中有一個單一職責原則,描述的是如果一個子產品承擔的職責過多,就等于把這些職責耦合在一起了。一個職責的變化可能會削弱或者抑制這個類完成其他職責的能力。這種耦合會導緻脆弱的設計,當發生變化時,設計會遭受到意想不到的破壞。而如果想要避免這種現象的發生,就要盡可能的遵守單一職責原則。此原則的核心就是解耦和增強内聚性。隻有子產品群組件滿足單一職責原則,我們的代碼才能夠更好地被修改,擴充,才能真正地擁抱變化。

總結

通過上面方法,我們将子產品群組件代碼切分成滿足單一職責的單元,并且每一個單元都盡可能地純粹。通過這樣的子產品群組件建構出來的系統才能保證穩定性和健壯性

底層基礎決定上層建築