读Android之大话设计模式--18种设计模式(二):创建型设计模式
4,单例模式
原文:http://www.cnblogs.com/guoshiandroid/archive/2010/06/29/1767144.html
单例模式解释:
GoF对单例模式(Singleton Pattern)的定义是:保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。
单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。
单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
英文定义为:Ensure a class only has one instance, and provide a global point of access to it.
单例模式的UML图:
单例模式比较的单纯,其UML图如下所示:
单例模式深入分析:
单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
单例模式适合于一个类只有一个实例的情况,比如窗口管理器,打印缓冲池和文件系统,它们都是原型的例子。典型的情况是,那些对象的类型被遍及一个软件系统的不同对象访问,因此需要一个全局的访问指针,这便是众所周知的单例模式的应用。当然这只有在你确信你不再需要任何多于一个的实例的情况下
在计算机系统中,需要管理的资源包括软件外部资源,譬如每台计算机可以有若干个打印机,但只能有一个Printer Spooler, 以避免两个打印作业同时输出到打印机中。每台计算机可以有若干传真卡,但是只应该有一个软件负责管理传真卡,以避免出现两份传真作业同时传到传真卡中的情况。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。
需要管理的资源也包括软件内部资源,譬如,大多数的软件都有一个(甚至多个)属性(properties)文件存放系统配置。这样的系统应当由一个对象来管理一个属性文件。
需要管理的软件内部资源也包括譬如负责记录网站来访人数的部件,记录软件系统内部事件、出错信息的部件,或是对系统的表现进行检查的部件等。这些部件都必须集中管理。
GG单例模式的第二个版本:“懒汉式”,在单线程下能够非常好的工作,但是在多线程下存在线程安全问题,具体代码如下:
1 package com.diermeng.designPattern.Singleton;
2 /*
3 * GG单例模式的第二个版本 采用“懒汉式” 在需要使用的时候才实例化GG
4 */
5 public class GGVersionTwo {
6 //GG的姓名
7 private String name;
8 //对单例本身引用的名称
9 private static GGVersionTwo gGVersionTwo;
10 public String getName() {
11 return name;
12 }
13 public void setName(String name) {
14 this.name = name;
15 }
16 //构造函数私有化
17 private GGVersionTwo() {
18 }
19 //提供一个全局的静态方法
20 public static GGVersionTwo getGG() {
21 if(gGVersionTwo == null) {
22 gGVersionTwo = new GGVersionTwo();
23 }
24 return gGVersionTwo;
25 }
26 }
GG单例模式第四个版本,既解决了“懒汉式的”多线程问题,又解决了资源浪费的现象,看上去是一种不错的选择,具体代码如下所示:
1 package com.diermeng.designPattern.Singleton;
2 /*
3 * GG单例模式的第四个版本,既解决了“懒汉式的”多线程问题,又解决了资源浪费的现象,看上去是一种不错的选择
4 */
5 public class GGVersionFour {
6 //GG的姓名
7 private String name;
8 //对单例本身引用的名称
9 private static GGVersionFour gGVersionFour;
10 public String getName() {
11 return name;
12 }
13 public void setName(String name) {
14 this.name = name;
15 }
16 //构造函数私有化
17 private GGVersionFour() {
18 }
19 //提供一个全局的静态方法
20 public static GGVersionFour getGG() {
21 if(gGVersionFour == null) {
22 synchronized (GGVersionFour.class) {
23 if(gGVersionFour == null) {
24 gGVersionFour = new GGVersionFour();
25 }
26 }
27 }
28 return gGVersionFour;
29 }
30 }
单例模式的优缺点分析:
优点:客户端使用单例模式类的实例的时候,只需要调用一个单一的方法即可生成一个唯一的实例,有利于节约资源。
缺点:首先单例模式很难实现序列化,这就导致采用单例模式的类很难被持久化,当然也很难通过网络传输;其次由于单例采用静态方法,无法在继承结构中使用。最后如果在分布式集群的环境中存在多个Java虚拟机的情况下,具体确定哪个单例在运行也是很困难的事情。
单例模式的实际应用简介:
单例模式一般会出现在以下情况下:
在多个线程之间,比如servlet环境,共享同一个资源或者操作同一个对象
在整个程序空间使用全局变量,共享资源
大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。
温馨提示:
细心的读者可能会发现,笔者在写单例模式的双重检查方式的使用了“看上去是一种不错的选择”之语,之所以样说,是因为:Java的线程工作顺序是不确定的,这就会导致在多线程的情况没有实例化就使用的现象,进而导致程序崩溃。不过双重检查在C语言中并没有问题。因为大师说:双重检查对Java语言并不是成立的。尽管如此,双重检查仍然不失为解决多线程情况下单例模式的一种理想的方案。
5,建造者模式
原文:http://www.cnblogs.com/guoshiandroid/archive/2011/05/18/2049638.html
参考:http://www.cnblogs.com/devinzhang/archive/2012/01/06/2314670.html
建造者模式解释:
建造者模式(Builder Pattern)又叫生成器模式,是GoF提出的23种设计模式中的一种。
建造者模式是一种对象创建型模式之一,用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽象,通过子类继承和重载的方式,动态地创建具有复合属性的对象。
英文定义为:Separate the construction of a complex object from its representation so that the same construction process can create different representations.
建造者模式的UML图:
建造者模式涉及以下的角色:
抽象建造者(Builder)角色:给出一个抽象接口,以规范产品对象的各个组成成分的建造。此接口中一般至少规定两个方法,一个是创建部分的方法,例如BuilderPart,另一个是返回结果的方法,例如GetProduct,以约束具体建造者实现。
具体建造者(ConcreteBuilder)角色:担任这个角色的是与应用程序紧密相关的一些类,它们在应用程序的调用下创建产品的实例。这个角色产品实现了抽象建造者接口,主要完成分步创建产品并提供产品对象的实例。
导演者(Director)角色:顾名思义,就是具体指挥使用哪个具体创造者来完成产品的创建,是创建工作的调用者。但是,导演者角色并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者角色。
产品(Product)角色:产品角色就是建造中的复杂对象。一般只有对于复杂对象的创建才使用建造者模式,一个系统中会出现多于一个的产品类,而这些产品类并不一定有共同的接口,可能完全不关联,这时就需要提供多套抽象和具体的建造者来完成不一致的产品的创建或者是采用一个统一接口来标识产品。
建造者模式深入分析:
在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法确相对稳定。如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?这就是要说的建造者模式。
建造者模式将复杂对象的构建与对象的表现分离开来,这样使得同样的构建过程可以创建出不同的表现。
建造者模式的优缺点分析:
优点:
在建造者模式中,客户端不用在直接负责对象的创建,而是把这任务交给了具体的创建者类,把具体的如何组装的责任交给了Director类,客户端只负责对象的调用即可,符合单一职责原则。而且由于可以选择不同的具体的创建者,所以可以有不同的形式展现对象。
缺点:
创建者模式比较符合产品差别不大的对象的创建,如果差别很大,就会导致非常多的具体的创建者,这时候最好结合工厂方法模式。
建造者模式的实际应用简介:
对象的创建:Builder模式是为对象的创建而设计的模式
创建的是一个复合对象:被创建的对象为一个具有复合属性的符合对象
关注对象创建各部分的创建过程,不同的工厂对产品的属性有不同的创建的方法。
6,原型模式
原文:http://www.cnblogs.com/guoshiandroid/archive/2010/07/03/1770357.html
参考阅读:http://www.cnblogs.com/java-my-life/archive/2012/04/11/2439387.html
原型模式应用场景举例:
GG和MM经常在QQ上聊天,但是GG打字的速度慢如蜗牛爬行,每次MM在瞬间完成恢复或者问候是,GG都会很紧张的去尽力快速打字,尽管如此,还是让MM有些不高心,MM说回复信息这么慢,显然是用心不专,不在乎她。哎,GG也是百口难辩啊,不过也确实是没有办法。
有一天,GG想自己的密友K倾诉了自己的苦衷。K顿生大笑。说道:“傻瓜,你怎么不去网上收集一些肉麻的情话以及一些你们经常说话会涉及到主题,把这些东西拷贝下来保存在自己的电脑或者U盘里面,这样一来如果下次在聊天就可以借用过来了!”,“K就是K,我怎么没有想到呢~妙极~妙极^_^”,“不过不要太高兴,这些东西是要适当修改的,要不然你把名字都搞错的话,就等着你的MM把你踹死吧O(∩_∩)O哈哈~”K补充道,“嗯,说的对,谢谢K哥解决了我的心腹之患啊”GG乐不可支的说道。
这是MM由在网上和GG聊天,GG专门复制那些实现准备好的肉麻情话经过稍加修改后发给MM,MM都快美死了…
原型模式解释:
原型模式(Prototype Pattern)是一种对象创建型模式,它采取复制原型对象的方法来创建对象的实例。使用Prototype模式创建的实例,具有与原型一样的初始化数据
英文定义为:Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
原型模式的UML图:
原型模式涉及以下的角色:
客户端(Client)角色: 客户端提出创建对象的请求。
抽象原型(Prototype)角色:通常由一个Java接口或者Java抽象类来实现。从而为具体原型设立好规范。
具体原型(Concrete Prototype)角色:被复制的具体对象,此具体角色实现了抽象原型角色所要求实现的方法。
原型模式的UML图如下所示:
原型模式深入分析:
原型模式的工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
Java在语言级别是直接支持原型模式的。我们知道,在java.lang.Object是一切类和接口的父类,而java.lang.Object正好提供了一个clone()方法来支持原型模式。当然,一个对象如果想具有被复制的能力,还必须声明自己实现了Cloneable接口,如果没有声明,就会在对象被复制的时候抛出CloneNotSupportedException.
在java.lang.Object中提供了一个 protected Object clone()方法来支持对象的克隆,子类可以采用默认的方式进行所有字段的复制,也可以在子类中覆盖clone()方法,根据实际需要定制自己的复制行为。
注意,所有的数组都被视为实现接口 Cloneable。
复制有浅复制和深复制之分
Object提供的clone方法是浅复制 (shallow copy):即,对于primitive类型和String(不可变类)类型,复制出来的内容与原型中的内容一致且隔离;对于除了String类型外的引用类型,浅复制意味着,复制出来的是原型中引用类型的引用,而不是new出了新的对象。copy出来的引用的对象和原型引用的对象是同一个的。
为了使复制出来的引用类型的对象是新的对象而不是原型中同一个的引用对象,要进行深复制。如果深复制,每一个引用类型也需要声明Cloneable接口。
原型模式使用场景分析及代码实现:
在上面的使用场景中,因为GG打字太慢经常被女朋友怪罪,所以有了拷贝网上肉麻情话的和主要聊天话题内容的办法。这样,以后GG每次和MM聊天的时候只需要把原话拷贝出来,加以适当修改就行,省时省力,而且效果绝佳^_^,这就是设计模式的原型模式的使用的好处O(∩_∩)O~
UML模型图如下所示:
建立一个肉麻情话类,类中有非常详细的注释,这里就不在解释了:
1 import java.util.ArrayList;
2 import java.util.List;
3 /*
4 * 肉麻情话类
5 */
6 public class SweetWord implements Cloneable{
7 //肉麻情话句子
8 private String content;
9 //肉麻情话句子集合
10 private List<String> contents;
11 /*
12 * 获取肉麻情话集合
13 */
14 public List<String> getContents() {
15 return contents;
16 }
17 /*
18 * 设置肉麻情话集合
19 */
20 public void setContents(List<String> contents) {
21 this.contents = contents;
22 }
23 /*
24 * 获取肉麻情话
25 */
26 public String getContent() {
27 return content;
28 }
29 /*
30 * 设置肉麻情话
31 */
32 public void setContent(String content) {
33 this.content = content;
34 }
35 /*
36 * 肉麻情话覆盖了Object类的clone()方法,因为这里有List引用进行深度复制
37 * @see java.lang.Object#clone()
38 */
39 public SweetWord clone() {
40 try {
41 //新建一个肉麻情话对象,同时复制基本的属性
42 SweetWord sweetWord = (SweetWord)super.clone();
43 //新建一个肉麻情话集合
44 List<String> newContents = new ArrayList<String>();
45 //把原对象的肉麻情话集合中的肉麻情话集合通过forEach循环加入新建的newContents中
46 for(String friend : this.getContents()) {
47 newContents.add(friend);
48 }
49 //把新的肉麻情话集合设置进新的对象
50 sweetWord.setContents(newContents);
51 //返回新的的肉麻情话对象
52 return sweetWord;
53 } catch (CloneNotSupportedException e) {
54 e.printStackTrace();
55 return null;
56 }
57 }
58 }
最后我们建立测试客户端:
import java.util.ArrayList;
import java.util.List;
import com.diermeng.designPattern.Prototype.impl.SweetWord;
/*
* 肉麻情话测试客户端
*/
public class PrototypeClient {
public static void main(String[] args) {
//新建一个肉麻情话对象并设置相应的属性
SweetWord content1 = new SweetWord();
List<String> contents = new ArrayList<String>();
contents.add("宝贝儿,我爱你");
contents.add("你是我的唯一");
content1.setContents(contents);
//复制content1
SweetWord content2 = content1.clone();
//分别输入两个对象的内容
System.out.println(content1.getContents());
System.out.println(content2.getContents());
//在原来的肉麻情话对象中加入新的内容并把新的内容设置进去
contents.add("你是我真命天女");
content1.setContents(contents);
//分别输出新的修改后的两个肉麻情话对象
System.out.println(content1.getContents());
System.out.println(content2.getContents());
}
}
输出的结果如下:
1 [宝贝儿,我爱你, 你是我的唯一] 2 [宝贝儿,我爱你, 你是我的唯一] 3 [宝贝儿,我爱你, 你是我的唯一, 你是我真命天女] 4 [宝贝儿,我爱你, 你是我的唯一]
原型模式的优缺点分析:
优点:
1.允许动态地增加或减少产品类。由于创建产品类实例的方法是产品类内部具有的,因此增加新产品对整个结构没有影响。
2.提供简化的创建结构。
3.具有给一个应用软件动态加载新功能的能力。
4.产品类不需要非得有任何事先确定的等级结构,因为原型模式适用于任何的等级结构。
缺点:
每一个类都必须配备一个克隆方法,这对于全新的类来说不是很难,而对已有的类来说实现clone()方法不一定很容易,而且在进行比较深层次的复制的时候也需要编写一定工作量的代码
原型模式的实际应用简介:
原型对象一般在适用于一下场景:
在创建对象的时候,我们不仅希望被创建的对象继承其类的基本机构,而且还希望继承原型对象的数据。
希望对目标对象的修改不影响既有的原型对象(深度克隆的时候可以完全互不影响)。
隐藏克隆操作的细节。很多时候,对对象本身的克隆需要涉及到类本身的数据细节。
温馨提示:
因为使用原型模式的时候每个类都要具备克隆方法。如果在类的设计之初没有很好的规划,等使用很久了才想到克隆,就可能非常的麻烦,尤其是在设计到深层次复制的时候,因为此时牵扯到很多因素,而且工作量非常大。
使用序列化来进行对象的深复制:
序列化即是把对象写到流里面的过程;反序列化即是把对象从流中读取出来的过程。写在流里的是对象的一个拷贝,而原来的对象仍然在JVM里面。
前提是对象以及对象内部所有用到的对象都是可序列化的,否则就需要考虑把那些不可序列化的对象标记为transient,从而把它排除到复制范围之外。
然后使对象实现Serializable接口。
把对象写入到一个流里(不用依赖于文件,直接暂存在内存中即可),在从流里读取出来,便得到了一个深复制的对象。
创建Employer2类实现序列化接口:
1 class Employer2 implements Serializable{ 2 private static final long serialVersionUID = 1L; 3 private String name; 4 public String getName() { 5 return name; 6 } 7 public void setName(String name) { 8 this.name = name; 9 } 10 }
创建Employee2类实现序列化接口,并通过序列化编写深复制的方法:
1 class Employee2 implements Serializable{ 2 private static final long serialVersionUID = 3969438177161438988L; 3 private String name; 4 private Employer2 employer; 5 public String getName() { 6 return name; 7 } 8 public void setName(String name) { 9 this.name = name; 10 } 11 public Employer2 getEmployer() { 12 return employer; 13 } 14 public void setEmployer(Employer2 employer) { 15 this.employer = employer; 16 } 17 /** 18 * 实现深复制的方法 19 */ 20 public Object deepCopy() throws IOException, ClassNotFoundException{ 21 //字节数组输出流,暂存到内存中 22 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 23 //序列化 24 ObjectOutputStream oos = new ObjectOutputStream(bos); 25 oos.writeObject(this); 26 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); 27 ObjectInputStream ois = new ObjectInputStream(bis); 28 //反序列化 29 return ois.readObject(); 30 } 31 }
在main方法中使用序列化深复制对象:
1 public static void main(String[] args) throws IOException, ClassNotFoundException { 2 Employer2 employer = new Employer2(); 3 employer.setName("arthinking"); 4 Employee2 employee = new Employee2(); 5 employee.setName("Jason"); 6 employee.setEmployer(employer); 7 //通过深复制创建employee2 8 Employee2 employee2 = (Employee2) employee.deepCopy(); 9 employee2.getEmployer().setName("Jason"); 10 11 System.out.println(employee.getEmployer().getName()); 12 System.out.println(employee2.getEmployer().getName()); 13 }
serialVersionUID是long类型的。在Eclipse中有两种生成方式:
默认的是1L:
private static final long serialVersionUID = 1L;
另外一个则是根据类名、接口名、成员方法以及属性等生成一个64位的哈希字段:
private static final long serialVersionUID = 3969438177161438988L;
如果没有提供serialVersionUID,对象序列化后存到硬盘上之后,再增加或减少类的filed。这样,当反序列化时,就会出现Exception,造成不兼容问题。
但当serialVersionUID相同时,它就会将不一样的field以type的缺省值反序列化。这样就可以避开不兼容问题了。