原型模式和单例模式#
原型(Prototype)和单例(Singleton)都是常见的设计模式中的理念。
原型模式#
原型模式是一种创建型模式,能够让你复制对象,而又无需使代码依赖他们所属的类。 也就是说,原型模式能够提供一种方式让我们得到一个现有对象的克隆版,但又与该对象类进行解耦。
问题引出#
在一些“引用”类型语言中,如果我们要完全复制一个对象,而不是仅仅复制其引用。我们需要递归遍历并复制该对象的所有的成员变量,这样才能完成整个对象的复制。耗时耗力的同时,如果该类型还有成员私有变量,无法从外部访问到,那么这样的克隆很显然是不可行的。
你必须知道对象所属的类才能创建复制品, 所以代码必须依赖该类。 即使你可以接受额外的依赖性, 那还有另外一个问题: 有时你只知道对象所实现的接口, 而不知道其所属的具体类, 比如我们可以向方法的某个参数传入实现了某个接口的任何对象。
问题解决#
原型模式通过将克隆这个过程委派给被克隆的实际对象实例。通过声明一个克隆接口Cloneable
,对象只需要实现这个接口,并实现其中的Clone
方法,在该方法中,复制整个对象包括对象的成员变量。(这是Java中最传统的实现原型的方式),也可以复制成员的私有变量,因为绝大部分语言都是支持对象访问其同类对象的私有变量(Java,Cpp等支持,Javascript、python等不支持)。
在Javascript中实现原型模式,因为Js本身就是以prototype-base的语言,所以实现原型的最佳实践是使用
Object.create()
实现原型模式:jsconst personPrototype = { greet() { console.log(`Hello, my name is ${this.name}`); } }; const person1 = Object.create(personPrototype); person1.name = "Alice";
原型:支持克隆的对象。
代码示例#
这里使用操作系统文件系统作为示例,每一个文件(包括文件和文件夹)都是一个Inode
// 声明通用的克隆接口
import (
"fmt"
"testing"
)
type Inode interface {
clone() Inode
print(string)
}
type Folder struct {
name string
children []Inode
}
func (f *Folder) print(s string) {
fmt.Println("Folder name: ", f.name)
}
func (f *Folder) clone() Inode {
res := &Folder{
name: f.name,
}
for _, child := range f.children {
c := child.clone()
res.children = append(res.children, c)
}
return res
}
type File struct {
name string
}
func (f *File) print(s string) {
fmt.Println("file name: ", f.name)
}
func (f *File) clone() Inode {
return &File{
name: f.name,
}
}
go同时,也可以声明一个中心化原型注册表,将常用的原型放入注册表中,使用不同的key标识,传入工厂类一个合适的搜索参数(key),然后在注册表中对原型进行克隆返回克隆对象。
单例模式#
单例模式,保证一个类只有一个实例,并提供一个能够全局访问实例的节点。通常使用枚举类实现。
单例模式一次解决了上面的两个问题,所以其违反了单一职责原则。
单例模式需要注意的是,访问限制以及多线程环境下的并发问题。因为本质上相当于引入了一个全局变量供全局进行使用,所以存在以下问题:
- 线程安全
- 防止反射攻击
- 防止反序列化破坏
- JVM保证枚举值全局唯一,不会被克隆,不会被GC 在Java中使用枚举类可完美解决上述问题。
代码示例#
使用双重检查锁(Double-Checked Locking)实现单例模式
ype singleton struct{}
var single *singleton
var lock = &sync.Mutex{}
func getInstance() *singleton {
if single == nil {
lock.Lock()
defer lock.Unlock()
if single == nil {
time.Sleep(100 * time.Millisecond) // 故意延迟创建实例
single = &singleton{}
fmt.Println("Creating new Instance!")
} else {
fmt.Println("Get the Lock! But The Instance Already Exists")
}
} else {
fmt.Println("But The Instance Already Exists")
}
return single
}
func Test_run(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
getInstance()
}(i)
}
wg.Wait()
}
go使用Sync.Once()
优雅的实现单例:
var once sync.Once
func getInstanceOnce() *singleton {
once.Do(func() {
single = &singleton{}
})
return single
}
go