定义

封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

类型

行为类模式

类图

Visitor Pattern

要素

  • 抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法中的参数定义哪些对象是可以被访问的。
  • 访问者:实现抽象访问者所声明的方法,它影响到访问者访问到一个类后该干什么,要做什么事情。
  • 抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。抽象元素一般有两类方法,一部分是本身的业务逻辑,另外就是允许接收哪类访问者来访问。
  • 元素类:实现抽象元素类所声明的accept方法,通常都是visitor.visit(this),基本上已经形成一种定式了。
  • 结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。

代码实现

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
abstract class Element {  
public abstract void accept(IVisitor visitor);
public abstract void doSomething();
}

interface IVisitor {
public void visit(ConcreteElement1 el1);
public void visit(ConcreteElement2 el2);
}

class ConcreteElement1 extends Element {
public void doSomething(){
System.out.println("这是元素1");
}

public void accept(IVisitor visitor) {
visitor.visit(this);
}
}

class ConcreteElement2 extends Element {
public void doSomething(){
System.out.println("这是元素2");
}

public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
class Visitor implements IVisitor {

public void visit(ConcreteElement1 el1) {
el1.doSomething();
}

public void visit(ConcreteElement2 el2) {
el2.doSomething();
}
}

class ObjectStruture {
public static List<Element> getList(){
List<Element> list = new ArrayList<Element>();
Random ran = new Random();
for(int i=0; i<10; i++){
int a = ran.nextInt(100);
if(a>50){
list.add(new ConcreteElement1());
}else{
list.add(new ConcreteElement2());
}
}
return list;
}
}

public class Client {
public static void main(String[] args){
List<Element> list = ObjectStruture.getList();
for(Element e: list){
e.accept(new Visitor());
}
}
}

## 访问者模式的优点

  • 符合单一职责原则:凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,使用访问者模式一方面符合单一职责原则,另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展。
  • 扩展性良好:元素类可以通过接受不同的访问者来实现对不同操作的扩展

缺点

  • 增加新的元素类比较困难:通过访问者模式的代码可以看到,在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。所以,访问者模式比较适用于对已有功能的重构,比如说,一个项目的基本功能已经确定下来,元素类的数据已经基本确定下来不会变了,会变的只是这些元素内的相关操作,这时候,我们可以使用访问者模式对原有的代码进行重构一遍,这样一来,就可以在不修改各个元素类的情况下,对原有功能进行修改。

访问者模式的适用场景

假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去。

假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去。

评论和共享

定义

定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。

类型

行为类模式

类图

Observer Pattern

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract class Subject {  
private Vector<Observer> obs = new Vector<Observer>();

public void addObserver(Observer obs){
this.obs.add(obs);
}
public void delObserver(Observer obs){
this.obs.remove(obs);
}
protected void notifyObserver(){
for(Observer o: obs){
o.update();
}
}
public abstract void doSomething();
}

观察者模式的优点

  • 观察者与被观察者之间是属于轻度的关联关系,并且是抽象耦合的,这样,对于两者来说都比较容易进行扩展。
  • 观察者模式是一种常用的触发机制,它形成一条触发链,依次对各个观察者的方法进行处理。但同时,这也算是观察者模式一个缺点,由于是链式触发,当观察者比较多的时候,性能问题是比较令人担忧的。并且,在链式结构中,比较容易出现循环引用的错误,造成系统假死。
    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

    class ConcreteSubject extends Subject {
    public void doSomething(){
    System.out.println("被观察者事件反生");
    this.notifyObserver();
    }
    }
    interface Observer {
    public void update();
    }
    class ConcreteObserver1 implements Observer {
    public void update() {
    System.out.println("观察者1收到信息,并进行处理。");
    }
    }
    class ConcreteObserver2 implements Observer {
    public void update() {
    System.out.println("观察者2收到信息,并进行处理。");
    }
    }

    public class Client {
    public static void main(String[] args){
    Subject sub = new ConcreteSubject();
    sub.addObserver(new ConcreteObserver1()); //添加观察者1
    sub.addObserver(new ConcreteObserver2()); //添加观察者2
    sub.doSomething();
    }
    }

##运行结果

1
2
3
被观察者事件反生
观察者1收到信息,并进行处理。
观察者2收到信息,并进行处理。

观察者模式的优点

观察者与被观察者之间是属于轻度的关联关系,并且是抽象耦合的,这样,对于两者来说都比较容易进行扩展。

观察者模式是一种常用的触发机制,它形成一条触发链,依次对各个观察者的方法进行处理。但同时,这也算是观察者模式一个缺点,由于是链式触发,当观察者比较多的时候,性能问题是比较令人担忧的。并且,在链式结构中,比较容易出现循环引用的错误,造成系统假死。

评论和共享

定义

用一个中介者对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使耦合松散,而且可以独立地改变它们之间的交互。

类型

行为类模式

类图

Mediator Pattern

为什么要使用中介者模式

不使用中介者,各个类的关系就像 p2p 网络,是一种星形的的,采用了中介者模式后,就像变成了 CS 架构。任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合

例子

我们使用一个例子来说明一下什么是同事类:有两个类A和B,类中各有一个数字,并且要保证类B中的数字永远是类A中数字的100倍。也就是说,当修改类A的数时,将这个数字乘以100赋给类B,而修改类B时,要将数除以100赋给类A。类A类B互相影响,就称为同事类。代码如下:

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
abstract class AbstractColleague {  
protected int number;

public int getNumber() {
return number;
}

public void setNumber(int number){
this.number = number;
}
//抽象方法,修改数字时同时修改关联对象
public abstract void setNumber(int number, AbstractColleague coll);
}

class ColleagueA extends AbstractColleague{
public void setNumber(int number, AbstractColleague coll) {
this.number = number;
coll.setNumber(number*100);
}
}

class ColleagueB extends AbstractColleague{

public void setNumber(int number, AbstractColleague coll) {
this.number = number;
coll.setNumber(number/100);
}
}

public class Client {
public static void main(String[] args){

AbstractColleague collA = new ColleagueA();
AbstractColleague collB = new ColleagueB();

System.out.println("==========设置A影响B==========");
collA.setNumber(1288, collB);
System.out.println("collA的number值:"+collA.getNumber());
System.out.println("collB的number值:"+collB.getNumber());

System.out.println("==========设置B影响A==========");
collB.setNumber(87635, collA);
System.out.println("collB的number值:"+collB.getNumber());
System.out.println("collA的number值:"+collA.getNumber());
}
}

假如我们要使用中介者模式,类A类B之间则不可以直接关联,他们之间必须要通过一个中介者来达到关联的目的。

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 AbstractColleague {  
protected int number;

public int getNumber() {
return number;
}

public void setNumber(int number){
this.number = number;
}
//注意这里的参数不再是同事类,而是一个中介者
public abstract void setNumber(int number, AbstractMediator am);
}

class ColleagueA extends AbstractColleague{

public void setNumber(int number, AbstractMediator am) {
this.number = number;
am.AaffectB();
}
}

class ColleagueB extends AbstractColleague{

@Override
public void setNumber(int number, AbstractMediator am) {
this.number = number;
am.BaffectA();
}
}

abstract class AbstractMediator {
protected AbstractColleague A;
protected AbstractColleague B;

public AbstractMediator(AbstractColleague a, AbstractColleague b) {
A = a;
B = b;
}

public abstract void AaffectB();

public abstract void BaffectA();

}
class Mediator extends AbstractMediator {

public Mediator(AbstractColleague a, AbstractColleague b) {
super(a, b);
}

//处理A对B的影响
public void AaffectB() {
int number = A.getNumber();
B.setNumber(number*100);
}

//处理B对A的影响
public void BaffectA() {
int number = B.getNumber();
A.setNumber(number/100);
}
}

public class Client {
public static void main(String[] args){
AbstractColleague collA = new ColleagueA();
AbstractColleague collB = new ColleagueB();

AbstractMediator am = new Mediator(collA, collB);

System.out.println("==========通过设置A影响B==========");
collA.setNumber(1000, am);
System.out.println("collA的number值为:"+collA.getNumber());
System.out.println("collB的number值为A的10倍:"+collB.getNumber());

System.out.println("==========通过设置B影响A==========");
collB.setNumber(1000, am);
System.out.println("collB的number值为:"+collB.getNumber());
System.out.println("collA的number值为B的0.1倍:"+collA.getNumber());

}
}

虽然代码比较长,但是还是比较容易理解的,其实就是把原来处理对象关系的代码重新封装到一个中介类中,通过这个中介类来处理对象间的关系。

中介者模式的优点

  • 适当地使用中介者模式可以避免同事类之间的过度耦合,使得各同事类之间可以相对独立地使用。
  • 使用中介者模式可以将对象间一对多的关联转变为一对一的关联,使对象间的关系易于理解和维护。
  • 使用中介者模式可以将对象的行为和协作进行抽象,能够比较灵活的处理对象间的相互作用。

适用场景

在面向对象编程中,一个类必然会与其他的类发生依赖关系,完全独立的类是没有意义的。一个类同时依赖多个类的情况也相当普遍,既然存在这样的情况,说明,一对多的依赖关系有它的合理性,适当的使用中介者模式可以使原本凌乱的对象关系清晰,但是如果滥用,则可能会带来反的效果。一般来说,只有对于那种同事类之间是网状结构的关系,才会考虑使用中介者模式。可以将网状结构变为星状结构,使同事类之间的关系变的清晰一些。

中介者模式是一种比较常用的模式,也是一种比较容易被滥用的模式。对于大多数的情况,同事类之间的关系不会复杂到混乱不堪的网状结构,因此,大多数情况下,将对象间的依赖关系封装的同事类内部就可以的,没有必要非引入中介者模式。滥用中介者模式,只会让事情变的更复杂。

评论和共享

定义

定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法中的某些特定步骤。

类型

行为类模式

类图

Template Method Pattern

模版方法是编程中一个经常用到的模式。

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
//抽象模板类
public abstract class AbstractClass {
//基本方法,不同实现
protected abstract void doSomething();
//模板方法,相同
public void templateMethod() {
//调用基本方法,完成相关逻辑
this.doSomething();
}
}

//具体模板类
public class ConcreateClass1 extends AbstractClass {
protected void doSomething() {
//业务逻辑
}
}
//省略 ConcreateClass2

public class Client {
public static void main(String[] args) {
AbstractClass class1 = new ConcreateClass1();
AbstractClass class2 = new ConcreateClass2();

class1.templateMethod();
class2.templateMethod();
}
}

模板方法优点

  • 封装不变部分,拓展可变部分
  • 提取公共部分代码,便于维护
  • 行为由父类控制,子类实现,符合开闭原则

模板方法缺点

按照习惯,抽象类负责声明最抽象,最一般的事物属性和方法,实现类完成具体的事物属性和方法。但是模板类却颠倒这个顺序,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响父类的结果,在复杂项目中带来了代码阅读的难度,让新手不适应。

使用场景

  • 多个子类有公有的方法,并且逻辑基本相同
  • 重要,复杂的算法,可以吧核心算法设计为模板方法,周边的相关细节功能则由各个子类实现
  • 重构时,把相同代码收取到父类中,让后通过钩子函数约束其行为。

评论和共享

创建类模式主要关注对象的创建过程,将对象的创建过程进行封装,使客户端可以直接得到对象,而不用去关心如何创建对象。创建类模式有5种,分别是:

  • 单例模式:用于得到内存中的唯一对象。
  • 工厂方法模式:用于创建复杂对象。
  • 抽象工厂模式:用于创建一组相关或相互依赖的复杂对象。
  • 建造者模式:用于创建模块化的更加复杂的对象。
  • 原型模式:用于得到一个对象的拷贝。

为什么需要创建性模式

首先,在编程中,对象的创建通常是一件比较复杂的事,因为,为了达到降低耦合的目的,我们通常采用面向抽象编程的方式,对象间的关系不会硬编码到类中,而是等到调用的时候再进行组装,这样虽然降低了对象间的耦合,提高了对象复用的可能,但在一定程度上将组装类的任务都交给了最终调用的客户端程序,大大增加了客户端程序的复杂度。采用创建类模式的优点之一就是将组装对象的过程封装到一个单独的类中,这样,既不会增加对象间的耦合,又可以最大限度的减小客户端的负担。

其次,使用普通的方式创建对象,一般都是返回一个具体的对象,即所谓的面向实现编程,这与设计模式原则是相违背的。采用创建类模式则可以实现面向抽象编程。客户端要求的只是一个抽象的类型,具体返回什么样的对象,由创建者来决定。

再次,可以对创建对象的过程进行优化,客户端关注的只是得到对象,对对象的创建过程则不关心,因此,创建者可以对创建的过程进行优化,例如在特定条件下,如果使用单例模式或者是使用原型模式,都可以优化系统的性能。

总结

所有的创建类模式本质上都是对对象的创建过程进行封装。

评论和共享

定义

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

类型

创建类模式

类图

Prototype Pattern

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:

  • 实现 Cloneable 接口。在java语言有一个Cloneable 接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用 clone 方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会出 CloneNotSupportedException异常。
  • 重写 Object 类中的 clone 方法。Java中,所有类的父类都是 Object 类,Object 类中有一个 clone 方法,作用是返回对象的一个拷贝,但是其作用域 protected 类型的,一般的类无法调用,因此,Prototype 类需要将 clone 方法的作用域修改为 public 类型。

原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类 Prototype 也常用抽象类来替代。

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
class Prototype implements Cloneable {  
public Prototype clone(){
Prototype prototype = null;
try{
prototype = (Prototype)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return prototype;
}
}

class ConcretePrototype extends Prototype{
public void show(){
System.out.println("原型模式实现类");
}
}

public class Client {
public static void main(String[] args){
ConcretePrototype cp = new ConcretePrototype();
for(int i=0; i< 10; i++){
ConcretePrototype clonecp = (ConcretePrototype)cp.clone();
clonecp.show();
}
}
}

原型模式的优点及适用场景

使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。

因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。

原型模式的注意事项

  • 使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。
  • 深拷贝与浅拷贝。Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Prototype implements Cloneable {  
    private ArrayList list = new ArrayList();
    public Prototype clone(){
    Prototype prototype = null;
    try{
    prototype = (Prototype)super.clone();
    prototype.list = (ArrayList) this.list.clone();
    }catch(CloneNotSupportedException e){
    e.printStackTrace();
    }
    return prototype;
    }
    }

由于ArrayList不是基本类型,所以成员变量list,不会被拷贝,需要我们自己实现深拷贝,幸运的是Java提供的大部分的容器类都实现了Cloneable接口。所以实现深拷贝并不是特别困难。

PS:深拷贝与浅拷贝问题中,会发生深拷贝的有java中的8中基本类型以及他们的封装类型,另外还有String类型。其余的都是浅拷贝。

评论和共享

定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

类型

创建类模式

类图

Builder Pattern

四个要素

  • 产品类:一般是一个较为复杂的对象,也就是说创建对象的过程比较复杂,一般会有比较多的代码量。在本类图中,产品类是一个具体的类,而非抽象类。实际编程中,产品类可以是由一个抽象类与它的不同实现组成,也可以是由多个抽象类与他们的实现组成。
  • 抽象建造者:引入抽象建造者的目的,是为了将建造的具体过程交与它的子类来实现。这样更容易扩展。一般至少会有两个抽象方法,一个用来建造产品,一个是用来返回产品。
  • 建造者:实现抽象类的所有未实现的方法,具体来说一般是两项任务:组建产品;返回组建好的产品。
  • 导演类:负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系,与导演类直接交互的是建造者类。一般来说,导演类被用来封装程序中易变的部分。

代码实现

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
class Product {
private String name;
private String type;
public void showProduct(){
System.out.println("名称:"+name);
System.out.println("型号:"+type);
}
public void setName(String name) {
this.name = name;
}
public void setType(String type) {
this.type = type;
}
}

abstract class Builder {
public abstract void setPart(String arg1, String arg2);
public abstract Product getProduct();
}
class ConcreteBuilder extends Builder {
private Product product = new Product();

public Product getProduct() {
return product;
}

public void setPart(String arg1, String arg2) {
product.setName(arg1);
product.setType(arg2);
}
}

public class Director {
private Builder builder = new ConcreteBuilder();
public Product getAProduct(){
builder.setPart("宝马汽车","X7");
return builder.getProduct();
}
public Product getBProduct(){
builder.setPart("奥迪汽车","Q5");
return builder.getProduct();
}
}
public class Client {
public static void main(String[] args){
Director director = new Director();
Product product1 = director.getAProduct();
product1.showProduct();

Product product2 = director.getBProduct();
product2.showProduct();
}
}

建造者模式的优点

  • 首先,建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在导演类中对整体而言可以取得比较好的稳定性。
  • 其次,建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。

建造者模式与工厂模式的区别

  • 我们可以看到,建造者模式与工厂模式是极为相似的,总体上,建造者模式仅仅只比工厂模式多了一个“导演类”的角色。在建造者模式的类图中,假如把这个导演类看做是最终调用的客户端,那么图中剩余的部分就可以看作是一个简单的工厂模式了。
  • 与工厂模式相比,建造者模式一般用来创建更为复杂的对象,因为对象的创建过程更为复杂,因此将对象的创建过程独立出来组成一个新的类——导演类。也就是说,工厂模式是将对象的全部创建过程封装在工厂类中,由工厂类向客户端提供最终的产品;而建造者模式中,建造者类一般只提供产品类中各个组件的建造,而将具体建造过程交付给导演类。由导演类负责将各个组件按照特定的规则组建为产品,然后将组建好的产品交付给客户端。

总结

  • 建造者模式与工厂模式类似,他们都是建造者模式,适用的场景也很相似。一般来说,如果产品的建造很复杂,那么请用工厂模式;如果产品的建造更复杂,那么请用建造者模式。

评论和共享

定义

为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。

类型

创建类模式

类图

Abstract Factory Pattern

抽象工厂模式与工厂方法模式的区别

  • 抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。他与工厂方法模式的区别就在于,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对的多个产品等级结构。在编程中,通常一个产品结构,表现为一个接口或者抽象类,也就是说,工厂方法模式提供的所有产品都是衍生自同一个接口或抽象类,而抽象工厂模式所提供的产品则是衍生自不同的接口或抽象类。
  • 在抽象工厂模式中,有一个产品族的概念:所谓的产品族,是指位于不同产品等级结构中功能相关联的产品组成的家族。抽象工厂模式所提供的一系列产品就组成一个产品族;而工厂方法提供的一系列产品称为一个等级结构。我们依然拿生产汽车的例子来说明他们之间的区别。

car

在上面的类图中,两厢车和三厢车称为两个不同的等级结构;而2.0排量车和2.4排量车则称为两个不同的产品族。

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
public abstract class AbstractProductA {
//每个产品共有的方法
public void shareMethod(){}
//每个产品不同的实现
public abstract void doSomething();
}

public class ProductA1 extends AbstractProductA {
public void doSomething() {
System.out.println("产品A1的实现方法")
}
}

//产品A2,B1,B2 类似,此处省略

//抽象工厂类
public abstract class AbstractCreator {
public abstract AbstractProductA createProductA();
public abstract AbstractProductB createProductB();
}

//产品级1的实现类 产品级2类似,此处省略
public class Creator1 extends AbstractCreator {
public abstract AbstractProductA createProductA() {
return new ProductA1();
}
public abstract AbstractProductB createProductB() {
return new ProductB1();
}
}

//客户端代码
public class Client {
public static void main(String[] args) {
AbstractCreator creator1 = new Creator1();
AbstractCreator creator2 = new Creator2();

AbstractProductA a1 = creator1.createProductA();
AbstractProductA b2 = creator2.createProductB();
//...
}
}

抽象工厂模式的优点

抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。所谓的产品族,一般或多或少的都存在一定的关联,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理。

抽象工厂模式的缺点

产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的。

适用场景

当需要创建的对象是一系列相互关联或相互依赖的产品族时,便可以使用抽象工厂模式。说的更明白一点,就是一个继承体系中,如果存在着多个等级结构(即存在着多个抽象类),并且分属各个等级结构中的实现类之间存在着一定的关联或者约束,就可以使用抽象工厂模式。假如各个等级结构中的实现类之间不存在关联或约束,则使用多个独立的工厂来对产品进行创建,则更合适一点。

总结

无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为他们之间的演变常常是令人琢磨不透的。经常你会发现,明明使用的工厂方法模式,当新需求来临,稍加修改,加入了一个新方法后,由于类中的产品构成了不同等级结构中的产品族,它就变成抽象工厂模式了;而对于抽象工厂模式,当减少一个方法使的提供的产品不再构成产品族之后,它就演变成了工厂方法模式。

所以,在使用工厂模式时,只需要关心降低耦合度的目的是否达到了。

评论和共享

设计模式:工厂方法模式(Factory Method Pattern)

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。

类型

创建类模式

类图

Factory Method Pattern

模板代码

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
interface IProduct {  
public void productMethod();
}

class Product implements IProduct {
public void productMethod() {
System.out.println("产品");
}
}

interface IFactory {
public IProduct createProduct();
}

class Factory implements IFactory {
public IProduct createProduct() {
return new Product();
}
}

public class Client {
public static void main(String[] args) {
IFactory factory = new Factory();
IProduct prodect = factory.createProduct();
prodect.productMethod();
}
}

典型应用

汽车由发动机、轮、底盘组成,现在需要组装一辆车交给调用者。假如不使用工厂模式,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Engine {  
public void getStyle(){
System.out.println("这是汽车的发动机");
}
}
class Underpan {
public void getStyle(){
System.out.println("这是汽车的底盘");
}
}
class Wheel {
public void getStyle(){
System.out.println("这是汽车的轮胎");
}
}
public class Client {
public static void main(String[] args) {
Engine engine = new Engine();
Underpan underpan = new Underpan();
Wheel wheel = new Wheel();
ICar car = new Car(underpan, wheel, engine);
car.show();
}
}

可以看到,调用者为了组装汽车还需要另外实例化发动机、底盘和轮胎,而这些汽车的组件是与调用者无关的,严重违反了迪米特法则,耦合度太高。并且非常不利于扩展。另外,本例中发动机、底盘和轮胎还是比较具体的,在实际应用中,可能这些产品的组件也都是抽象的,调用者根本不知道怎样组装产品。假如使用工厂方法的话,整个架构就显得清晰了许多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface IFactory {  
public ICar createCar();
}
class Factory implements IFactory {
public ICar createCar() {
Engine engine = new Engine();
Underpan underpan = new Underpan();
Wheel wheel = new Wheel();
ICar car = new Car(underpan, wheel, engine);
return car;
}
}
public class Client {
public static void main(String[] args) {
IFactory factory = new Factory();
ICar car = factory.createCar();
car.show();
}
}

适用场景

  • 首先,作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过new就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
  • 其次,工厂模式是一种典型的解耦模式迪米特法则在工厂模式中表现的尤为明显。假如调用者自己组装产品需要增加依赖关系时,可以考虑使用工厂模式。将会大大降低对象之间的耦合度。
  • 再次,由于工厂模式是依靠抽象架构的,它把实例化产品的任务交由实现类完成,扩展性比较好。也就是说,当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。

优点

  • 可以使代码结构清晰,有效地封装变化。
  • 对调用者屏蔽具体的产品类
  • 降低耦合度

评论和共享

定义

确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

类型

创建类模式

类图

Singleton

要素

  • 私有的构造方法
  • 指向自己实例的私有静态引用
  • 以自己实例为返回值的静态的公有的方法

饿汉式单例

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

懒汉式单例

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {  
private static Singleton singleton;
private Singleton(){}

public static synchronized Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}

单例模式的优点

  • 在内存中只有一个对象,节省内存空间。
  • 避免频繁的创建销毁对象,可以提高性能。
  • 避免对共享资源的多重占用。
  • 可以全局访问。

适用场景

由于单例模式的以上优点,所以是编程中用的比较多的一种设计模式。我总结了一下我所知道的适合使用单例模式的场景:

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。
  • 以及其他我没用过的所有要求只有一个对象的场景。

单例模式注意事项

  • 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
  • 不要做断开单例类对象与类中静态引用的危险操作。
  • 多线程使用单例使用共享资源时,注意线程安全问题。

评论和共享

作者的图片

Archie Shi

Nothing to say


Front-End Development Engineer