通过克隆对象来创建一个新的对象叫做原型模式(prototype pattern)。原型模式属于创建设计模式的范畴,与它相对的单例模式(Singleton Pattern)相对,都很简单,也很常用。
使用场景:
1. 当有许多子类,并且仅仅是对象的类型不同而已。2. 引用程序中,需要创建大量的类实例且这些实例的状态等差异很小。
3. 动态的绑定或加重方法。
4. 使用一个实例,仅仅通过改变它的状态或参数去完成一个工作。
5. 在运行时,添加和删除对象。
6. 通过修改实例的结构来指定新对象。
7. 动态地用类配置一个应用程序。
需要记住的是:当使用clone去复制时,是否需要一个浅度克隆还是深度克隆(deep clone or shallow clone). 基于具体的业务需要,选择不同的克隆方式。如果你想使用深度克隆,你能够使用内存序列化( using in memory serialization)技术来实现。当实现原型设计模式时,使用克隆去复制完全是一个设计决策。请阅读如下例子。
public class Prototype { /** * Dynamic loading is a typical object-oriented feature and prototype example. * For example, overriding method is a kind of prototype pattern. */ static Complex c1 = new Complex(); /** * Cloning is a shallow copy of the original object. * If the cloned object is changed, the original object * will be changed accordingly. See the following alteration. * @return Complex */ static Complex makeCopy() { return (Complex)c1.clone(); } public static void main(String []args){ //Dynamically load method Shape s1 = new Line(); Shape s2 = new Square(); Shape s3 = new Circle(); paint(s1); paint(s2); paint(s3); /** * If we want to make code more readable or do more stuff, * we can code the paint method in the following way: static void paint(Shape s){ if ( s instanceof Line) s.draw(); //more job here if (s instanceof Square) s.draw(); //more job here if (s instanceof Circle) s.draw(); //more job here } */ Complex c1 = makeCopy(); int[] mycopy = c1.getNums(); for(int i = 0; i < mycopy.length; i++) System.out.print(mycopy[i]); } static void paint(Shape s) { s.draw(); } } interface Shape { public void draw(); } class Line implements Shape { public void draw() { System.out.println("line"); } } class Square implements Shape { public void draw() { System.out.println("square"); } } class Circle implements Shape { public void draw() { System.out.println("circle"); } } /** *The prototype is typically used to clone an object, * i.e. to make a copy of an object. When an object * is complicated or time consuming to be created , * you may take prototype pattern to make such object * cloneable. Assume the Complex class is a complicated, * you need to implement Cloneable interface and override * the clone method(protected Object clone()). */ class Complex implements Cloneable { int[] nums = {1,2,3,4,5}; public Object clone() { try { return super.clone(); }catch(CloneNotSupportedException cnse) { System.out.println(cnse.getMessage()); return null; } } int[] getNums() { return nums; } }
这个例子使用了prototype模式,减少了创建对象的花费(这个实例只是作为阐明原型设计模式,不能作为实际用途。)。值得使出的是, 对于原型模式而言,克隆不是一个强制的选择。
也许你感觉上面的例子不够接近实际应用,那么就认真看看下面的实例吧,也许能够在实际的项目中应用。
PrototypeCapable.java接口,扩展 Cloneable。
public interface PrototypeCapable extends Cloneable{ public PrototypeCapable clone() throws CloneNotSupportedException; }定义三个子类Album.java, Movie.java, Show.java, 实现PrototypeCapable接口。
public class Album implements PrototypeCapable{ private String name = null; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public Album clone() throws CloneNotSupportedException { System.out.println("Cloning Album object.."); return (Album) super.clone(); } @Override public String toString() { return "Album"; } }
public class Movie implements PrototypeCapable{ private String name = null; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public Movie clone() throws CloneNotSupportedException { System.out.println("Cloning Movie object.."); return (Movie) super.clone(); } @Override public String toString() { return "Movie"; } }
public class Show implements PrototypeCapable { private String name = null; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public Show clone() throws CloneNotSupportedException { System.out.println("Cloning Show object.."); return (Show) super.clone(); } @Override public String toString() { return "Show"; } }定义工厂类PrototypeFactory,根据类名字符克隆对象。
public class PrototypeFactory{ private static java.util.Map<String , PrototypeCapable> prototypes = new java.util.HashMap<String , PrototypeCapable>(); static{ prototypes.put(ModelType.MOVIE, new Movie()); prototypes.put(ModelType.ALBUM, new Album()); prototypes.put(ModelType.SHOW, new Show()); } public static PrototypeCapable getInstance(final String s) throws CloneNotSupportedException { return ((PrototypeCapable) prototypes.get(s)).clone(); } public static class ModelType { public static final String MOVIE = "movie"; public static final String ALBUM = "album"; public static final String SHOW = "show"; } }
测试这个类,测试代码如下:
import static org.junit.Assert.*; import junit.framework.Assert; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.pattern.rationaljava.creationalpattern.prototype.PrototypeFactory; import org.pattern.rationaljava.creationalpattern.prototype.PrototypeFactory.ModelType; public class TestPrototypeFactory { @BeforeClass public static void setUpBeforeClass() throws Exception { } @Before public void setUp() throws Exception { } @After public vo
一、引子
这是一个很简单的模式,却被非常广泛的使用。之所以简单是因为在这个模式中仅仅使
用到了继承关系。
继承关系由于自身的缺陷,被专家们扣上了“罪恶”的帽子。“使用委派关系代替继承关
系”,“尽量使用接口实现而不是抽象类继承”等等专家警告,让我们这些菜鸟对继承“另眼相
看”。
其实,继承还是有很多自身的优点所在。只是被大家滥用的似乎缺点更加明显了。合理
的利用继承关系,还是能对你的系统设计起到很好的作用的。而模板方法模式就是其中的一
个使用范例。
二、定义与结构
模板方法(Template Method)模式:定义一个操作中的算法的骨架,而将一些步骤延
迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。这里
的算法的结构,可以理解为你根据需求设计出来的业务流程。特定的步骤就是指那些可能在
内容上存在变数的环节。
可以看出来,模板方法模式也是为了巧妙解决变化对系统带来的影响而设计的。使用模
板方法使系统扩展性增强,最小化了变化对系统的影响。这一点,在下面的举例中可以很明
显的看出来。
来看下这个简单模式的结构吧:
1) 抽象类(Abstract Class):定义了一到多个的抽象方法,以供具体的子类来实现它们;
而且还要实现一个模板方法,来定义一个算法的骨架。该模板方法不仅调用前面的抽象
方法,也可以调用其他的操作,只要能完成自身的使命。
2) 具体类(Concrete Class):实现父类中的抽象方法以完成算法中与特定子类相关的步骤。
下面是模板方法模式的结构图。直接把《设计模式》上的图拿过来用下:
三、举例
还是在我刚刚分析完源码的JUnit 中找个例子吧。JUnit 中的TestCase 以及它的子类就
是一个模板方法模式的例子。在TestCase 这个抽象类中将整个测试的流程设置好了,比如
先执行Setup 方法初始化测试前提,在运行测试方法,然后再TearDown 来取消测试设置。
但是你将在Setup、TearDown 里面作些什么呢?鬼才知道呢!!因此,而这些步骤的具体实
现都延迟到子类中去,也就是你实现的测试类中。
来看下相关的源代码吧。
这是TestCase 中,执行测试的模板方法。你可以看到,里面正像前面定义中所说的那
样,它制定了“算法”的框架——先执行setUp 方法来做下初始化,然后执行测试方法,最
后执行tearDown 释放你得到的资源。
public void runBare() throws Throwable {
setUp();
try {
runTest();
}
finally {
tearDown();
}
}
这就是上面使用的两个方法。与定义中不同的是,这两个方法并没有被实现为抽象方法,
而是两个空的无为方法(被称为钩子方法)。这是因为在测试中,我们并不是必须要让测试
程序使用这两个方法来初始化和释放资源的。如果是抽象方法,则子类们必须给它一个实现,
不管用到用不到。这显然是不合理的。使用钩子方法,则你在需要的时候,可以在子类中重
写这些方法。
protected void setUp() throws Exception {
}
protected void tearDown() throws Exception {
}
四、适用情况
根据上面对定义的分析,以及例子的说明,可以看出模板方法适用于以下情况:
1) 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
2) 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。其实这可
以说是一种好的编码习惯了。
3) 控制子类扩展。模板方法只在特定点调用操作,这样就只允许在这些点进行扩展。比如
上面runBare ()方法就只在runTest 前面适用setUp 方法。如果你不愿子类来修改你
的模板方法定义的框架,你可以采用两种方式来做:一是在API 中不体现出你的模板方
法;或者将你的模板方法置为final 就可以了。
可以看出,使用模板方法模式可以将代码的公共行为提取出来,达到复用的目的。而且,
在模板方法模式中,是由父类的模板方法来控制子类中的具体实现。这样你在实现子类的时
候,根本不需要对业务流程有太多的了解。
下载:
http://download.csdn.net/detail/undoner/5335717
深入浅出设计模式-中文版
1、模板方法模式包括哪些角色以及角色的作用?
答:模板方法模式包括两种角色:抽象模板和具体模板。
抽象模板(Abstract Template)角色:
抽象模板是一个抽象类。抽象模板定义了若干个方法以表示一个算法的各个步骤,这些方法中有抽象方法也有非抽象方法,其中的抽象方法称作原语操作。重要的一点是,抽象模板中还定义了一个称作模板方法的方法,该方法不仅包含有抽象模板中表示算法步骤的方法调用,而且也可以包含有定义在抽象模板中的其他对象的方法调用,即模板方法定义了算法的骨架。
具体模版(Concrete Template)角色:
具体模板是抽象模板的子类,实现抽象模板中的原语操作。
2、模板方法的优缺点。
答:优点:可以通过在抽象模板定义模板方法给出成熟的算法步骤,同时又不限制步骤的细节,具体模板实现算法细节不会改变整个算法的骨架。在抽象模板模式中,可以通过钩子方法对某些步骤进行挂钩,具体模板通过钩子可以选择算法骨架中的某些步骤。在子类定义详细的处理算法时不会改变算法的结构,实现了代码的复用,通过对子类的扩展可以增加新的行为,符合“开闭原则”。
缺点:每个不同的实现都需要定义一个子类,这也会导致类的个数的增加,系统更加庞大,设计也更加抽象,但是更加符合“单一职责原则”,使得类的内聚性得以提高。
3、适合模板方法模式的情景有哪些?
答:设计者需要给出一个算法的固定步骤,并将某些步骤的具体实现留给子类来实现;
需要对代码进行重构,将各个子类公共行为提取出来集中到一个共同的父类中以避免代码重复。
1)在TinyOS 2.x 中,标准的消息缓冲区 message_t,该缓冲区定义在 tos/types/message.h中。比之前Tinyos 1.x 优化了许多。如下:
typedef nx_struct message_t { nx_uint8_t header[sizeof(message_header_t)]; nx_uint8_t data[TOSH_DATA_LENGTH]; nx_uint8_t footer[sizeof(message_footer_t)]; nx_uint8_t metadata[sizeof(message_metadata_t)]; } message_t;TinyOS 2.x的组件把数据包看成抽象数据类型(ADT),而不是C中的数据结构,好处在于:首先,数据包层将不依赖于特定区域字段或位置,从而可以实现可选择数据包格式,并做出各种优化。
下面一一分析message_t中的各个定义:
1 header
typedef union message_header { cc2420_header_t cc2420; serial_header_t serial; } message_header_t;
nxle_uint8_t length;
nxle_uint16_t fcf;
nxle_uint8_t dsn;
nxle_uint16_t destpan;
nxle_uint16_t dest;
nxle_uint16_t src;
/** CC2420 802.15.4 header ends here */
#ifdef CC2420_HW_SECURITY
security_header_t secHdr;
#endif
#ifndef TFRAMES_ENABLED
/** I-Frame 6LowPAN interoperability byte */
nxle_uint8_t network;
#endif
nxle_uint8_t type;
} cc2420_header_t;
2 data
message_t的数据字段存储单跳数据包有效载荷。TOSH_DATA_LENGTH,默认大小28字节。可以修改。
3 footer
message_footer_t字段确保message_t中有足够的空间来存储所有底层链路层的MTU大小。在tinyos2.x中并未有该实际字段。
* CC2420 Packet Footer
*/
typedef nx_struct cc2420_footer_t {
} cc2420_footer_t;
4 metadata
存储收集到的信息但不用于发送。如RSSI或者时间戳。
typedef nx_struct cc2420_metadata_t { nx_uint8_t tx_power; nx_uint8_t rssi; nx_uint8_t lqi; nx_bool crc; nx_bool ack; nx_uint16_t time; } cc2420_metadata_t;
* CC2420 Packet metadata. Contains extra information about the message
* that will not be transmitted.
*
* Note that the first two bytes automatically take in the values of the
* FCS when the payload is full. Do not modify the first two bytes of metadata.
*/
typedef nx_struct cc2420_metadata_t {
nx_uint8_t rssi;
nx_uint8_t lqi;
nx_uint8_t tx_power;
nx_bool crc;
nx_bool ack;
nx_bool timesync;
nx_uint32_t timestamp;
nx_uint16_t rxInterval;
/** Packet Link Metadata */
#ifdef PACKET_LINK
nx_uint16_t maxRetries;
nx_uint16_t retryDelay;
#endif
} cc2420_metadata_t;
2)CTP数据包格式
1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P|C| reserved | THL | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ETX | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | origin | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | seqno | collect_id | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- P: Routing pull. P位允许节点从其它节点请求路由信息。如果节点收到一个P位置位的包,它应当传输一个路由帧。
- C: Congestion notification. 拥塞标志位。如果节点丢弃了一个CTP数据帧,它必须在下一个传输的数据帧中置C位。
- THL: Time Has Lived. 已存活时间。当节点产生一个CTP数据帧时,它必须设THL为0。当节点接收到一个CTP数据帧时,它必须增加THL值。如果节点接收到的数据包THL为255,则将它回绕为0
- ETX: The ETX routing metric of the single-hop sender. 单跳发送者的ETX值。当节点发送一个CTP数据帧时,它必须将到单跳目的地的路由ETX值填入ETX字段。如果节点接收到的路由梯度比自己的小,则它必须准备发送一个路由帧。
- origin: The originating address of the packet. 包的源地址。转发的节点不可修改这个字段
- seqno: Origin sequence number. 源顺序号。源节点设置了这个字段,转发节点不可修改它。
- collect_id: Higher-level protocol identifier. 高层协议标识。源节点设置了这个字段,转发节点不可修改它。
- data: the data payload数据负载。0个或多个字节。转发节点不可修改这个字段。
Tinyos 2.x实现在net/ctp中。
typedef nx_struct {nx_ctp_options_t options;
nx_uint8_t thl;
nx_uint16_t etx;
nx_am_addr_t origin;
nx_uint8_t originSeqNo;
nx_collection_id_t type;
nx_uint8_t (COUNT(0) data)[0]; // Deputy place-holder, field will probably be removed when we Deputize Ctp