声明:本系列为刘伟老师博客内容总结(http://blog.csdn.net/lovelion),博客中有完整的设计模式的相关博文,以及作者的出版书籍推荐
本系列内容思路分析借鉴了刘伟老师的博文内容,同时改用C#代码进行代码的演示和分析(Java资料过多 C#表示默哀).
本系列全部源码均在文末地址给出。
本系列开始讲解创建型模式,顾名思义,这类模式主要是为了解决类的创建问题。
孙悟空可以用猴毛根据自己的形象,复制(又称“克隆”或“拷贝”)出很多跟自己长得一模一样的“身外身”来。在设计模式中也存在一个类似的模式,可以通过一个原型对象克隆出多个一模一样的对象,该模式称之为原型模式。
对工作周报模块进行重新设计和实现:
(1)除了允许用户创建新周报外,还允许用户将创建好的周报保存为模板;
(2)用户在再次创建周报时,可以创建全新的周报,还可以选择合适的模板复制生成一份相同的周报,然后对新生成的周报根据实际情况进行修改,产生新的周报。
namespace Prototype
{
class WeeklyLog:ICloneable
{
private String name;
private String date;
private String content;
public void setName(String name)
{
this.name = name;
}
public void setDate(String date)
{
this.date = date;
}
public void setContent(String content)
{
this.content = content;
}
public String getName()
{
return (this.name);
}
public String getDate()
{
return (this.date);
}
public String getContent()
{
return (this.content);
}
//克隆方法clone(),此处使用C#语言提供的克隆机制
public Object Clone()
{
WeeklyLog obj = new Prototype.WeeklyLog ();
try
{
obj.name = this.name;
obj.date = this.date;
obj.content = this.content;
return (Object)obj;
}
catch
{
Console.WriteLine("不支持复制!");
return null;
}
}
}
class Program
{
static void Main(string[] args)
{
WeeklyLog log_previous = new WeeklyLog(); //创建原型对象
log_previous.setName("张无忌");
log_previous.setDate("第12周");
log_previous.setContent("这周工作很忙,每天加班!");
Console.WriteLine("****周报****");
Console.WriteLine("周次:" + log_previous.getDate());
Console.WriteLine("姓名:" + log_previous.getName());
Console.WriteLine("内容:" + log_previous.getContent());
Console.WriteLine("--------------------------------");
WeeklyLog log_new;
log_new = (WeeklyLog)log_previous.Clone(); //调用克隆方法创建克隆对象
log_new.setDate("第13周");
Console.WriteLine("****周报****");
Console.WriteLine("周次:" + log_new.getDate());
Console.WriteLine("姓名:" + log_new.getName());
Console.WriteLine("内容:" + log_new.getContent());
}
}
}
如何才能实现周报和附件的同时复制呢?继续探讨。
首先看一下两种克隆方式
浅克隆(Shallow Clone):当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制(房子起名字 同一个房子 可以叫茅草屋 也可以叫狗窝 也可以叫皇宫 这几个名字都指向这个房子)
深克隆(Deep Clone):除了对象本身被复制外,对象所包含的所有成员变量也将被复制(细胞复制,工厂批量盖房子 新的对象与原有对象有相同的的内容 同时占据新的空间)
在C#语言中,通过覆盖Object类的MemberwiseClone()方法可以实现浅克隆(也可以自己重新写一个),。我们首先使用浅克隆来实现工作周报和附件类的复制,来感受二者的不同区别。
namespace ShallowClone
{
class Attachment
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public void Download()
{
Console.WriteLine("下载附件,文件名为{0}。", name);
}
}
class WeeklyLog:ICloneable
{
private Attachment attachment;
private string name;
private string date;
private string content;
public Attachment Attachment
{
get { return attachment; }
set { attachment = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public string Date
{
get { return date; }
set { date = value; }
}
public string Content
{
get { return content; }
set { content = value; }
}
public object Clone()
{
return this.MemberwiseClone(); //客户端进行类型转换
}
//使用MemberwiseClone()方法实现浅克隆
//public WeeklyLog Clone()
//{
// return this.MemberwiseClone() as WeeklyLog;
//}
}
class Program
{
static void Main(string[] args)
{
WeeklyLog log_previous, log_new;
log_previous = new WeeklyLog();
Attachment attachment = new Attachment();
log_previous.Attachment = attachment;
log_new = (WeeklyLog)log_previous.Clone();
Console.WriteLine("周报是否相同?{0}", (log_previous == log_new) ? "是" : "否");
Console.WriteLine("附件是否相同?{0}", (log_previous.Attachment == log_new.Attachment) ? "是" : "否");
Console.Read();
}
}
}
深克隆常见的有序列号方式 C#自身还可以用反射进行
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
Clone不同 其他相同 需要复制的内容添加序列化标签
//使用序列化方式实现深克隆
public Object Clone()
{
Object clone = null;
FileStream fs = new FileStream("Temp.dat", FileMode.Create);
BinaryFormatter formatter = new BinaryFormatter();
try
{
formatter.Serialize(fs, this);
}
catch (SerializationException e)
{
Console.WriteLine("Failed to serialize. Reason: " + e.Message);
throw;
}
finally
{
fs.Close();
}
FileStream fs1 = new FileStream("Temp.dat", FileMode.Open);
BinaryFormatter formatter1 = new BinaryFormatter();
try
{
clone = (WeeklyLog)formatter1.Deserialize(fs1);
}
catch (SerializationException e)
{
Console.WriteLine("Failed to deserialize. Reason: " + e.Message);
throw;
}
finally
{
fs.Close();
}
return clone;
}
利用反射 通过反射拿到引用类型的类型并创建一个实例,将原来的数据赋值给信创建的实例。
纯反射实现,无需实现任何接口,哦对,需要实体类有个无参的构造方法,简单使用强大。
public Object Copy(object obj)
{
if (obj == null)
{
return null;
}
Object targetDeepCopyObj;
Type targetType = obj.GetType();
//值类型
if (targetType.IsValueType == true)
{
targetDeepCopyObj = obj;
}
//引用类型
else
{
targetDeepCopyObj = System.Activator.CreateInstance(targetType); //创建引用对象
System.Reflection.MemberInfo[] memberCollection = obj.GetType().GetMembers();
foreach (System.Reflection.MemberInfo member in memberCollection)
{
//拷贝字段
if (member.MemberType == System.Reflection.MemberTypes.Field)
{
System.Reflection.FieldInfo field = (System.Reflection.FieldInfo)member;
Object fieldValue = field.GetValue(obj);
if (fieldValue is ICloneable)
{
field.SetValue(targetDeepCopyObj, (fieldValue as ICloneable).Clone());
}
else
{
field.SetValue(targetDeepCopyObj, Copy(fieldValue));
}
}
//拷贝属性
else if (member.MemberType == System.Reflection.MemberTypes.Property)
{
System.Reflection.PropertyInfo myProperty = (System.Reflection.PropertyInfo)member;
MethodInfo info = myProperty.GetSetMethod(false);
if (info != null)
{
try
{
object propertyValue = myProperty.GetValue(obj, null);
if (propertyValue is ICloneable)
{
myProperty.SetValue(targetDeepCopyObj, (propertyValue as ICloneable).Clone(), null);
}
else
{
myProperty.SetValue(targetDeepCopyObj, Copy(propertyValue), null);
}
}
catch (System.Exception ex)
{
}
}
}
}
}
return targetDeepCopyObj;
}
public object Clone()
{
Object obj = Copy(this);
return obj;
}
动机和意图
一般结构
改进的优点
现存的缺点
优化空间
适用场景
(1)创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
(2) 系统要保存对象的状态,而对象的状态变化很小。
举例:金拱门汉堡 同样的配方同样的味道 煤老板小王去买车,进门看着奥拓86不错 直接大手一挥,给我来一打这个一模一样的。
实例源代码
GitHub地址
百度云地址:链接: https://pan.baidu.com/s/1jIw2Umq 密码: 24zr