设计模式学习笔记--Prototype原型模式
原型模式(Prototype Pattern)也是一种创建型模式,它关注的是“大量相似对象”的创建问题。我们经常会遇到这样的情况:在系统中要创建大量的对象,这些对象之间具有几乎完全相同的功能,只是在细节上有一点儿差别。
网上有一些针对Prototype 模式的比较好的比方:
1、假如您在图书馆看到几本自己喜欢的书籍,当看到某些知识点时,想在上面作相关记号,但由于其是图书馆的书,不能在上面乱涂乱画。此时您只好把相关的章节,用复印机把它复印出来,然后在自己复印的纸张上作记号。在 Prototype Pattern 里,Clone 方法就如同此种复印的动作,用户从一个既有的原型实例 (如同图书馆里的书),复印后得到一或多个新的拷贝,不会破坏原本的原型,且用户不必知道原型的内容和格式。
2、Prototype 也具有一种「展示」的意味,就像是车展上的「原型」车款。当您对某个车款感兴趣时,您可购买相同的车款,而不是车展上展示的那辆车。
3、孙悟空在与黄风怪的战斗中,"使一个身外身的手段:把毫毛揪下一把,用口嚼得粉碎,望上一喷,叫声'变',变有百十个行者,都是一样得打扮,各执一根铁棒,把那怪围在空中。"换而言之,孙悟空可以根据自己的形象,复制出很多"身外身"来。
4、在软件设计方面,也常需要进行此种对象复制。编程软件的操作界面上有一条 Toolbar,用户只要单击 Toolbar 上的 Button,或用鼠标拖曳到设计窗格中,就可创建一个按钮控件的副本,并可事后改变它的颜色或位置。当程序员改变设计图中的副本对象时,Toolbar 上的Button「原型」对象并不会跟着被改变。同样的观念,亦适用于工业设计 CAD 软件、图像处理软件,以及 Visual Studio 等各种软件的设计。
这些都是Prototype原型模式的比较形象的比喻。
因此,ProtoType原型模式:
2、用GoF的话来说就是:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
3、Prototype 模式的重点在于 Clone 方法,它负责复制 (克隆) 出一个新的对象并返回,而不是用 new 运算符和某个类的构造函数去创建实例。在此模式中,派生类如何覆写父类的 Clone 方法将是重点。而 clone 的方式,又可分为「浅拷贝 (shallow copy)」和「深拷贝 (deep copy)
但为什么不用 new 运算符加上某个类的构造函数去创建实例,而要再衍生出这种 Prototype Pattern 呢?原因是:
1、在系统设计上,类的种类可能会很多,而难以整合成特定的自定义类时,或类与类之间有大量平行阶层结构,类的数量过多会造成管理上的困难。例如:原型类为小轿车类,而小轿车又有许多种,有:宝马车,丰田车,福特车...总之各种品牌各种造型种类上千的轿车,这种情况下,如果为每一种都定义一个自定义类则管理非常复杂。
2、为了避免整个系统中,类与类之间结构的剧烈变化,避免为了重载一个新的函数,导致我们要修改的不是一个类,而是整个继承关系体系里的每一个类。
3、性能要求。例如用户在用鼠标操作室内设计软件时 (运行时期),若要重复创建相同的圆形桌子对象时,用 Prototype Pattern 这种对象复制的方式,由于对象初始化的内容都相同,会比从头用类去创建新的实例要容易,同时能在客户端程序中隐藏对象创建的细节,且在速度和性能上会较优。
ProtoType原型模式参与者:
二、具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象原型角色所要求的接口。
三、客户(Client)角色:客户类提出创建对象的请求。
前面说了一大堆,下面,我们来实现ProtoType,我们建立的情景是:一个省的体校培养很多运动员,我们这里现在要用到足球运动员和棒球运动员并对他们进行管理,今后,我们可能还会加入其它体育项目的运动员类别,具体将会加入多少种,我们不得而知,这就处于前面所指的"在系统设计上,类的种类可能会很多,而难以整合成特定的自定义类时,或类与类之间有大量平行阶层结构,类的数量过多会造成管理上的困难"这样的情况,所以,我们决定把“人”这个类做为抽象原型(Prototype)角色,把我们目前用到的足球和棒球运动员做为具体原型(Concrete Prototype)角色,由他们可以创造出任何数量的我们想要的具体的足球运动员或棒球运动员(在创建时可以进一步赋予不同的扩展属性:运动场上的位置)。现在我们就来实现这样的目标。
程序如下图:
一、抽象原型(Prototype)角色:这是一个抽象角色,通常由一个C#接口或抽象类实现。此角色给出所有的具体原型类所需的接口。在C#中,抽象原型角色通常实现了ICloneable接口。
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ProtoTypeFootballTeam
{
//抽象原型(Prototype)角色:这是一个抽象角色,通常由一个C#接口或抽象类实现。此角色给出所有的具体原型类所需的接口。
//在C#中,抽象原型角色通常实现了ICloneable接口
public abstract class AbstractPerson
{
定义Career属性
定义Sex属性
构造函数
定义Clone接口
}
}
二、具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象原型角色所要求的接口。
I、足球运动员
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ProtoTypeFootballTeam
{
//具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象原型角色所要求的接口。
定义"足球运动员"类
}
II、 棒球运动员
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ProtoTypeFootballTeam
{
定义"棒球运动员"类
}
三、客户(Client)角色:客户类提出创建对象的请求。
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ProtoTypeFootballTeam
{
class Program
{
static void Main(string[] args)
{
// Create two instances and clone each
FootballPlayer p1 = new FootballPlayer("前锋");
FootballPlayer c1 = (FootballPlayer)p1.Clone();
Console.WriteLine("Cloned:职业--{0},性别--{1},球场位置--{2}", c1.Career, c1.Sex, c1.Position);
FootballPlayer p2 = new FootballPlayer("后卫");
FootballPlayer c2 = (FootballPlayer)p2.Clone();
Console.WriteLine("Cloned:职业--{0},性别--{1},球场位置--{2}",c2.Career,c2.Sex,c2.Position);
BaseballPlayer bp1 = new BaseballPlayer("一垒手");
BaseballPlayer bc1 = (BaseballPlayer)bp1.Clone();
Console.WriteLine("Cloned:职业--{0},性别--{1},球场位置--{2}", bc1.Career, bc1.Sex, bc1.Position);
BaseballPlayer bp2 = new BaseballPlayer("跑垒员");
BaseballPlayer bc2 = (BaseballPlayer)bp2.Clone();
Console.WriteLine("Cloned:职业--{0},性别--{1},球场位置--{2}", bc2.Career, bc2.Sex, bc2.Position);
Console.Read();
}
}
}
运行效果如下:
ProtoType Pattern原型模式总结:
Prototype Pattern 适用的情景:
1、当系统中,某个系列的类,变化和扩展特别频繁的时侯。
2、当系统应该独立于它的产品创建时。
3、想对客户端程序,隐藏类的具体内容。
4、希望依用户的操作,在「执行时期」动态地加载 (dynamic loading) 或动态获得对象的状态。如某些设计软件,会依使用者鼠标的操作来动态创建控件副本。
5、同上一点,当需要的类型不是编译时就能确定的,而是能在运行过程中动态选择的。
6、当类型本身可枚举的种类非常固定时,例如一家软件公司,只有「主管、程序员、业务员」三种职务,当公司标到大型项目,需要招幕一百个程序员,与其通过某种机制 new 一百个程序员实例,不如通过一个现成的「程序员」原型实例,克隆一百个对象出来。
7、当一个类的多个实例,他们之间的字段和属性只有些许不同时。
8、当一个类的实例,只能有几种不同状态组合的其中一种时。
9、当对象的初始化需要高成本,例如:构造函数的参数、字段数量很多很复杂时。
Prototype Pattern 的优点:
1、Prototype模式允许动态增加或减少产品类。由于创建产品类实例的方法是产批类内部具有的,因此增加新产品对整个结构没有影响。
2、Prototype模式提供了简化的创建结构。工厂方法模式常常需要有一个与产品类等级结构相同的等级结构,而Prototype模式就不需要这样。
3、Portotype模式具有给一个应用软件动态加载新功能的能力。由于Prototype的独立性较高,可以很容易动态加载新功能而不影响老系统。
4、产品类不需要非得有任何事先确定的等级结构,因为Prototype模式适用于任何的等级结构。
5、可达到资源优化,避免用 new 创建实例会较消耗资源,且这样做速度也比 clone 对象慢
6、独立性和灵活性高,容易动态加载新功能。
7、减少类的数量。避免类的种类太多时,其子类数量会迅速地增加。
8、能在客户端程序中隐藏对象创建的细节。
Prototype Pattern 的缺点:
Prototype模式的最主要缺点就是每一个类必须配备一个克隆方法。而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事。
Prototype Pattern 的其他特性:
可避免形成多个类与类之间的大量平行 (平级) 阶层结构,在宽度和深度上的扩展。
「浅拷贝」可以提供低成本的对象复制;但通过「序列化 (Serialization)」进行的「深拷贝」代价就比较大,而非低成本的。