设计模式之创建型模式

从现在起,我们开始学习设计模式。
实际上,在以前的学习中已经接触到了许多的设计模式。比如,IO的装饰者模式,String常量池中的享元模式,单例模式,简单工厂、静态工厂模式,GUI监测的观察者模式,Spring中的代理模式等等。在一本书上看过,设计模式不只是一套公式,为了设计模式而设计,而是来源于程序而高于程序的一套系统的方法论。可以说任意的程序都能找到设计模式的影子,可能是一个模式,也可能是很多设计模式杂糅在一起。
虽然之前接触过,也大概了解一些设计模式的含义,但是还没有经过系统的学习。学设计模式的意义在于了解大局观,能从架构上寻找合适的解决问题方法。
GOF的中介绍,设计模式大致分为3大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。在此基础之上,我们再来学习J2EE的几种设计模式。

参考:
设计模式
java常用设计模式

本篇介绍 创建者模式 ,其中包含这几个大类:

  • 工厂模式(Factory Pattern)
  • 抽象工厂模式(Abstract Factory Pattern)
  • 单例模式(Singleton Pattern)
  • 建造者模式(Builder Pattern)
  • 原型模式(Prototype Pattern)

这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用新的运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。


工厂模式

工厂模式是Java中最常用的设计模式之一,提供了创建对象的最佳方式。这种情况下,创建对象时不会对客户端暴露创建的逻辑,而且通过一个公共接口来指向新的对象。
在我们明确的计划需要在不同条件下创建不同的示例时,工厂模式就是我们需要的模式。比如说,我们需要一辆汽车,只需要付款,并去工厂提货就可以了,具体这车的车体、发动机、轮毂是如何生产我们不需要关心,只需要好好用就可以了。
工厂模式的优点:

  • 一个调用中想创建一个对象,只知道名称即可,不需要了解其内部实现;
  • 扩展性高,每增加一个产品,只需要扩展工厂类就可以;

但同时,也有这个缺点:

  • 每次增加一个产品,就需要增加一个具体类和对象实现工厂,使得系统中的类的个数直线上升,同时使得类的关系错综复杂,增加了耦合性。

使用工厂模式需要注意的是,不能滥用这个模式。如果创建的对象只需要new,那么没必要设计一个复杂的工厂类,如果使用了工厂模式,那么需要引入工厂类,增加了复杂度。
工厂模式典型例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//1.创建一个接口
interface Shape {
void draw();
}
//2.创建实现接口的实体类
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("this is rectangle");
}
}
class Squre implements Shape {
@Override
public void draw() {
System.out.println("this is squre");
}
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("this is circle");
}
}
//3.创建工厂方法
class ShapeFactory {
public Shape getShape (String shapeType) {
if (shapeType == null)
return null;
switch (shapeType.toLowerCase()) {
case "circle":
return new Circle();
case "rectangle" :
return new Rectangle();
case "squre" :
return new Squre();
}
return null;
}
}
//4.使用该工厂,通过传递类型信息来获取对应的对象
public class FactoryPatternDemo {
public static void main(String[] args) {
ShapeFactory sf = new ShapeFactory();
Shape shapes[] = new Shape[3];
shapes[0] = sf.getShape("CIRCLE");
shapes[1] = sf.getShape("RECTANGLE");
shapes[2] = sf.getShape("SQURE");
for (Shape shape : shapes)
shape.draw();
}
}

抽象工厂类型

围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。抽象工厂的概念和工厂很像,都是生产对象,但区别也很大:抽象工厂是工厂的上一层,是生产工厂的工厂。比如有多个工厂生产键鼠,A是罗技,B是微软。此时就需要抽象工厂在上一层来统筹规划,指派A或者B来生产对象。如果只使用普通的工厂,生产同类型但又不同的对象需要增加方法,而抽象工厂只需要换一个工厂就可以同时替换键盘和鼠标。
所以可以这样总结:工厂模式类似于流水线,产生所需要的对象,而抽象工厂就像真正的工厂,可以使用不同流水线产生对象。
下面来介绍一个简单的例子,使用了上一个例子中的几个类:
首先像上一个例子一样写一个color接口,并新建几个color方法Red, Blue, Black。创建一个抽象工厂类:

1
2
3
4
abstract class AbstractFatory {
abstract Color getColor(String color);
abstract Shape getShape(String shape);
}

再创建工厂类AbstractColorFactory、AbstractShapeFactory实现抽象工厂。
最后创建生产工厂的方法,让外部程序调用产生对象:

1
2
3
4
5
6
7
8
9
10
11
class FactoryProducer {
public AbstractFatory getFactory (String choice) {
switch (choice.toLowerCase()) {
case "shape" :
return new AbstractShapeFactory();
case "color" :
return new AbstractColorFactory();
}
return null;
}
}

单例模式

单例模式是最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有这一个对象被创建。可以直接访问,不需要而且不可以实例化该类的对象。
单例模式应用广泛。比如在hibernate中,创建Session需要很大的资源消耗,但这是必要的一步,而且,每次只需要使用一次即可。这种情况下,单例模式是最好的方法。减少内存的开销,避免了对资源的多重占用。
实现单例模式的方式有很多,比如懒汉模式、饿汉模式等。在此不加展开,使用用途非常广泛的这种方式:

1
2
3
4
5
6
7
class SingleObject {
private static SingleObject instance = new SingleObject();
private SingleObject(){};
public static SingleObject getInstance() {
return instance;
}
}

建造者模式

建造者模式使用多个简单的对象一步一步构成复杂的对象。最终构成的对象独立于其他对象。其内涵是 将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以有不同的表示
在软件系统中,有时面临着一个“复杂对象”的创建工作,通常各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常需要改变,但是将它们组合在一起的算法却相对稳定。这时可以采用建造者模式。用一个director类把控流程,而用许多不同的builder去建造流程中的细节并产生产品。这样,生产出来的产品是绝对不会出问题的,因为流程把控好了。可以有多个builder去负责建造生产产品,而让director去把控流程。如果有新的产品,但是流程一致,就可以再扩张出一个builder来。
建造者模式一般包括以下几个角色:

  1. Builder:给出一个抽象接口,规范建造者对于生产的产品的各个组成部分的建造。这个接口只是定一个规范,不涉及具体的建造,具体的建造让继承于它的子类(ConcreteBuilder)去实现。
  2. ConcreteBuilder:实现builder接口,针对不同的商业逻辑,具体化各对象部分的建造,最后返回一个建造好的产品。
  3. Director:导演,顾名思义,负责规范流程之用。在指导中不涉及产品的创建,只负责保证复杂对象各部分被创建或按某种顺序创建。
  4. Product:复杂对象。

下面是建造者模式的一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//Builder,制定规范,每个产品包含名字、包装、价格三个要素
interface Item {
public String name();
public Packing packing();
public float price();
}
interface Packing {
public String pack();
}
class Wrapper implements Packing {
public String pack() {
return "Wrapper";
}
}
class Bottle implements Packing {
public String pack() {
return "Bottle";
}
}
//上一层抽象层的产品
abstract class Burger implements Item {
public Packing packing() {
return new Wrapper();
}
public abstract float price();
}
abstract class ColdDrink implements Item {
public Packing packing() {
return new Bottle();
}
public abstract float price();
}
//ConcreteBuilder,实现了Builder接口。针对不同逻辑产生不同产品,包括汉堡、冷饮等
class VegBurger extends Burger {
public String name() {
return "Veg Burger";
}
public float price() {
return 25.0f;
}
}
class ChickenBurger extends Burger {
public String name() {
return "Chicken Burger";
}
public float price() {
return 50.5f;
}
}
class Coke extends ColdDrink {
public String name() {
return "Coke";
}
public float price() {
return 30f;
}
}
class Pepsi extends ColdDrink {
public String name() {
return "Pepsi";
}
public float price() {
return 35f;
}
}
//工具类,表示一顿饭,是一个容器类
class Meal {
private List<Item> items = new ArrayList<Item>();
public void addItem(Item item) {
items.add(item);
}
public float getCost() {
float cost = 0.0f;
for (Item item : items)
cost += item.price();
return cost;
}
public void showItems() {
for (Item item : items)
System.out.println("Item:" + item.name() + ", Packing:" +
item.packing().pack() + ", Price:" + item.price());
}
}
//Director,规范流程,即建造者是怎样怎样具体工作的
class MealBuilder {
public Meal papareVegMeal() {
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}
public Meal papareNonVegMeal() {
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}
//Product,在客户端中真正使用导演来构建复杂对象
public class BuilderPattern {
public static void main(String[] args) {
MealBuilder mealBuilder = new MealBuilder();

Meal vegMeal = mealBuilder.papareVegMeal();
System.out.println("Veg Meal");
vegMeal.showItems();
System.out.println("Total cost:" + vegMeal.getCost());

Meal nonVegMeal = mealBuilder.papareNonVegMeal();
System.out.println("\n\nNon-Veg Meal");
nonVegMeal.showItems();
System.out.println("Total cost:" + nonVegMeal.getCost());
}
}

建造者者模式和工厂模式有什么区别呢?我认为建造者模式更注重于流程,过程更加复杂但是对于流程控制严格的应用场合相当重要。而工厂模式关注与具体的实现策略,讲究到底是如何实现的。
使用建造者模式的优势如下:

  1. 使用建造者模式可以使客户端不必知道产品内部组成的细节。
  2. 具体的建造者类之间是相互独立的,对系统的扩展非常有利。
  3. 由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。

参考设计模式——建造者

原型模式

原型模式用于创建重复的对象,同时又保证性能。原型模式的思想是生成一个原对象的克隆版本。一般是通过原有对象的内部克隆方法,来生产一个新的对象。相对于工厂、建造者模式,原型模式不需要new一个对象出来,而是直接复制。
原型模式主要应用在直接创建对象的代价比较大的场合。比如,一个对象在一个高代价数据库操作后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,直到需要的时候更新数据库,以此来减少数据库的调用。
原型模式绕过了构造函数,不需要创建实例,使得创建对象就像我们复制粘贴一样easy。而且使用的clone方法是本地方法,可以直接操作内存中的二进制流,性能得到显著提升。但原型模式无视构造方法,与隐藏构造方法的单例模式冲突,使用时要加以注意。此外,复制的过程中需要注意深复制和浅复制,要求我们加以注意。
原型模式的使用方法:

  1. 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  2. 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
abstract class PrototypeShape implements Cloneable {
private String id;
protected String type;

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;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
class PrototypeRectangle extends PrototypeShape {
void draw() {
System.out.println("this is rectangle");
}
public PrototypeRectangle() {
type = "Rectangle";
}
}
class PrototypeSqure extends PrototypeShape {
void draw() {
System.out.println("this is squre");
}
public PrototypeSqure() {
type = "Squre";
}
}
class PrototypeCircle extends PrototypeShape {
void draw() {
System.out.println("this is circle");
}
public PrototypeCircle() {
type = "Circle";
}
}

class ShapeCache {
private static Map<String, PrototypeShape> shapeMap =
new HashMap<String, PrototypeShape>();
//取得对象的克隆版本,即原型
public static PrototypeShape getShape(String shapeId) {
PrototypeShape cachedShape = shapeMap.get(shapeId);
return (PrototypeShape) cachedShape.clone();
}
// 将对象放入缓存中
public static void loadCache() {
PrototypeCircle circle = new PrototypeCircle();
circle.setId("1");
shapeMap.put(circle.getId(), circle);
PrototypeSqure squre = new PrototypeSqure();
squre.setId("2");
shapeMap.put(squre.getId(), squre);
PrototypeRectangle rectangle = new PrototypeRectangle();
rectangle.setId("3");
shapeMap.put(rectangle.getId(), rectangle);
}
}

public class PrototypeDemo {
public static void main(String[] args) {
ShapeCache.loadCache();
PrototypeShape clonedShape1 = (PrototypeShape) ShapeCache.getShape("1");
System.out.println("Shape:" + clonedShape1.getType());
PrototypeShape clonedShape2 = (PrototypeShape) ShapeCache.getShape("2");
System.out.println("Shape:" + clonedShape2.getType());
PrototypeShape clonedShape3 = (PrototypeShape) ShapeCache.getShape("3");
System.out.println("Shape:" + clonedShape3.getType());
}
}