原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。其实就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的的细节。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
介绍
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除原型。
何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码: 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
应用实例: 1、细胞分裂。 2、简历复制
优点: 每new一次,都需要执行一次构造函数,如果构造函数的执行时间很长,那么多次的执行这个初始化操作就太低效了。一般在初始化的信息不发生变化的情况下,克隆是最好的方法,这即隐藏了对象创建的细节,又对性能是大大的提高。即不用重新初始化对象,而是动态地获得对象运行的状态。即性能提高并且逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 ICloneable 接口。 3、逃避构造函数的约束。
使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
实现
我们将创建一个抽象类 Shape 和扩展了 Shape 类的实体类。下一步是定义类 ShapeCache,该类把 shape 对象存储在一个Hashtable 中,并在请求的时候返回它们的克隆。
PrototypPatternDemo,我们的演示类使用 ShapeCache 类来获取 Shape 对象。
步骤 1
创建一个实现了 IClonable 接口的抽象类。IClonable源码其实就是:
namespace System
{
// 摘要:
// 支持克隆,即用与现有实例相同的值创建类的新实例。
[ComVisible(true)]
public interface ICloneable
{
// 摘要:
// 创建作为当前实例副本的新对象。
//
// 返回结果:
// 作为此实例副本的新对象。
object Clone();
}
}
Shape.cs
using System;
namespace Prototype_Pattern
{
public abstract class Shape : ICloneable
{
private string id;
protected string type;
public abstract void draw();
public string getType()
{
return type;
}
public string getId()
{
return id;
}
public void setId(string id)
{
this.id = id;
}
public object Clone()
{
object clone = null;
clone = this.MemberwiseClone();
return clone;
}
}
}
步骤 2
创建扩展了上面抽象类的实体类。
Rectangle.cs
using System;
namespace Prototype_Pattern
{
class Rectangle : Shape
{
public Rectangle()
{
type = "Rectangle";
}
public override void draw()
{
Console.WriteLine("Inside Rectangle::draw() method.");
}
}
}
Square.cs
using System;
namespace Prototype_Pattern
{
class Square : Shape
{
public Square()
{
type = "Square";
}
public override void draw()
{
Console.WriteLine("Inside Square::draw() method.");
}
}
}
Circle.cs
using System;
namespace Prototype_Pattern
{
class Circle : Shape
{
public Circle()
{
type = "Circle";
}
public override void draw()
{
Console.WriteLine("Inside Circle::draw() method.");
}
}
}
步骤 3
创建一个类,从数据库获取实体类,并把它们存储在一个 Hashtable 中。
ShapeCache.cs
using System;
using System.Collections;
namespace Prototype_Pattern
{
public class ShapeCache
{
private static Hashtable shapeMap = new Hashtable();
public static Shape getShape(string shapeId)
{
if (shapeMap.ContainsKey(shapeId))
{
Shape cachedShape = (Shape)shapeMap[shapeId];
return (Shape)cachedShape.Clone();
}
return null;
}
// 对每种形状都运行数据库查询,并创建该形状
// shapeMap.Add(shapeKey, shape);
// 例如,我们要添加三种形状
public static void loadCache()
{
Circle circle = new Circle();
circle.setId("1");
shapeMap.Add(circle.getId(), circle);
Square square = new Square();
square.setId("2");
shapeMap.Add(square.getId(), square);
Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeMap.Add(rectangle.getId(), rectangle);
}
}
}
步骤 4
PrototypePatternDemo 使用 ShapeCache 类来获取存储在 Hashtable 中的形状的克隆。
PrototypePatternDemo.cs
using System;
namespace Prototype_Pattern
{
class PrototypePatternDemo
{
static void Main(string[] args)
{
ShapeCache.loadCache();
Shape clonedShape = ShapeCache.getShape("1");
Console.WriteLine("Shape : " + clonedShape.getType());
Shape clonedShape2 = ShapeCache.getShape("2");
Console.WriteLine("Shape : " + clonedShape2.getType());
Shape clonedShape3 = ShapeCache.getShape("3");
Console.WriteLine("Shape : " + clonedShape3.getType());
Console.Read();
}
}
}
步骤 5
验证输出。
Shape : Circle
Shape : Square
Shape : Rectangle
深拷贝与浅拷贝
MemberwiseClone方法:如果字段是值类型的,则对该字段进行逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象,因此,原始对象及其复本引用同一对象。我们先看个例子:
using System;
using System.Collections.Generic;
using System.Text;
namespace 原型模式
{
class Program
{
static void Main(string[] args)
{
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
Resume b = (Resume)a.Clone();
b.SetWorkExperience("1998-2006", "YY企业");
Resume c = (Resume)a.Clone();
c.SetPersonalInfo("男", "24");
c.SetWorkExperience("1998-2003", "ZZ企业");
a.Display();
b.Display();
c.Display();
Console.Read();
}
}
//简历
class Resume : ICloneable
{
private string name;
private string sex;
private string age;
private WorkExperience work;
public Resume(string name)
{
this.name = name;
work = new WorkExperience();
}
//设置个人信息
public void SetPersonalInfo(string sex, string age)
{
this.sex = sex;
this.age = age;
}
//设置工作经历
public void SetWorkExperience(string workDate, string company)
{
work.WorkDate = workDate;
work.Company = company;
}
//显示
public void Display()
{
Console.WriteLine("{0} {1} {2}", name, sex, age);
Console.WriteLine("工作经历:{0} {1}", work.WorkDate, work.Company);
}
public Object Clone()
{
return (Object)this.MemberwiseClone();
}
}
//工作经历
class WorkExperience
{
private string workDate;
public string WorkDate
{
get { return workDate; }
set { workDate = value; }
}
private string company;
public string Company
{
get { return company; }
set { company = value; }
}
}
}
输出结果:
大鸟 男 29
工作经历 1998-2003 ZZ 企业
大鸟 男 29
工作经历 1998-2003 ZZ 企业
大鸟 男 24
工作经历 1998-2003 ZZ 企业
这就说明了MemberwiseClone对于值类型的的复制,没什么问题,对于引用类型,就只是复制了引用,对引用的对象还是指向了原来的对象。所以会出现输出情况中三个引用都是最后的设置。
浅拷贝:被复制对象的所有变量都含有与原来对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
深拷贝:把引用对象的变量指向复制过的新对象,而不是原来的被引用对象。
为了实现深拷贝,我们将程序修改为:
using System;
namespace 原型模式
{
class Program
{
static void Main(string[] args)
{
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
Resume b = (Resume)a.Clone();
b.SetWorkExperience("1998-2006", "YY企业");
Resume c = (Resume)a.Clone();
c.SetWorkExperience("1998-2003", "ZZ企业");
a.Display();
b.Display();
c.Display();
Console.Read();
}
}
//简历
class Resume : ICloneable
{
private string name;
private string sex;
private string age;
private WorkExperience work;
public Resume(string name)
{
this.name = name;
work = new WorkExperience();
}
private Resume(WorkExperience work)
{
this.work = (WorkExperience)work.Clone();
}
//设置个人信息
public void SetPersonalInfo(string sex, string age)
{
this.sex = sex;
this.age = age;
}
//设置工作经历
public void SetWorkExperience(string workDate, string company)
{
work.WorkDate = workDate;
work.Company = company;
}
//显示
public void Display()
{
Console.WriteLine("{0} {1} {2}", name, sex, age);
Console.WriteLine("工作经历:{0} {1}", work.WorkDate, work.Company);
}
public Object Clone()
{
Resume obj = new Resume(this.work);
obj.name = this.name;
obj.sex = this.sex;
obj.age = this.age;
return obj;
}
}
//工作经历
class WorkExperience : ICloneable
{
private string workDate;
public string WorkDate
{
get { return workDate; }
set { workDate = value; }
}
private string company;
public string Company
{
get { return company; }
set { company = value; }
}
public Object Clone()
{
return (Object)this.MemberwiseClone();
}
}
}
输出结果为:
大鸟 男 29
工作经历 1998-2000 XX 公司
大鸟 男 29
工作经历 1998-2006 YY 企业
大鸟 男 29
工作经历 1998-2003 ZZ 企业