如果一个类的属性很多,层级还是很深的。这样是对的,因为我的对象很大,所以你创建它的时候忍耐……然后每次你要创建它的时候都忍耐?有没有什么好的方法可以让我们创造更好的用户体验?在今天的文章中,我将介绍一种设计模式来解决这个问题。本文介绍的是创意设计模式中的原型模式。事实上,原型模式在Java、Go等编程语言中使用并不频繁。如果你写过一些JS代码,你可能听说过原型链这样的东西。原型模式在JavaScript实现中确实被广泛使用。它的面向对象实现与Java、C++等语言的面向对象实现并不相同。继承实际上是通过原型进行克隆。在出来的原型基础上,继续添加或修改实现。什么是原型模式通过复制、复制或克隆现有对象来创建新对象的设计模式称为原型模式,被复制的对象也称为原型对象。根据约定,原型对象将公开一个Clone方法,使外部调用者有机会以“零成本”从自身克隆一个新对象。这里的“零成本”是指调用者什么都不用做,只需要等待,原型对象在Clone方法中克隆自己,交给调用者,所以按照这个约定,所有的原型对象都必须实现一个Clone方法.typePrototypeinterface{Clone()SpecificType}这里我们使用UML类图来描述原型模式下各个角色的行为以及它们之间的关系。还是浅拷贝?可以理解为优先使用深拷贝。充分掌握这个思路后,可以根据实际情况将两者结合使用,比如节省空间,降低clone方法编写的复杂度。原型模式更多的是一种编程模式,并没有限制我们如何实现它。比如下面这个深拷贝和浅拷贝结合的例子。//示例代码来自:https://lailin.xyz/post/prototype.htmlpackageprototypeimport("encoding/json""time")//关键词搜索keywordtypeKeywordstruct{wordstringvisitintUpdatedAt*time.Time}//这里clone使用序列化和反序列化来深拷贝func(k*Keyword)Clone()*Keyword{varnewKeywordKeywordb,_:=json.Marshal(k)json.Unmarshal(b,&newKeyword)return&newKeyword}//关键字keywordsmaptypeKeywordsmap[string]*Keyword//clone复制一个新的keywords//updatedWords:需要更新的关键词列表,因为从数据库中获取的数据往往是一个数组func(wordsKeywords)Clone(updatedWords[]*Keyword)Keywords{newKeywords:=Keywords{}fork,v:=rangewords{//这里是浅拷贝,直接复制地址newKeywords[k]=v}//替换需要的updated字段,这里是对_,word:=rangeupdatedWords{newKeywords[word.word]=word.Clone()}returnnewKeywords}的深拷贝使用pr的目的ototype模式使用原型模式的目的主要是为了节省创建对象所花费的时间和资源消耗,提高性能。还有一点就是,比如全局配置对象也可以作为原型对象。如果不希望程序在运行时修改初始化的原型对象,影响其他线程的执行,也可以使用原型模式快速复制一份原型对象,然后在运行时进行自定义修改.使用场景当创建对象的成本比较高,且同一类的不同对象之间差异不大(大部分属性值相同)时,如果对象的属性值需要进行复杂的计算、排序,或者需要从网络、DB等中获取,这些缓慢的IO获取,或者属性值都有很深的层次,这就是原型模式发挥作用的地方。因为对象在内存中复制自己比每次创建对象都要经历上面的操作效率要高得多。这里再举一个例子,让我们更好的理解原型模式的优势。使用原型模式实现文档树下面是一个DOM树对象的例子,因为DOM对象往往是有层次的,所以当我们想创建一个类似的DOM树时,更能理解原型模式的优势。这个展示示例代码来自:https://blog.ralch.com/articles/design-patterns/golang-prototype/packagedomimport("bytes""fmt")//NodeadocumentobjectmodelnodetypeNodeinterface{//Stringsreturnsnodes文本表示String()string//Parent返回节点parentParent()Node//SetParent设置节点parentSetParent(nodeNode)//Children返回节点childrennodesChildren()[]Node//AddChild添加一个子节点nodeAddChild(childNode)//Clone克隆一个节点Clone()Node}//Element表示文档对象中的一个元素)*Element{return&Element{text:text,parent:nil,children:make([]Node,0),}}//父元素返回元素parentfunc(e*Element)Parent()Node{returne.parent}//SetParent设置元素parentfunc(e*Element)SetParent(nodeNode){e.parent=node}//Children返回elementchildrenelementsfunc(e*Element)Children()[]Node{returne.children}//AddChild添加一个子元素func(e*Element)AddChild(childNode){copy:=child.Clone()copy.SetParent(e)e.children=append(e.children,copy)}//克隆复制特定元素。请注意,该元素成为//neworphantreefunc(e*Element)Clone()Node{copy:=&Element{text:e.text,parent:nil,children:make([]Node,0),}for_,child:=rangee.children{copy.AddChild(child)}returncopy}//字符串返回elementfunc(e*Element)String()string{buffer:=bytes.NewBufferString(e.text)for_,c:=rangee.Children(){text:=c.String()fmt.Fprintf(buffer,"\n%s",text)}returnbuffer.String()}上面的DOM对象——Node和Element都支持原型模式需要的Clone方法,所以有了原型克隆的能力,如果我们想将基于创建的DOM树的子分支克隆为一个独立的DOM树对象,我们可以简单地执行Node.Clone()如下,将所有的节点及其子节点复制出来进行比较,方便我们使用构造方法重构树结构。下面的例子是利用DOM树结构创建公司中的等级关系,然后从任意层级克隆一棵新的树。funcmain(){//等级节点--总监directorNode:=dom.NewElement("工程总监")//等级节点--研发经理engManagerNode:=dom.NewElement("工程经理")engManagerNode.AddChild(dom.NewElement("LeadSoftwareEngineer"))//研发经理是总监的下属directorNode.AddChild(engManagerNode)directorNode.AddChild(engManagerNode)//办公室经理也是总监的下属officeManagerNode:=dom.NewElement("OfficeManager")directorNode.AddChild(officeManagerNode)fmt.Println("")fmt.Println("#CompanyHierarchy")fmt.Print(directorNode)fmt.Println("")//克隆一个新树fmt来自R&Dmanager节点.Println("#TeamHiearachy")fmt.Print(engManagerNode.Clone())}原型模式总结关于原型模式的总结,我们先说说原型模式的优缺点。Prototype模式的优点有时候克隆比直接new一个对象然后一个一个赋值的过程更简洁高效。比如在创建一个深层次的对象时,克隆比直接使用构造要方便的多。可以使用深度克隆的方式来保存对象的状态,可以辅助undo操作的执行。原型模式的缺点是克隆方法位于类内部。修改已有类时,需要修改代码,违反了开闭原则。在实现深度克隆时,需要编写更复杂的代码,尤其是对象之间存在多个嵌套引用时。为了实现深度克隆,每一层对象对应的类都必须支持深度克隆。因此,深克隆和浅克隆需要合理使用。在项目中使用原型模式时,可能需要在项目初始化时创建一个提供克隆能力的原型对象。在多线程环境中,每个线程在处理任务时,都使用相关的对象,这些对象可以从原型对象中复制过来。.但是适合作为原型对象的数据并不多,所以在开发中使用原型模式的频率并不高。如果有机会做项目架构,可以适当考虑。确实很有必要在项目中引入这种设计模式。
