C# Json反序列化 C# 实现表单的自动化测试<通过程序控制一个网页> 验证码处理类:UnCodebase.cs + BauDuAi 读取验证码的值(并非好的解决方案) 大话设计模式:原型模式 C# 深浅复制 MemberwiseClone
C# Json反序列化
Json反序列化有两种方式【本人】,一种是生成实体的,方便处理大量数据,复杂度稍高,一种是用匿名类写,方便读取数据,较为简单。
使用了Newtonsoft.Json,可以自行在nuget中导入
Json源数据:
var data = "{'jingdong_ldop_receive_trace_get_responce':{'code':'0','querytrace_result':{'data':[{'opeTitle':'快递签收','opeRemark':'货物已交付京东物流','opeTime':'2011/04/17 18:23:20','opeName':'京东快递','waybillCode':'bc00000001'},{'opeTitle':'站点验货','opeRemark':'货物已分配,等待配送','opeTime':'2011/04/23 08:29:56','opeName':'京东快递','waybillCode':'bc00000001'},{'opeTitle':'配送员收货','opeRemark':'配送员开始配送,请您准备收货,','opeTime':'2011/04/23 08:36:28','opeName':'京东快递','waybillCode':'bc00000001'},{'opeTitle':'妥投','opeRemark':'货物已完成配送,感谢您选择京东物流','opeTime':'2011/04/23 09:47:13','opeName':'京东快递','waybillCode':'bc00000001'}],'messsage':'成功','code':100}}}";
第一种:是用匿名方法生成,按照Json的格式,从外到内,一步一步写,非数组用new{},数组用new[]{},名字必须与json中名字一致
//使用匿名变量构造
{
var JsonDataForVar = Newtonsoft.Json.JsonConvert.DeserializeAnonymousType(data, new
{
jingdong_ldop_receive_trace_get_responce = new
{
code = string.Empty,
querytrace_result = new
{
data = new[] {
new {
opeTitle=string.Empty,
opeRemark=string.Empty,
opeTime=string.Empty,
waybillCode=string.Empty,
opeName=string.Empty
}
}
}
}
});
foreach (var item in JsonDataForVar.jingdong_ldop_receive_trace_get_responce.querytrace_result.data)
{
var a = item.opeTitle;
var b = item.opeRemark;
var c = item.opeTime;
var d = item.waybillCode;
var f = item.opeName;
}
}
第二种:使用实体
//实体部分,建议从内到外写实体,名字必须与json中名字一致【简便方法,搜索Json转实体,将Json字符串导入,自动生成实体】
public class Item
{
public string opeTitle { get; set; }
public string opeRemark { get; set; }
public string opeTime{ get; set; }
public string waybillCode { get; set; }
public string opeName { get; set; }
}
public class JdResult
{
public string code { get; set; }
public string msg { get; set; }
public List<Item> data { get; set; }
}
public class JdResponce
{
public string code { get; set; }
public JdResult querytrace_result { get; set; }
}
public class JdBody
{
public JdResponce jingdong_ldop_receive_trace_get_responce { get; set; }
}
//方法
//使用实体构造
{
var JsonDataForClass = Newtonsoft.Json.JsonConvert.DeserializeAnonymousType(data, new JdBody());
foreach (var item in JsonDataForClass.jingdong_ldop_receive_trace_get_responce.querytrace_result.data)
{
var a = item.opeTitle;
var b = item.opeRemark;
var c = item.opeTime;
var d = item.waybillCode;
var f = item.opeName;
}
}
C# 实现表单的自动化测试<通过程序控制一个网页>
2018-04-24 09:22 by 天才卧龙, 85 阅读, 0 评论, 收藏, 编辑
学历代表你的过去,能力代表你的现在,学习代表你的将来
十年河东,十年河西,莫欺少年穷
学无止境,精益求精
C# 实现表单的自动化测试,这标题看着就来劲!那么,如何通过C#程序控制一个网页呢?
在此,以一个简单的案例来展示,我们要控制百度,并让其自动搜索‘helloworld’
利用VS新建一个控制台应用程序,并添加如下引用:



书写如下程序即可:
class Program
{
private static bool ie_Read = false;
static void Main(string[] args)
{
SHDocVw.InternetExplorer ie = new SHDocVw.InternetExplorer();
ie.DocumentComplete += ie_DocumentComplete;
ie.Navigate("https://www.baidu.com/");
ie.Visible = true;
System.Threading.Thread.Sleep(1000);
mshtml.HTMLDocument doc = ie.Document;
doc.getElementById("kw").innerText = "hello world";
doc.getElementById("su").click();
Console.Read();
}
private static void ie_DocumentComplete(object pDisp, ref object URL)
{
ie_Read = true;
}
}
这样,就会启动IE浏览器,并打开百度,自动输入‘helloworld’并搜索!

当然,如果要想学到真正的控制网页,还需要很多知识需要学习,比如:mshtml.HTMLDocument 的结构,doc 的方法属性等,本篇博客仅仅提供一个入门。
更详细的信息大家可参考:
https://www.cnblogs.com/lzyGod/p/6639103.html 《良心好文》
https://www.cnblogs.com/kissdodog/p/3725774.html 《良心好文》
https://www.cnblogs.com/endv/p/5983110.html 《用法好文》
@陈卧龙的博客
验证码处理类:UnCodebase.cs + BauDuAi 读取验证码的值(并非好的解决方案)
2018-04-23 17:44 by 天才卧龙, 4 阅读, 0 评论, 收藏, 编辑
主要功能:变灰,去噪,等提高清晰度等
代码类博客,无需多说,如下:
public class UnCodebase
{
public Bitmap bmpobj;
public UnCodebase(Bitmap pic)
{
bmpobj = new Bitmap(pic); //转换为Format32bppRgb
}
/// <summary>
/// 根据RGB,计算灰度值
/// </summary>
/// <param name="posClr">Color值</param>
/// <returns>灰度值,整型</returns>
private int GetGrayNumColor(Color posClr)
{
return (posClr.R * 19595 + posClr.G * 38469 + posClr.B * 7472) >> 16;
}
/// <summary>
/// 灰度转换,逐点方式
/// </summary>
public Bitmap GrayByPixels()
{
for (int i = 0; i < bmpobj.Height; i++)
{
for (int j = 0; j < bmpobj.Width; j++)
{
int tmpValue = GetGrayNumColor(bmpobj.GetPixel(j, i));
bmpobj.SetPixel(j, i, Color.FromArgb(tmpValue, tmpValue, tmpValue));
}
}
return bmpobj;
}
/// <summary>
/// 去图形边框
/// </summary>
/// <param name="borderWidth"></param>
public Bitmap ClearPicBorder(int borderWidth)
{
for (int i = 0; i < bmpobj.Height; i++)
{
for (int j = 0; j < bmpobj.Width; j++)
{
if (i < borderWidth || j < borderWidth || j > bmpobj.Width - 1 - borderWidth ||
i > bmpobj.Height - 1 - borderWidth)
bmpobj.SetPixel(j, i, Color.FromArgb(255, 255, 255));
}
}
return bmpobj;
}
/// <summary>
/// 灰度转换,逐行方式
/// </summary>
public Bitmap GrayByLine()
{
Rectangle rec = new Rectangle(0, 0, bmpobj.Width, bmpobj.Height);
BitmapData bmpData = bmpobj.LockBits(rec, ImageLockMode.ReadWrite, bmpobj.PixelFormat);
// PixelFormat.Format32bppPArgb);
// bmpData.PixelFormat = PixelFormat.Format24bppRgb;
IntPtr scan0 = bmpData.Scan0;
int len = bmpobj.Width * bmpobj.Height;
int[] pixels = new int[len];
Marshal.Copy(scan0, pixels, 0, len);
//对图片进行处理
int GrayValue = 0;
for (int i = 0; i < len; i++)
{
GrayValue = GetGrayNumColor(Color.FromArgb(pixels[i]));
pixels[i] = (byte)(Color.FromArgb(GrayValue, GrayValue, GrayValue)).ToArgb(); //Color转byte
}
bmpobj.UnlockBits(bmpData);
return bmpobj;
}
/// <summary>
/// 得到有效图形并调整为可平均分割的大小
/// </summary>
/// <param name="dgGrayValue">灰度背景分界值</param>
/// <param name="CharsCount">有效字符数</param>
/// <returns></returns>
public void GetPicValidByValue(int dgGrayValue, int CharsCount)
{
int posx1 = bmpobj.Width;
int posy1 = bmpobj.Height;
int posx2 = 0;
int posy2 = 0;
for (int i = 0; i < bmpobj.Height; i++) //找有效区
{
for (int j = 0; j < bmpobj.Width; j++)
{
int pixelValue = bmpobj.GetPixel(j, i).R;
if (pixelValue < dgGrayValue) //根据灰度值
{
if (posx1 > j) posx1 = j;
if (posy1 > i) posy1 = i;
if (posx2 < j) posx2 = j;
if (posy2 < i) posy2 = i;
}
;
}
;
}
;
// 确保能整除
int Span = CharsCount - (posx2 - posx1 + 1) % CharsCount; //可整除的差额数
if (Span < CharsCount)
{
int leftSpan = Span / 2; //分配到左边的空列 ,如span为单数,则右边比左边大1
if (posx1 > leftSpan)
posx1 = posx1 - leftSpan;
if (posx2 + Span - leftSpan < bmpobj.Width)
posx2 = posx2 + Span - leftSpan;
}
//复制新图
Rectangle cloneRect = new Rectangle(posx1, posy1, posx2 - posx1 + 1, posy2 - posy1 + 1);
bmpobj = bmpobj.Clone(cloneRect, bmpobj.PixelFormat);
}
/// <summary>
/// 得到有效图形,图形为类变量
/// </summary>
/// <param name="dgGrayValue">灰度背景分界值</param>
/// <param name="CharsCount">有效字符数</param>
/// <returns></returns>
public void GetPicValidByValue(int dgGrayValue)
{
int posx1 = bmpobj.Width;
int posy1 = bmpobj.Height;
int posx2 = 0;
int posy2 = 0;
for (int i = 0; i < bmpobj.Height; i++) //找有效区
{
for (int j = 0; j < bmpobj.Width; j++)
{
int pixelValue = bmpobj.GetPixel(j, i).R;
if (pixelValue < dgGrayValue) //根据灰度值
{
if (posx1 > j) posx1 = j;
if (posy1 > i) posy1 = i;
if (posx2 < j) posx2 = j;
if (posy2 < i) posy2 = i;
}
;
}
;
}
;
//复制新图
Rectangle cloneRect = new Rectangle(posx1, posy1, posx2 - posx1 + 1, posy2 - posy1 + 1);
bmpobj = bmpobj.Clone(cloneRect, bmpobj.PixelFormat);
}
/// <summary>
/// 得到有效图形,图形由外面传入
/// </summary>
/// <param name="dgGrayValue">灰度背景分界值</param>
/// <param name="CharsCount">有效字符数</param>
/// <returns></returns>
public Bitmap GetPicValidByValue(Bitmap singlepic, int dgGrayValue)
{
int posx1 = singlepic.Width;
int posy1 = singlepic.Height;
int posx2 = 0;
int posy2 = 0;
for (int i = 0; i < singlepic.Height; i++) //找有效区
{
for (int j = 0; j < singlepic.Width; j++)
{
int pixelValue = singlepic.GetPixel(j, i).R;
if (pixelValue < dgGrayValue) //根据灰度值
{
if (posx1 > j) posx1 = j;
if (posy1 > i) posy1 = i;
if (posx2 < j) posx2 = j;
if (posy2 < i) posy2 = i;
}
;
}
;
}
;
//复制新图
Rectangle cloneRect = new Rectangle(posx1, posy1, posx2 - posx1 + 1, posy2 - posy1 + 1);
return singlepic.Clone(cloneRect, singlepic.PixelFormat);
}
/// <summary>
/// 平均分割图片
/// </summary>
/// <param name="RowNum">水平上分割数</param>
/// <param name="ColNum">垂直上分割数</param>
/// <returns>分割好的图片数组</returns>
public Bitmap[] GetSplitPics(int RowNum, int ColNum)
{
if (RowNum == 0 || ColNum == 0)
return null;
int singW = bmpobj.Width / RowNum;
int singH = bmpobj.Height / ColNum;
Bitmap[] PicArray = new Bitmap[RowNum * ColNum];
Rectangle cloneRect;
for (int i = 0; i < ColNum; i++) //找有效区
{
for (int j = 0; j < RowNum; j++)
{
cloneRect = new Rectangle(j * singW, i * singH, singW, singH);
PicArray[i * RowNum + j] = bmpobj.Clone(cloneRect, bmpobj.PixelFormat); //复制小块图
}
}
return PicArray;
}
/// <summary>
/// 返回灰度图片的点阵描述字串,1表示灰点,0表示背景
/// </summary>
/// <param name="singlepic">灰度图</param>
/// <param name="dgGrayValue">背前景灰色界限</param>
/// <returns></returns>
public string GetSingleBmpCode(Bitmap singlepic, int dgGrayValue)
{
Color piexl;
string code = "";
for (int posy = 0; posy < singlepic.Height; posy++)
for (int posx = 0; posx < singlepic.Width; posx++)
{
piexl = singlepic.GetPixel(posx, posy);
if (piexl.R < dgGrayValue) // Color.Black )
code = code + "1";
else
code = code + "0";
}
return code;
}
/// <summary>
/// 去掉噪点
/// </summary>
/// <param name="dgGrayValue"></param>
/// <param name="MaxNearPoints"></param>
public Bitmap ClearNoise(int dgGrayValue, int MaxNearPoints)
{
Color piexl;
int nearDots = 0;
int XSpan, YSpan, tmpX, tmpY;
//逐点判断
for (int i = 0; i < bmpobj.Width; i++)
for (int j = 0; j < bmpobj.Height; j++)
{
piexl = bmpobj.GetPixel(i, j);
if (piexl.R < dgGrayValue)
{
nearDots = 0;
//判断周围8个点是否全为空
if (i == 0 || i == bmpobj.Width - 1 || j == 0 || j == bmpobj.Height - 1) //边框全去掉
{
bmpobj.SetPixel(i, j, Color.FromArgb(255, 255, 255));
}
else
{
if (bmpobj.GetPixel(i - 1, j - 1).R < dgGrayValue) nearDots++;
if (bmpobj.GetPixel(i, j - 1).R < dgGrayValue) nearDots++;
if (bmpobj.GetPixel(i + 1, j - 1).R < dgGrayValue) nearDots++;
if (bmpobj.GetPixel(i - 1, j).R < dgGrayValue) nearDots++;
if (bmpobj.GetPixel(i + 1, j).R < dgGrayValue) nearDots++;
if (bmpobj.GetPixel(i - 1, j + 1).R < dgGrayValue) nearDots++;
if (bmpobj.GetPixel(i, j + 1).R < dgGrayValue) nearDots++;
if (bmpobj.GetPixel(i + 1, j + 1).R < dgGrayValue) nearDots++;
}
if (nearDots < MaxNearPoints)
bmpobj.SetPixel(i, j, Color.FromArgb(255, 255, 255)); //去掉单点 && 粗细小3邻边点
}
else //背景
bmpobj.SetPixel(i, j, Color.FromArgb(255, 255, 255));
}
return bmpobj;
}
/// <summary>
/// 扭曲图片校正
/// </summary>
public Bitmap ReSetBitMap()
{
Graphics g = Graphics.FromImage(bmpobj);
Matrix X = new Matrix();
// X.Rotate(30);
X.Shear((float)0.16666666667, 0); // 2/12
g.Transform = X;
// Draw image
//Rectangle cloneRect = GetPicValidByValue(128); //Get Valid Pic Rectangle
Rectangle cloneRect = new Rectangle(0, 0, bmpobj.Width, bmpobj.Height);
Bitmap tmpBmp = bmpobj.Clone(cloneRect, bmpobj.PixelFormat);
g.DrawImage(tmpBmp,
new Rectangle(0, 0, bmpobj.Width, bmpobj.Height),
0, 0, tmpBmp.Width,
tmpBmp.Height,
GraphicsUnit.Pixel);
return tmpBmp;
}
// <summary>
/// 得到灰度图像前景背景的临界值 最大类间方差法,yuanbao,2007.08
/// </summary>
/// <returns>前景背景的临界值</returns>
public int GetDgGrayValue()
{
int[] pixelNum = new int[256]; //图象直方图,共256个点
int n, n1, n2;
int total; //total为总和,累计值
double m1, m2, sum, csum, fmax, sb; //sb为类间方差,fmax存储最大方差值
int k, t, q;
int threshValue = 1; // 阈值
int step = 1;
//生成直方图
for (int i = 0; i < bmpobj.Width; i++)
{
for (int j = 0; j < bmpobj.Height; j++)
{
//返回各个点的颜色,以RGB表示
pixelNum[bmpobj.GetPixel(i, j).R]++; //相应的直方图加1
}
}
//直方图平滑化
for (k = 0; k <= 255; k++)
{
total = 0;
for (t = -2; t <= 2; t++) //与附近2个灰度做平滑化,t值应取较小的值
{
q = k + t;
if (q < 0) //越界处理
q = 0;
if (q > 255)
q = 255;
total = total + pixelNum[q]; //total为总和,累计值
}
pixelNum[k] = (int)((float)total / 5.0 + 0.5); //平滑化,左边2个+中间1个+右边2个灰度,共5个,所以总和除以5,后面加0.5是用修正值
}
//求阈值
sum = csum = 0.0;
n = 0;
//计算总的图象的点数和质量矩,为后面的计算做准备
for (k = 0; k <= 255; k++)
{
sum += (double)k * (double)pixelNum[k]; //x*f(x)质量矩,也就是每个灰度的值乘以其点数(归一化后为概率),sum为其总和
n += pixelNum[k]; //n为图象总的点数,归一化后就是累积概率
}
fmax = -1.0; //类间方差sb不可能为负,所以fmax初始值为-1不影响计算的进行
n1 = 0;
for (k = 0; k < 256; k++) //对每个灰度(从0到255)计算一次分割后的类间方差sb
{
n1 += pixelNum[k]; //n1为在当前阈值遍前景图象的点数
if (n1 == 0) { continue; } //没有分出前景后景
n2 = n - n1; //n2为背景图象的点数
if (n2 == 0) { break; } //n2为0表示全部都是后景图象,与n1=0情况类似,之后的遍历不可能使前景点数增加,所以此时可以退出循环
csum += (double)k * pixelNum[k]; //前景的“灰度的值*其点数”的总和
m1 = csum / n1; //m1为前景的平均灰度
m2 = (sum - csum) / n2; //m2为背景的平均灰度
sb = (double)n1 * (double)n2 * (m1 - m2) * (m1 - m2); //sb为类间方差
if (sb > fmax) //如果算出的类间方差大于前一次算出的类间方差
{
fmax = sb; //fmax始终为最大类间方差(otsu)
threshValue = k; //取最大类间方差时对应的灰度的k就是最佳阈值
}
}
return threshValue;
}
/// <summary>
/// 3×3中值滤波除杂,yuanbao,2007.10
/// </summary>
/// <param name="dgGrayValue"></param>
public void ClearNoise(int dgGrayValue)
{
int x, y;
byte[] p = new byte[9]; //最小处理窗口3*3
byte s;
//byte[] lpTemp=new BYTE[nByteWidth*nHeight];
int i, j;
//--!!!!!!!!!!!!!!下面开始窗口为3×3中值滤波!!!!!!!!!!!!!!!!
for (y = 1; y < bmpobj.Height - 1; y++) //--第一行和最后一行无法取窗口
{
for (x = 1; x < bmpobj.Width - 1; x++)
{
//取9个点的值
p[0] = bmpobj.GetPixel(x - 1, y - 1).R;
p[1] = bmpobj.GetPixel(x, y - 1).R;
p[2] = bmpobj.GetPixel(x + 1, y - 1).R;
p[3] = bmpobj.GetPixel(x - 1, y).R;
p[4] = bmpobj.GetPixel(x, y).R;
p[5] = bmpobj.GetPixel(x + 1, y).R;
p[6] = bmpobj.GetPixel(x - 1, y + 1).R;
p[7] = bmpobj.GetPixel(x, y + 1).R;
p[8] = bmpobj.GetPixel(x + 1, y + 1).R;
//计算中值
for (j = 0; j < 5; j++)
{
for (i = j + 1; i < 9; i++)
{
if (p[j] > p[i])
{
s = p[j];
p[j] = p[i];
p[i] = s;
}
}
}
// if (bmpobj.GetPixel(x, y).R < dgGrayValue)
bmpobj.SetPixel(x, y, Color.FromArgb(p[4], p[4], p[4])); //给有效值付中值
}
}
}
}
上述代码用于变灰,去噪点等功能,下面我们结合BaiDuAi 来实现读取验证码的功能<实验证明,baiduAi提供的Api仅仅能读取比较清晰的文字,像验证码这种,读取的不是太好>
namespace BaiduAi.ORC
{
class Program
{
static string APP_ID = "";
static string API_KEY = "";
static string SECRET_KEY = "";
static void Main(string[] args)
{
string Pth = Environment.CurrentDirectory;
Image img = Image.FromFile(Pth + "/ajax.png");
Bitmap bitmap = new Bitmap(img);
UnCodebase Ub = new UnCodebase(bitmap);
bitmap = Ub.GrayByPixels();
bitmap.Save(Pth + "/he.png");
int GV = Ub.GetDgGrayValue();
Ub.GetPicValidByValue(bitmap, GV);
Ub.ClearNoise(GV, 2);
bitmap.Save(Pth + "/12.png");
GeneralBasicDemo();
Console.ReadKey();
}
public static void GeneralBasicDemo()
{
string Pth = Environment.CurrentDirectory;
Image img = Image.FromFile(Pth + "/12.png");
Bitmap bitmap = new Bitmap(img);
UnCodebase Ub = new UnCodebase(bitmap);
Ub.ClearNoise(10000, 400000);
bitmap.Save(Pth + "/ajax1.png");
//
var client = new Baidu.Aip.Ocr.Ocr(API_KEY, SECRET_KEY);
client.Timeout = 60000; // 修改超时时间
var image = File.ReadAllBytes(Pth + "/ajax.png");
// 调用通用文字识别, 图片参数为本地图片,可能会抛出网络等异常,请使用try/catch捕获
var result = client.GeneralBasic(image);
Console.WriteLine(result);
// 如果有可选参数
var options = new Dictionary<string, object>{
{"language_type", "CHN_ENG"},
{"detect_direction", "true"},
{"detect_language", "true"},
{"probability", "true"}
};
// 带参数调用通用文字识别, 图片参数为本地图片
result = client.GeneralBasic(image, options);
Console.WriteLine(result);
}
public static void GeneralBasicUrlDemo()
{
var client = new Baidu.Aip.Ocr.Ocr(API_KEY, SECRET_KEY);
client.Timeout = 60000; // 修改超时时间
var url = "http://www.xiaozhu.com/ajax.php?op=AJAX_GetVerifyCode&nocache=1524468631393";
// 调用通用文字识别, 图片参数为远程url图片,可能会抛出网络等异常,请使用try/catch捕获
var result = client.GeneralBasicUrl(url);
Console.WriteLine(result);
// 如果有可选参数
var options = new Dictionary<string, object>{
{"language_type", "CHN_ENG"},
{"detect_direction", "true"},
{"detect_language", "true"},
{"probability", "true"}
};
// 带参数调用通用文字识别, 图片参数为远程url图片
result = client.GeneralBasicUrl(url, options);
Console.WriteLine(result);
}
}
}
上述的AppID AppKey等是百度开发者相关的参数!
首先我们来看看验证的原图:

这样一个彩色的验证码,
变灰和去噪点处理后,变成了这样:

彩色的字母变成了灰色/黑色
最后调用百度的接口,读取图片的内容!

验证码的内容是AvHv
Api读成了:aviv 和 H 两个部分,而且还多了. : 等符号、所有本篇并非读取验证码的解决方案!
此外说说BaiduAi : http://ai.baidu.com/

看到了吗?各种人工智能!百度还是相当牛逼的!呵呵呵!上述验证码识别用到的是文字识别 所谓文字识别,百度提供了识别车牌号,身份证号,税务号等等,总之,我认为所谓的车牌号。身份证号等都应该是非常清晰的图片!而不像验证码,他亲妈都认不出来!特别是12306的!擦X
有时间在研究这些东西吧!
@陈卧龙的博客
大话设计模式:原型模式
2018-04-20 17:14 by 天才卧龙, 6 阅读, 0 评论, 收藏, 编辑
学无止境,精益求精
十年河东,十年河西,莫欺少年穷
学历代表你的过去,能力代表你的现在,学习代表你的将来
上篇博客介绍了C# 深浅复制,其实原型模式讲的主要就是对象的深浅复制 参考: C# 深浅复制 MemberwiseClone
OK,言归正传
原型模式的概念:
用原型实例指定创建对象的种类,并且通过拷贝这些原型对象来创建新的对象!
原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。
首先简单介绍下原型模式的应用场景,如下:
一般在初始化的信息不发生变动的情况下,不用重新初始化对象,而是动态的获得对象运行时的状态,克隆是最好的方法。这既隐藏了对象创建的细节,又对性能是大大的提高。
上述的解释似乎难以弄明白,现在我们来解释下:
我们知道,对象是通过构造函数来创建的,如果在构造函数中存在着大量的业务逻辑,那么这个对象的创建就会消耗不少的资源,这也意味着对象的创建过程会消耗掉一定的时间。如果我们需要一个‘类似’的对象,可以采用克隆的方式来创建这个对象,这样就可以不执行构造函数来创建一个新的对象,这个对象的创建将会大大提升性能!
因此,使用原型模式有以下条件:
1、初始化信息不发生变动时,可考虑使用原型模式来克隆对象
2、当通过构造函数创建一个新的实例会消耗过多资源时,可考虑使用原型模式来克隆对象
3、当需要动态获取对象运行时状态时,可考虑使用原型模式来克隆对象
总之:原型模式的基本理念就是在符合一定的场景下,通过克隆对象来动态获取对象的运行时状态并大大提升对象创建的效率!
何为克隆对象呢?
.net 在system 命名空间中提供了ICloneable 接口,其中就是唯一的一个方法Clone(),实现这个接口就可以完成原型模式了。
那么关于克隆的具体实现,大家还要参考上一篇博客:C# 深浅复制 MemberwiseClone
OK,上述从概念性的东西介绍原型模式,那么,我们现在进入实战:
1、原型模式的UML类图

2、原型模式在生活中的体现:
现实生活中,也有很多原型设计模式的例子,例如,细胞分裂的过程,一个细胞的有丝分裂产生两个相同的细胞;还有西游记中孙悟空变出后孙的本领和火影忍者中鸣人的隐分身忍术等。下面就以孙悟空为例子来演示下原型模式的实现。
具体的实现代码如下:
///孙悟空的的变化就是原型模式
class Client
{
static void Main(string[] args)
{
// 孙悟空 原型
MonkeyKingPrototype prototypeMonkeyKing = new ConcretePrototype("MonkeyKing");
// 变一个
MonkeyKingPrototype cloneMonkeyKing = prototypeMonkeyKing.Clone() as ConcretePrototype;
Console.WriteLine("Cloned1:\t"+cloneMonkeyKing.Id);
// 变两个
MonkeyKingPrototype cloneMonkeyKing2 = prototypeMonkeyKing.Clone() as ConcretePrototype;
Console.WriteLine("Cloned2:\t" + cloneMonkeyKing2.Id);
Console.ReadLine();
}
}
/// <summary>
/// 孙悟空原型
/// </summary>
public abstract class MonkeyKingPrototype
{
public string Id { get; set; }
public MonkeyKingPrototype(string id)
{
this.Id = id;
}
// 克隆方法,即孙大圣说“变”
public abstract MonkeyKingPrototype Clone();
}
/// <summary>
/// 创建具体原型
/// </summary>
public class ConcretePrototype : MonkeyKingPrototype
{
public ConcretePrototype(string id)
: base(id)
{ }
/// <summary>
/// 浅拷贝
/// </summary>
/// <returns></returns>
public override MonkeyKingPrototype Clone()
{
// 调用MemberwiseClone方法实现的是浅拷贝,另外还有深拷贝
return (MonkeyKingPrototype)this.MemberwiseClone();
}
}
上述代码通过‘浅复制’来创建孙悟空的副本!并没有涉及到深复制。
关于深/浅复制,在我的上篇博客中已经非常详细的介绍了,在此不再列举其他案例!
原型模式的优点有:
- 原型模式向客户隐藏了创建新实例的复杂性
- 原型模式允许动态增加或较少产品类。
- 原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
- 产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构
原型模式的缺点有:
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
最后,列举下C#中用到的深浅复制,比如:C#中的数据集对象DataSet,它就有Clone()方法和Copy()方法,其中Clone()是用来复制DataSet的结构,相当于浅复制。Copy()方法不仅复制DataSet的结构,也会复制DataSet的数据,因此,Copy()方法类似于深复制!
@陈卧龙的博客
C# 深浅复制 MemberwiseClone
2018-04-16 14:20 by 天才卧龙, 16 阅读, 1 评论, 收藏, 编辑
学无止境,精益求精
十年河东,十年河西,莫欺少年穷
学历代表你的过去,能力代表你的现在,学习代表你的将来
最近拜读了大话设计模式:原型模式,该模式主要应用C# 深浅复制来实现的!关于深浅复制大家可参考MSDN:https://msdn.microsoft.com/zh-cn/library/system.object.memberwiseclone.aspx
所谓深浅复制可解读为:
浅复制:在C#中调用 MemberwiseClone() 方法即为浅复制。如果字段是值类型的,则对字段执行逐位复制,如果字段是引用类型的,则复制对象的引用,而不复制对象,因此:原始对象和其副本引用同一个对象!
深复制:如果字段是值类型的,则对字段执行逐位复制,如果字段是引用类型的,则把引用类型的对象指向一个全新的对象!
上述的解释可能看不太懂,我们作如下案例进行分析:
class Program
{
public static void Main()
{
//创建P1对象
Person p1 = new Person();
p1.Age = 42;
p1.Name = "Sam";
p1.IdInfo = new IdInfo("081309207");
//通过浅复制 得到P2对象
Person p2 = p1.ShallowCopy();
//分别输出
Console.WriteLine("对象P1相关属性如下");
DisplayValues(p1);
//p1.Name = "";
//p1.IdInfo.IdNumber = "XXXXX";
Console.WriteLine("对象P2相关属性如下");
DisplayValues(p2);
//现在测试深复制
Person p3 = p1.DeepCopy();
p1.Name = "George";
p1.Age = 39;
p1.IdInfo.IdNumber = "081309208";
Console.WriteLine("对象P1相关属性如下");
DisplayValues(p1);
//p1.IdInfo.IdNumber = "CCCCCCC";
Console.WriteLine("对象P3相关属性如下");
DisplayValues(p3);
Console.Read();
}
public static void DisplayValues(Person p)
{
Console.WriteLine(" Name: {0:s}, Age: {1:d}", p.Name, p.Age);
Console.WriteLine(" Value: {0:d}", p.IdInfo.IdNumber);
}
}
public class IdInfo
{
public string IdNumber;
public IdInfo(string IdNumber)
{
this.IdNumber = IdNumber;
}
}
public class Person
{
public int Age;
public string Name;
public IdInfo IdInfo;
public Person ShallowCopy()
{
return (Person)this.MemberwiseClone();
}
public Person DeepCopy()
{
Person other = (Person)this.MemberwiseClone();
other.IdInfo = new IdInfo(IdInfo.IdNumber);
other.Name = String.Copy(Name);
return other;
}
}
上述代码分析如下:
原始对象P1,通过浅复制得到对象P2,通过深复制得到P3
原始对象P1中的值类型属性有:Age 和 Name ,引用类型对象有:IdInfo
根据上述浅复制的概念可知:P2中的Age 和 Name 相对于 P1是全新的,但P2中的 IdInfo 和 P1中的 IdInfo 是同一个对象,二者同在一个内存地址!
根据上述深复制的概念可知:P3中的Age 和 Name 相对于 P1是全新的,但P3中的 IdInfo 和 P1中的 IdInfo 不是同一个对象,也就是说 P3中的IdInfo是一个全新的对象,开辟了自己的内存地址!
上述代码测试如下:

我们现在讲代码修改如下:
public static void Main()
{
//创建P1对象
Person p1 = new Person();
p1.Age = 42;
p1.Name = "Sam";
p1.IdInfo = new IdInfo("081309207");
//通过浅复制 得到P2对象
Person p2 = p1.ShallowCopy();
//分别输出
Console.WriteLine("对象P1相关属性如下");
DisplayValues(p1);
p1.Name = "浅复制中,修改了P1的Name属性,但Name是值类型,所以不会影响P2";
p1.IdInfo.IdNumber = "浅复制中,修改了P1的IdInfo属性,但IdInfo是引用类型,所以会影响P2 (浅复制中引用类型原始对象和副本指向同一内存地址)";
Console.WriteLine("对象P2相关属性如下");
DisplayValues(p2);
Console.Read();
}
在输出P2之前,我们修改了P1对象的值类型Name 和 引用类型 IdInfo 。
无论是浅复制还是深复制,副本中的值类型都是全新的!
浅复制中原始对象和副本的引用类型指向同一内存地址,所以,修改了P1的IdInfo会同时影响P2的IdInfo
输出如下:

继续修改代码,如下:
public static void Main()
{
//创建P1对象
Person p1 = new Person();
p1.Age = 42;
p1.Name = "Sam";
p1.IdInfo = new IdInfo("081309207");
//现在测试深复制
Person p3 = p1.DeepCopy();
p1.Name = "George";
p1.Age = 39;
p1.IdInfo.IdNumber = "081309208";
Console.WriteLine("对象P1相关属性如下");
DisplayValues(p1);
p1.IdInfo.IdNumber = "深复制中,修改了P1的IdInfo属性,即使IdInfo是引用类型,也不会影响P3 (深复制中引用类型原始对象和副本分别指向不同的内存地址)";
Console.WriteLine("对象P3相关属性如下");
DisplayValues(p3);
Console.Read();
}
深复制中原始对象和副本的引用类型指向各自的地址,两者完全是两个不同的对象!
因此:修改P1不会影响P3
so,是不是很简单,是不是很Easy.
深浅复制主要用于当创建一个对象需要消耗过多资源时,可以采取复制的方法提升效率!
大话设计模式的原话是这样滴:当你New一个对象时,每New一次,都需要执行一个构造函数,如果构造函数的执行时间很长,那么多次New对象时会大大拉低程序执行效率,因此:一般在初始化信息不发生变化的前提下,克隆是最好的办法,这既隐藏了对象的创建细节,又大大提升了性能!
当然,如果每个类都要写自己的深复制,这岂不是非常非常麻烦,因此,有一个通用的深复制方法,如下:
/// <summary>
/// 通用的深复制方法
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
public class BaseClone<T>
{
public virtual T Clone()
{
MemoryStream memoryStream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, this);
memoryStream.Position = 0;
return (T)formatter.Deserialize(memoryStream);
}
}
相关案例如下(通用的深复制方法使用时必须为相关类及类的引用类型加上可序列化标识:[Serializable]):
class Program
{
public static void Main()
{
//创建P1对象
Person p1 = new Person();
p1.Age = 42;
p1.Name = "Sam";
p1.IdInfo = new IdInfo("081309207");
//现在测试深复制
Person p3 = p1.Clone();
p1.Name = "George";
p1.Age = 39;
p1.IdInfo.IdNumber = "081309208";
Console.WriteLine("对象P1相关属性如下");
DisplayValues(p1);
p1.IdInfo.IdNumber = "深复制中,修改了P1的IdInfo属性,即使IdInfo是引用类型,也不会影响P3 (深复制中引用类型原始对象和副本分别指向不同的内存地址)";
Console.WriteLine("对象P3相关属性如下");
DisplayValues(p3);
Console.Read();
}
public static void DisplayValues(Person p)
{
Console.WriteLine(" Name: {0:s}, Age: {1:d}", p.Name, p.Age);
Console.WriteLine(" Value: {0:d}", p.IdInfo.IdNumber);
}
}
[Serializable]
public class IdInfo
{
public string IdNumber;
public IdInfo(string IdNumber)
{
this.IdNumber = IdNumber;
}
}
[Serializable]
public class Person : BaseClone<Person>
{
public int Age;
public string Name;
public IdInfo IdInfo;
public Person ShallowCopy()
{
return (Person)this.MemberwiseClone();
}
public Person DeepCopy()
{
Person other = (Person)this.MemberwiseClone();
other.IdInfo = new IdInfo(IdInfo.IdNumber);
other.Name = String.Copy(Name);
return other;
}
public override Person Clone()
{
MemoryStream memoryStream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, this);
memoryStream.Position = 0;
return (Person)formatter.Deserialize(memoryStream);
}
}
/// <summary>
/// 通用的深复制方法
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
public class BaseClone<T>
{
public virtual T Clone()
{
MemoryStream memoryStream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, this);
memoryStream.Position = 0;
return (T)formatter.Deserialize(memoryStream);
}
}
@陈卧龙的博客



浙公网安备 33010602011771号