網(wǎng)站首頁 編程語言 正文
前言
如果一個類的有非常多的屬性,層級還很深。每次構(gòu)造起來,不管是直接構(gòu)造還是用建造者模式,都要對太多屬性進行復(fù)制,那么有沒有一種好的方式讓我們創(chuàng)建太的時候使用體驗更好一點呢? 今天的文章里就給大家介紹一種設(shè)計模式,來解決這個問題。
這篇內(nèi)容要說的是創(chuàng)造型設(shè)計模式里的原型模式,如果寫過點 JS 代碼的話,大家可能聽說過原型鏈這么個東西,原型模式在 JavaScript 實現(xiàn)里確實廣泛應(yīng)用,它那個面向?qū)ο蟾?Java、C++ 這些語言的面向?qū)ο蟮膶崿F(xiàn)方式還不太一樣,繼承其實是通過原型克隆出來,在拷貝出來的原型的基礎(chǔ)上再繼續(xù)添加或者修改來實現(xiàn)的。
什么是原型模式
通過復(fù)制、拷貝或者叫克隆已有對象的方式來創(chuàng)建新對象的設(shè)計模式叫做原型模式,被拷貝的對象也被稱作原型對象。
原型對象按照慣例,會暴露出一個 Clone 方法,給外部調(diào)用者一個機會來從自己這里“零成本”的克隆出一個新對象。
這里的“零成本”說的是,調(diào)用者啥都不用干,干等著,原型對象在 Clone 方法里自己克隆出自己,給到調(diào)用者,所以按照這個約定所有原型對象都要實現(xiàn)一個 Clone 方法。
type Prototype interface { Clone() SpecificType }
這里我們用UML類圖描述一下原型模式中各角色擁有的行為以及它們之間的關(guān)系
至于原型對象克隆自己的時候用的是深拷貝還是淺拷貝?可以先理解成是都用深拷貝,等完全掌握這種思想后,可以再根據(jù)實際情況,比如為了節(jié)省空間、以及減少編寫克隆方法的復(fù)雜度時可以兩者綜合使用。
原型模式更多的是闡述一種編程模式,并沒有限制我們用什么方式實現(xiàn)。比如下面這個深拷貝和淺拷貝結(jié)合使用的例子。
// 示例代碼來自:https://lailin.xyz/post/prototype.html package prototype import ( "encoding/json" "time" ) // Keyword 搜索關(guān)鍵字 type Keyword struct { word string visit int UpdatedAt *time.Time } // Clone 這里使用序列化與反序列化的方式深拷貝 func (k *Keyword) Clone() *Keyword { var newKeyword Keyword b, _ := json.Marshal(k) json.Unmarshal(b, &newKeyword) return &newKeyword } // Keywords 關(guān)鍵字 map type Keywords map[string]*Keyword // Clone 復(fù)制一個新的 keywords // updatedWords: 需要更新的關(guān)鍵詞列表,由于從數(shù)據(jù)庫中獲取數(shù)據(jù)常常是數(shù)組的方式 func (words Keywords) Clone(updatedWords []*Keyword) Keywords { newKeywords := Keywords{} for k, v := range words { // 這里是淺拷貝,直接拷貝了地址 newKeywords[k] = v } // 替換掉需要更新的字段,這里用的是深拷貝 for _, word := range updatedWords { newKeywords[word.word] = word.Clone() } return newKeywords }
使用原型模式的目的
使用原型模式的目的主要是為了節(jié)省創(chuàng)建對象所花費的時間和資源消耗,提升性能。
還有一點就是,比如全局配置對象這種也可以當(dāng)成原型對象,如果不想讓程序在運行時修改初始化好的原型對象,導(dǎo)致影響其他線程的程序執(zhí)行的時候,也可以用原型模式快速拷貝出一份,再在副本上做運行時自定義修改。
使用場景
當(dāng)對象的創(chuàng)建成本比較大,并且同一個類的不同對象間差別不大時(大部分屬性值相同),如果對象的屬性值需要經(jīng)過復(fù)雜的計算、排序,或者需要從網(wǎng)絡(luò)、DB等這些慢IO中獲取、亦或者或者屬性值擁有很深的層級,這時就是原型模式發(fā)揮作用的地方了。
因為對象在內(nèi)存中復(fù)制自己遠(yuǎn)比每次創(chuàng)建對象時重走一遍上面說的操作要來高效的多。
下面再來一個例子,讓我們更好的理解原型模式的優(yōu)點。
利用原型模式實現(xiàn)文檔樹
下面是一個類似 DOM 樹對象的例子,因為 DOM 對象往往層級會很深,那么要創(chuàng)建類似的DOM樹的時候能讓我們更好的理解原型模式的優(yōu)勢。
這個示例代碼來自:blog.ralch.com/articles/de…
package dom import ( "bytes" "fmt" ) // Node a document object model node type Node interface { // Strings returns nodes text representation String() string // Parent returns the node parent Parent() Node // SetParent sets the node parent SetParent(node Node) // Children returns the node children nodes Children() []Node // AddChild adds a child node AddChild(child Node) // Clone clones a node Clone() Node } // Element represents an element in document object model type Element struct { text string parent Node children []Node } // NewElement makes a new element func NewElement(text string) *Element { return &Element{ text: text, parent: nil, children: make([]Node, 0), } } // Parent returns the element parent func (e *Element) Parent() Node { return e.parent } // SetParent sets the element parent func (e *Element) SetParent(node Node) { e.parent = node } // Children returns the element children elements func (e *Element) Children() []Node { return e.children } // AddChild adds a child element func (e *Element) AddChild(child Node) { copy := child.Clone() copy.SetParent(e) e.children = append(e.children, copy) } // Clone makes a copy of particular element. Note that the element becomes a // root of new orphan tree func (e *Element) Clone() Node { copy := &Element{ text: e.text, parent: nil, children: make([]Node, 0), } for _, child := range e.children { copy.AddChild(child) } return copy } // String returns string representation of element func (e *Element) String() string { buffer := bytes.NewBufferString(e.text) for _, c := range e.Children() { text := c.String() fmt.Fprintf(buffer, "\n %s", text) } return buffer.String() }
上面的DOM對象-- Node、Element 這些都支持原型模式要求的 Clone 方法,那么有了這個原型克隆的能力后,假如我們想根據(jù)創(chuàng)建好的 DOM 樹上克隆出一個子分支作為一顆獨立的 DOM 樹對象的時候,就可以像下面這樣簡單地執(zhí)行 Node.Clone() 把節(jié)點和其下面的子節(jié)點全部拷貝出去。比我們使用構(gòu)造方法再重新構(gòu)造樹形結(jié)構(gòu)要方便許多。
下面的例子是用DOM樹結(jié)構(gòu)創(chuàng)建一下公司里的職級關(guān)系,然后還可以從任意層級克隆出一顆新的樹。
func main() { // 職級節(jié)點--總監(jiān) directorNode := dom.NewElement("Director of Engineering") // 職級節(jié)點--研發(fā)經(jīng)理 engManagerNode := dom.NewElement("Engineering Manager") engManagerNode.AddChild(dom.NewElement("Lead Software Engineer")) // 研發(fā)經(jīng)理是總監(jiān)的下級 directorNode.AddChild(engManagerNode) directorNode.AddChild(engManagerNode) // 辦公室經(jīng)理也是總監(jiān)的下級 officeManagerNode := dom.NewElement("Office Manager") directorNode.AddChild(officeManagerNode) fmt.Println("") fmt.Println("# Company Hierarchy") fmt.Print(directorNode) fmt.Println("") // 從研發(fā)經(jīng)理節(jié)點克隆出一顆新的樹 fmt.Println("# Team Hiearachy") fmt.Print(engManagerNode.Clone()) }
總結(jié)
關(guān)于原型模式的總結(jié),我們先來說一下原型模式的優(yōu)缺點。
原型模式的優(yōu)點
- 某些時候克隆比直接new一個對象再逐屬性賦值的過程更簡潔高效,比如創(chuàng)建層級很深的對象的時候,克隆比直接用構(gòu)造會方便很多。
- 可以使用深克隆方式保存對象的狀態(tài),可輔助實現(xiàn)撤銷操作。
原型模式的缺點
- clone方法位于類的內(nèi)部,當(dāng)對已有類進行改造的時候,需要修改代碼,違背了開閉原則。
- 當(dāng)實現(xiàn)深克隆時,需要編寫較為復(fù)雜的代碼,尤其當(dāng)對象之間存在多重嵌套引用時,為了實現(xiàn)深克隆,每一層對象對應(yīng)的類都必須支持深克隆。因此,深克隆、淺克隆需要運用得當(dāng)。
在項目中使用原型模式時,可能需要在項目初始化時就把提供克隆能力的原型對象創(chuàng)建好,在多線程環(huán)境下,每個線程處理任務(wù)的時候,用到了相關(guān)對象,可以去原型對象那里拷貝。不過適合當(dāng)作原型對象的數(shù)據(jù)并不多,所以原型模式在開發(fā)中的使用頻率并不高,如果有機會做項目架構(gòu),可以適當(dāng)考慮,確實需要再在項目中引入這種設(shè)計模式。
原文鏈接:https://juejin.cn/post/7174239262244601869
相關(guān)推薦
- 2022-10-10 Android實現(xiàn)藍(lán)牙串口通訊_Android
- 2022-10-01 iOS簡單實現(xiàn)輪播圖效果_IOS
- 2022-10-07 React中父組件如何獲取子組件的值或方法_React
- 2022-06-15 go語言方法集為類型添加方法示例解析_Golang
- 2022-04-23 R語言兩組變量特征相關(guān)關(guān)系熱圖繪制畫法_R語言
- 2023-12-26 layui彈窗傳值
- 2022-11-14 python?pycharm中使用opencv時沒有代碼自動補全提示的解決方案_python
- 2023-08-01 Antd的Select組件二次封裝
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支