前言:笔者在最开始写程序的时候经常会遇到一种情况,例如更改一个字段、或者添加一种小功能,就要把原来写过的东西几乎废弃掉,或者更改大量以前写过的代码。又或者自己写的东西时间久了再去回顾,完全找不到到时为什么这么写的头绪,如果遇到了Bug更是无法快速定位在哪里小范围出现的问题。如果你也经常遇到这种问题,就说明你现阶段非常需要学习下设计模式了。
在网上经常说的设计模式有23种,也有一些更多的设计模式,无非也是从这些设计模式中变种而来。如果让笔者来形容什么是设计模式,我认为设计模式是:一种思想,一种模式,一种套路,一种解决问题的高效策略。
有说的不正确或者不准确的地方欢迎留言指正
有什么有趣的写作技巧或者想法欢迎大家给我留言,大家的帮助是我写下去最有效的动力
细胞分裂这个概念大家应该都知道,一个细胞,从自己的内部慢慢酝酿生成另一个和自己一模一样的个体,当生产完全时,会从原来的本体剥离,变成2个一模一样的细胞,这种以本体为原型,从内部生产另外一个一模一样实体的行为在设计模式中也有相应的表现,那就是笔者今天要跟大家介绍的原型模式。
原型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
参考链接
示例
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Custom.Log;
using System.Threading;
public class LifeCycleLog: ICloneable
{
public string description;
public object Clone()
{
return MemberwiseClone();
}
}
public class Cell:ICloneable
{
public int id;
public string name;
public LifeCycleLog lifeCycleLog;
public Cell(int tempID,string tempName)
{
this.id = tempID;
this.name = tempName;
lifeCycleLog = new LifeCycleLog();
Thread.Sleep(1000);
}
public object Clone()
{
Cell tempCell = new Cell(this.id, this.name);
tempCell.lifeCycleLog.description = lifeCycleLog.description;
return tempCell;
}
public void Log()
{
this.Log($"细胞ID:{id},细胞名称:{name},日志:{lifeCycleLog.description}");
}
}![2.gif](https://upload-images.jianshu.io/upload_images/7643202-987c70b99206d626.gif?imageMogr2/auto-orient/strip)
调用
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Custom.Log;
using System.Threading;
using System.Threading.Tasks;
public class PrototypePatternComponent : MonoBehaviour
{
void Start()
{
Task.Run(() =>
{
Cell Cell = new Cell(0, "编号00");
Cell.lifeCycleLog.description = "生命周期日志00";
Cell.Log();
this.Log("【准备复制】");
Thread.Sleep(500);
for (int i = 0; i < 5; i++)
{
Cell tempCell = (Cell)Cell.Clone();
tempCell.Log();
}
this.Log("【复制完毕】");
});
}
}
打印日志
这样我们最粗糙的原型模式就算是完成了,是不感觉很简单呢。但是为什么说粗糙呢?因为还有更有效的拷贝方式,那当然要用C#自带的MemberwiseClone函数啦。我们只需要继承ICloneable接口将原来的Clone函数稍加改动即可,速度快的飞起~
public object Clone()
{
return MemberwiseClone();
}
打印日志
MemberwiseClone函数对应的文档中我们知道,他这种复制属于浅复制,在这里笔者就要引入两个概念了,浅拷贝与深拷贝。
首先我们先从一个示例看什么是浅拷贝和深拷贝
示例代码
void Start()
{
Cell cell = new Cell(0, "编号00");
cell.lifeCycleLog.description = "生命周期日志00";
Cell cell01 = (Cell)cell.Clone();
cell01.id = 1;
cell01.name = "编号01";
cell01.lifeCycleLog.description = "改动后的生命周期日志";
cell.Log();
cell01.Log();
}
打印信息
大家应该会发现,笔者只更改了cell01中的lifeCycleLog的描述,但是所有的描述都进行了更改,因为这就是使用了浅拷贝的缘故。
浅拷贝
浅复制正在创建一个新对象,然后将当前对象的非静态字段复制到新对象。如果字段是值类型 - >执行字段的逐位复制; 对于引用类型 - >复制引用,但不复制引用对象; 因此原始对象及其克隆引用相同的对象。 在C#和VB.NET中,浅层复制由对象MemberwiseClone()方法完成。
深拷贝
深层复制正在创建一个新对象,然后将当前对象的非静态字段复制到新对象。如果字段是值类型 - >执行字段的逐位复制。如果字段是引用类型 - >执行引用对象的新副本。
不知道这回大家是否明白为什么会造成lifeCycleLog描述一致的原因呢?最初笔者曾认为浅拷贝是
Cell cell01 = cell;
这种形式的23333~现在看并没有那么“浅”。但是又有同学会表示疑问,string类型也是引用类型,而且stirng有这种特殊的享元模式,他问什么没有一同改变呢?这个要从string的定义来看了。string本质是只读的,所以有任何的更改都会创建一个新的副本。所以string这种引用类型并不会一起改变。
所以说需要深拷贝的话我们可以这么写,既保证了速度又进行了深度的拷贝。
public object DeepClone()
{
Cell tempCell = (Cell)MemberwiseClone();
tempCell.lifeCycleLog = (LifeCycleLog)this.lifeCycleLog.Clone();
return tempCell;
}
打印日志
原型模式其实就是从一个对象再创建另一个可定制的对象,而且不需要知道任何创建的细节
当然深拷贝还有以下几种方式
- 用反射进行克隆(Activator.CreateInstance)Unity C#基础之 反射反射,程序员的快乐
- 使用序列化进行克隆(序列化成XML/JSON等然后再反序列化)Unity 基础之LitJSON
- 表达式目录树(笔者目前没研究过)
如果大家在项目中有这种需要实例化多个一样的类且初始化比较耗时的需求时,原型模式是个不错的选择~