XingPiaoLiang's

Back

原型模式和单例模式#

原型(Prototype)和单例(Singleton)都是常见的设计模式中的理念。

原型模式#

原型模式是一种创建型模式,能够让你复制对象,而又无需使代码依赖他们所属的类。 也就是说,原型模式能够提供一种方式让我们得到一个现有对象的克隆版,但又与该对象类进行解耦。

问题引出#

在一些“引用”类型语言中,如果我们要完全复制一个对象,而不是仅仅复制其引用。我们需要递归遍历并复制该对象的所有的成员变量,这样才能完成整个对象的复制。耗时耗力的同时,如果该类型还有成员私有变量,无法从外部访问到,那么这样的克隆很显然是不可行的。

你必须知道对象所属的类才能创建复制品, 所以代码必须依赖该类。 即使你可以接受额外的依赖性, 那还有另外一个问题: 有时你只知道对象所实现的接口, 而不知道其所属的具体类, 比如我们可以向方法的某个参数传入实现了某个接口的任何对象。

问题解决#

原型模式通过将克隆这个过程委派给被克隆的实际对象实例。通过声明一个克隆接口Cloneable,对象只需要实现这个接口,并实现其中的Clone方法,在该方法中,复制整个对象包括对象的成员变量。(这是Java中最传统的实现原型的方式),也可以复制成员的私有变量,因为绝大部分语言都是支持对象访问其同类对象的私有变量(Java,Cpp等支持,Javascript、python等不支持)。

在Javascript中实现原型模式,因为Js本身就是以prototype-base的语言,所以实现原型的最佳实践是使用Object.create()实现原型模式:

const personPrototype = {
   greet() {
       console.log(`Hello, my name is ${this.name}`);
   }
};

const person1 = Object.create(personPrototype);
person1.name = "Alice";
js

原型:支持克隆的对象。

代码示例#

这里使用操作系统文件系统作为示例,每一个文件(包括文件和文件夹)都是一个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
设计模式-创建型-原型与单例
https://astro-pure.js.org/blog/prototype-singleton
Author erasernoob
Published at May 20, 2025
Comment seems to stuck. Try to refresh?✨