DDD - Domain-driven design荔仁,領(lǐng)域驅(qū)動設(shè)計伍宦。很多人喜歡把事情分的很清,這是后端的設(shè)計思想乏梁,對我們前端有什么影響呢次洼?其實不然,
javascript
是一種面向?qū)ο蟮拈_發(fā)語言遇骑,既然是面向?qū)ο舐艋伲覀兙涂梢赃m用任何的設(shè)計思想。
當(dāng)我們在討論DDD
的時候质蕉,我們在討論什么势篡?
什么是Domain
?如何driven design
模暗?這些太過拘于概念禁悠,我們以一些實際的例子,來看看兑宇,DDD
是一種什么思想碍侦。
以個人為例,我們擁有年齡隶糕、身高瓷产、體重這樣的屬性,先排出其他影響因素枚驻,我們假設(shè)身高和體重只受到年齡影響濒旦,由此我們可以進(jìn)行如下設(shè)計,在class Person
中再登,包含三個Property
分別是age
年齡尔邓、weight
體重,height
身高锉矢。
通常來說梯嗽,在年齡的增長過程中,體重沽损、身高會有不同階段的變化灯节,我們假設(shè)變化的公式為weight = f(age)
和height = f(age)
則有下面序列圖
也就是weight
和height
的變化是受age
影響的,那么如果我們統(tǒng)一封裝在Person
類中镶奉,這個類的領(lǐng)域就會變得很奇怪入篮,所謂基本的屬性绿饵,為什么類本身會需要對基本的屬性有方法去處理變化呢惠窄?
領(lǐng)域驅(qū)動設(shè)計在這里引入了 值對象 的概念凑兰,根據(jù) 單一職責(zé) 和 迪米特法則雳窟,把weight
和height
這樣的具有變化特點(diǎn)的值负间,轉(zhuǎn)為對象的形式苛秕,也就是面試對象三大特點(diǎn)之一的 封裝唯笙,所謂 封裝變化螟蒸。
// Weight.js
class Weight {
#weight = "opps";
constructor(age) {
this.#weight = this._handleAgeStateWeight(age);
}
getWeight() {
return this.#weight;
}
_handleAgeStateWeight(age) {
if (age < 24) return "normal weight";
else return "fat weight";
}
}
// Height.js
class Height {
#height = "opps";
constructor(age) {
this.#height = this._handleAgeStateHeight(age);
}
getHeight() {
return this.#height;
}
_handleAgeStateHeight(age) {
if (age < 24) return "normal height";
else return "no growing";
}
}
// Person.js
class Person {
#age = 24;
#weight = "opps";
#height = "opps";
constructor(age = 24) {
this.#age = age;
this.#weight = new Weight(this.#age).getWeight();
this.#height = new Height(this.#age).getHeight();
}
getPersonInfo() {
console.log("age", this.#age);
console.log("weight", this.#weight);
console.log("height", this.#height);
}
}
看到這里,你們是否對DDD
領(lǐng)域驅(qū)動設(shè)計有一定了解了呢崩掘?
個人拙見:值對象的概念是 單一職責(zé) 和 迪米特法則 的合集七嫌,更注重顆粒度,無疑苞慢,這種做法實現(xiàn)了面向?qū)ο蟮母邇?nèi)聚低耦合诵原,但是同樣的,在內(nèi)存開銷和項目初期增加了巨大的問題挽放。
領(lǐng)域驅(qū)動設(shè)計是一種大型項目構(gòu)建比較推薦的設(shè)計绍赛,通過這樣的設(shè)計,我們可以對項目形成較好對管理成本辑畦,接手項目的時候吗蚌,可以對最小顆粒進(jìn)行版本迭代和更新從而產(chǎn)生最小的影響。
這不是必須要求你的項目就一定要這樣做
因為初期成本太高纯出,不適合初創(chuàng)型公司以這樣的形式去設(shè)計項目蚯妇。
在前端里,領(lǐng)域驅(qū)動設(shè)計又意味著什么呢
很多時候暂筝,作為一個前端開發(fā)箩言,我們會以頁面作為最小顆粒,也就是在設(shè)計的時候焕襟,我們會認(rèn)為一個頁面就是一個 實體(實體可以理解為值對象的集合)陨收,一個menu
的模塊,就是 聚合(聚合可以理解為實體的集合)鸵赖。在理解上面這兩個名詞之后务漩,我們從最小顆粒(頁面)開始看起。
頁面通常包含 N 個組件卫漫,在React
或者Vue
中,我們使用Ant Design
或者ElementUi
肾砂,這里我僅用React
舉例列赎。
一個React
的頁面Container
,是一些Component
的合集,我們把頁面的變化進(jìn)行了封裝包吝,因為我們認(rèn)為業(yè)務(wù)的變化會產(chǎn)生頁面的變化饼煞。這一點(diǎn)沒錯,但是诗越,相反的砖瞧,我們?nèi)ピ敿?xì)的說應(yīng)該叫 業(yè)務(wù)的變化會引起部分頁面中組件的變化,我們一概而論的把變化封裝在了更高一層的頁面上嚷狞,實際上就是沒有進(jìn)行良好的組件拆分块促。
那么我們繼續(xù)解耦,我們將每一個Component
作為一個值對象去看床未,這樣在業(yè)務(wù)變化的時候竭翠,我們就可以進(jìn)一步的去修改特定的組件。
那么薇搁,領(lǐng)域驅(qū)動設(shè)計到此為止了嗎斋扰?
在我看來,這是開始啃洋。
從第二節(jié)看下來传货,通篇的文字都是業(yè)務(wù)的變化,這就是最大的問題宏娄。我們所有的設(shè)計问裕,都是基于業(yè)務(wù)。這并不符合DDD
绝编,因為DDD
是領(lǐng)域驅(qū)動設(shè)計僻澎,也就是在DDD
看來,設(shè)計的根本是因為領(lǐng)域十饥。那么領(lǐng)域是如何去定義窟勃,我們就需要進(jìn)行探討了。
我們還是看一個比較常見的場景逗堵。
在上面這個需求里秉氧,我們可以看到有Select
和Table
構(gòu)成的一個Component
,如果我們將此Component
作為值對象的話蜒秤,如果我Select
區(qū)域要新增一些條件汁咏,或者Table
需要有改動,進(jìn)行的修改作媚,都是需要在此Component
上攘滩,所以,在DDD
中纸泡,我認(rèn)為漂问,構(gòu)成的最小顆粒應(yīng)該是由 UI 庫提供的組件是值對象,而我們的Component
級別的封裝應(yīng)該是 實體 ,構(gòu)成的頁面應(yīng)該是 聚合蚤假。
// RecentSales.jsx
import Header from "./component/Header";
import Content from "./component/Content";
export default () => {
return [
<Header key="recentSalesHeader" />,
<Content key="recentSalesContent" />
];
};
// ./component/Header/index.jsx
import Title from "./entity/Title";
import SelectTime from "./entity/SelectTime";
export default () => {
return [<Title key="headerTitle" />, <SelectTime key="headerSelectTime" />];
};
// ./component/Header/entity/Title/index.jsx
import { Typography } from "antd";
const { Title, Text } = Typography;
export default () => {
return [<Title key="titleTitle" />, <Text key="titleText" />];
};
// ./component/Header/entity/SelectTime/index.jsx
import { DatePicker } from "antd";
export default () => {
return <DatePicker />;
};
// ./component/Content/index.jsx
import SalesTable from "./entity/SalesTable";
export default () => {
return <SalesTable />;
};
// ./component/Content/entity/SalesTable/index.jsx
import { Table } from "antd";
export default () => {
return <Table />;
};
講到這里栏饮,我希望你有所收獲,無論是你對DDD
領(lǐng)域驅(qū)動設(shè)計的不屑一顧磷仰,還是你從中有了新的見解袍嬉。
之后,我要開始講一些 奇怪 的東西了灶平。
剛剛我們不斷的在說伺通,DDD
是在進(jìn)行封裝最小變化,那么問題來了民逼,<Table />
這樣的組件泵殴,具有可擴(kuò)展性嗎?所有的可拓展性拼苍,都是基于樣式層面的調(diào)整笑诅,如果我想對表格的渲染過程有不同的設(shè)計呢?如果我對表格的渲染需要惰性分頁疮鲫,滾動分頁呢吆你?你會發(fā)現(xiàn),并不支持俊犯,所以妇多,為什么中臺系統(tǒng)是ant design
這類 UI 庫主打,是因為通用性極強(qiáng)燕侠,此類庫提供了“最佳”實踐者祖,特殊的需求要自己寫【钔看起來沒什么問題七问,但是在我看來,不具備闊拓展性茫舶。
所以我希望的械巡,或者我所設(shè)想的組件庫,應(yīng)該充分利用extends
饶氏,達(dá)到如下的效果:
也就是我們不在自己構(gòu)建一個全新的值對象讥耗,而是在通用的基礎(chǔ)上,去繼承并極大的增加復(fù)用性疹启。
以上都是個人的一些見解啦古程,DDD
是一個還在摸索階段的東西,來吧喊崖,關(guān)注 ihap 技術(shù)黑洞挣磨,我是 ihap肥少菲宴。