单例模式、原型模式和Builder模式
2013 年 8 月 7 日
这篇文章记录三个设计模式,因为他们都比较简单,因此较短的篇幅就可以描述完,就把这三个放在一起。
单例模式
单例模式,就是为了确保全局唯一。在Go语言里实现单例模式,好像也没啥好办法,一般就是:
- 全局变量
- sync.Once
然而我之前写过 Python中实现单例模式的四种方式
,
究其本质,其实也是通过全局变量来实现的,要么就是全局变量,要么就是类变量,而类本身也是全局唯一的。
原型模式
在Go语言里,我倒是很少见到使用原型模式,原型模式是这样一种情况:通常来说我们新建一个对象都是直接实例化比如:
new(SomeStruct) make(SomeStruct) SomeStruct{}
但是原型模式并不直接通过类或者结构体来实例化,而是通过一个实例对自身进行clone来得到一个新的实例(其实一般情况也就是clone
方法自己偷偷的实例化了一个对象然后把属性copy过去),原型模式和直接实例化的最大区别就是通过原型模式,可以直接把实例clone时
自身的状态也一起copy过去。
我从来没直接用过原型模式,不过GORM里有,我们来看看他是怎么实现的:
func (stmt *Statement) clone() *Statement { newStmt := &Statement{ Table: stmt.Table, Model: stmt.Model, Dest: stmt.Dest, ReflectValue: stmt.ReflectValue, Clauses: map[string]clause.Clause{}, Distinct: stmt.Distinct, Selects: stmt.Selects, Omits: stmt.Omits, Joins: map[string][]interface{}{}, Preloads: map[string][]interface{}{}, ConnPool: stmt.ConnPool, Schema: stmt.Schema, Context: stmt.Context, RaiseErrorOnNotFound: stmt.RaiseErrorOnNotFound, } for k, c := range stmt.Clauses { newStmt.Clauses[k] = c } for k, p := range stmt.Preloads { newStmt.Preloads[k] = p } for k, j := range stmt.Joins { newStmt.Joins[k] = j } return newStmt }
瞧,他就是新建一个,然后把属性copy过去。
Builder模式
Builder模式适用于这么一种情况:无法或不想一次性把实例的所有属性都给出,而是要分批次、分条件构造,举个例子,不是这样实例化:
a := SomeStruct{1, 2, "hello"}
而是这样:
a := SomeStruct{} a.setAge(1) a.setMonth(2) if (blabla) { a.setSlogan("hello") }
这种模式的一个用处就是,上古时期的动态网站就是靠Builder模式来生成HTML的,大家都这么玩:
a := emptyPage{} a.addTag("p", "balblabla") a.addTag("br")
可能最后就会生成这么一个HTML:
balblabla
可能有人要问了,为啥不直接初始化实例的时候,把属性放进去呢?
setXXX
Builder模式除了上面例子中的形态,还有一种变种,那就是链式:
a := SomeStruct{} a = a.setAge(1).setMonth(2).setSlogan("hello")
那这是怎么实现的呢?其实就是在每一个函数的最后,把实例自身返回。那Builder模式在哪里有用到呢?比如 go-resty
:
// Create a Resty Client client := resty.New() resp, err := client.R(). SetQueryParams(map[string]string{ "page_no": "1", "limit": "20", "sort":"name", "order": "asc", "random":strconv.FormatInt(time.Now().Unix(), 10), }). SetHeader("Accept", "application/json"). SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F"). Get("/search_result") // Sample of using Request.SetQueryString method resp, err := client.R(). SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more"). SetHeader("Accept", "application/json"). SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F"). Get("/show_product")
就是通过Builder模式来构造请求的。