满纸荒唐言,一把心酸泪,都云作者痴,谁解其中味。 技术博客 心情随笔
设计模式:原型
2025/3/17 105

导航

1前言

2原型模式实现

3栈与堆

4浅拷贝与深拷贝

5总结

1 前言

原型模式非常简单,用一句话即可概括:以一个已存在的对象作为原型,快速克隆产生出新的对象。一些对象的构造逻辑包含复杂的过程,比如I/O操作、网络通信、第三方交互等,通过原型模式可以跳过这些过程,高效的克隆出目标对象。来源:https://www.wubayue.com

2 原型模式实现

原型模式类图

孙悟空通过实现ICloneable接口,拥有了拔毛哈气变猢狲的法力,变出的小猢狲都是自己的分身,小猢狲们不用再经过破石诞生、东海学艺、五百年修行这些构造过程,看来孙悟空已基本掌握了原型模式。来源:https://www.wubayue.com

// 孙悟空
public class SunWuKong : ICloneable
{
    public SunWuKong()
    {
        // 破石诞生
        // 东海学艺
        // 五百年修行
        // ......
    }

    public object Clone()
    {
        // 拔毛哈气变猢狲
        return this.MemberwiseClone();
    }
} 

3 堆与栈

从软件设计角度,原型模式其实已经介绍完了,让一个类通过实现接口来拥有自我克隆的能力,就是原型模式的精髓。但如果深入到对象克隆的技术细节,就需要介绍一些其它知识了,堆与栈是计算机发展史上两个古老的词汇,它们在早期编程语言的演进过程中诞生。

栈(Stack)

值类型的数据通常都是存储在栈中,栈的诞生来源于模块化编程,大概在冯·诺依曼时代,计算机编程并没有模块化,虽然现在难以想象,但当时程序受限于内存空间,只能从头到尾执行一段逻辑,没有函数,更没有面向对象。后来随着硬件的发展,内存空间逐渐变大,人们发现从头到尾的一大段逻辑难以理解维护,如果将其拆解为多个部分,不论开发还是维护就容易多了,这便是模块化编程。模块化编程将一大段复杂逻辑拆解到多个函数当中,这些函数相互调用就像乐高积木一样任意拼装,比如函数A调用函数B,函数B调用函数C,如果仔细观察函数嵌套调用时的内存使用,会发现一个规律,最后被调用的函数使用的内存空间总是最先被释放,因为在函数嵌套调用时,越是后被调用的函数,生命周期越是早结束。针对这种情况,人们便发明了后进先出的栈数据结构与工作模式:

栈的数据结构与工作原理

栈是一项古老的技术,它贯穿了计算机软硬件的发展史,因此CPU和操作系统针对栈都做了适配,比如CPU有专门的栈指针寄存器以及栈指令,操作系统提供了栈的基础管理。

针对栈的特性,总结如下:
1、后进先出的数据结构,只能在顶端进行数据操作;
2、自我内存管理,进栈自动分配,出栈自动回收;
3、只能为大小确定的数据分配内存,不能动态分配内存;
4、可用空间不大但执行效率高;

堆(Heap)

引用类型的数据通常储在堆中,堆的概念也是伴随着计算机的发展而出现的,早期的计算机内存空间有限,每个字节的使用都需要精打细算,在编写代码时,就需要计算好变量使用的内存大小。后来随着内存空间越来越大,程序越来越复杂,开发人员发现如果在程序运行时能动态的申请和使用内存空间,会更方便,于是提出了堆的概念。堆实际就是程序运行时动态分配的一片内存空间,它并不像栈一样有特殊的数据结构,今天仍然可以从C语言的malloc()和free()函数窥见堆的诞生:

void mallocDemo()
{
    // 申请堆空间
    int* ptr = (int*)malloc(sizeof(int));
    // 堆空间赋值
    *ptr = 100;
    printf("%d", *ptr);
    //释放堆空间
    free(ptr);
} 

针对堆的特性,总结如下。
1、在程序执行时动态分配内存空间(栈中数据通常在编译期确认大小);
2、存储数据量远大于栈(受限于物理内存与磁盘交换空间);
3、执行效率低于栈;来源:https://www.wubayue.com

4 浅拷贝与深拷贝

有了堆、栈知识铺垫,我们再回到孙悟空,孙悟空拔毛哈气变出的小猢狲有点像提线木偶,但西游记有一章写真假美猴王,假六耳猕猴跟真孙悟空简直一模一样,不光朝夕相处的唐僧师徒分不清,天上的神仙们也分不清。同样是克隆,从成品效果来看,显然六耳猕猴的完成度远大于孙悟空拔毛哈气变出的小猢狲。如果从软件设计角度,我觉得浅拷贝和深拷贝比较容易解释其中原因:

浅拷贝与深拷贝示意图

浅拷贝(Shallow Copy)

不拷贝引用的子对象。

深拷贝(Deep Copy)

递归拷贝所有子对象,克隆出一个完全独立的新对象。来源:https://www.wubayue.com

// 孙悟空
[Serializable]
public class SunWuKong : ICloneable
{
    // 深拷贝
    public object Clone()
    {
        // 克隆六耳猕猴
        object obj;
        using (MemoryStream ms = new MemoryStream())
        {
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(ms, this);
            ms.Seek(0, SeekOrigin.Begin);
            obj = bf.Deserialize(ms);
            ms.Close();
        }
        return (SunWuKong)obj;
    }
} 

5 总结

原型模式是非常简单的一种设计模式,它用于满足对象的快速克隆需求,在克隆过程中,需要注意浅拷贝与深拷贝的处理细节。像C#这种托管型(内存管理托管给框架)语言,只需关注业务逻辑对应的是浅拷贝还是深拷贝即可,而像C++这种非托管型语言,如果对一个包含指针的对象进行多次浅拷贝,则会带来重复析构时的空指针使用问题,造成程序崩溃。来源:https://www.wubayue.com

<全文完>