Perfil de 福亮欢迎您来到我的地盘!FotosBlogListasMás Herramientas Ayuda

欢迎您来到我的地盘!

福亮 高

Ocupación
Ubicación
Intereses
Todavía no se han agregado elementos de lista.
Foto 1 de 1
Más álbumes (1)
23 septiembre

Strategy 策略模式

  

Strategy模式也叫策略模式,是由GoF提出的23种软件设计模式的一种。 Strategy模式是行为模式之一,它对一系列的算法加以封装,为所有算法定义一个抽象的算法接口,并通过继承该抽象算法接口对所有的算法加以封装和实 现,具体的算法选择交由客户端决定(策略)。Strategy模式主要用来平滑地处理算法的切换。


本文介绍设计模式中的(Strategy)模式的概念,用法,以及实际应用中怎么样使用Strategy模式进行开发。
Strategy模式的概念与应用场景
概要:
- Strategy模式定义一个算法族,并把每一种可能的算法封装成一个类,这些算法可以在应用程序内部被动态替换。策略模式可以降低客户端与算法之间的耦合关系。
- 这些算法具有一个相同的抽象接口,具体的算法通过继承的子类实现。
一个应用程序需要动态切换算法的时候,Strategy模式便变得非常有用。

Strategy模式的应用场景一般是:
- 具有多种可能需要实现的算法
- 需要在程序中对算法进行动态切换

Strategy模式UML类图

Strategy模式的角色:
Strategy
    策略(算法)抽象。
ConcreteStrategy
    各种策略(算法)的具体实现。
Context
    策略的外部封装类,或者说策略的容器类。根据不同策略执行不同的行为。策略由外部环境决定。


Strategy模式的应用范例
为了帮助理解,我们举例来说明。
我们的程序要实现加密功能。加密有一个加密算法,加密算法有很多种,比如MD5,DES等,我们的程序需要实现这个功能,就是可以让用户选择不同的加密算法进行加密。
这个过程我们可以用Strategy模式来实现。

文件一览:
Client
    测试类。
EncryptStrategy
    相当于Strategy角色。加密算法的抽象类/接口。
DesStrategy
    相当于ConcreteStrategy角色。Des加密算法。
MD5Strategy
    相当于ConcreteStrategy角色。MD5加密算法。
EncryptContext
    相当于Context角色。

代码:
  1. public class Client {  
  2.     /** 
  3.      * Test Strategy Pattern 
  4.      *  
  5.      */  
  6.     public static void main(String[] args) {  
  7.         //2种不同的策略  
  8.           
  9.         //使用DES策略(算法)  
  10.         EncryptContext context = new EncryptContext(new DesStrategy());  
  11.         context.encrypt();  
  12.           
  13.         //使用MD5策略(算法)  
  14.         context = new EncryptContext(new MD5Strategy());  
  15.         context.encrypt();  
  16.           
  17.     }  
  18. }  
  19.   
  20. /** 
  21.  * Strategy & subclass 
  22.  * 
  23.  */  
  24. interface EncryptStrategy {  
  25.     public void encrypt();  
  26. }  
  27.   
  28. //Strategy'subclass  
  29. class DesStrategy implements EncryptStrategy {  
  30.     public void encrypt() {  
  31.         System.out.println("encrypt by DES algorithm.");  
  32.         //TODO DES algorithm HERE。这里我们没给出具体的算法。  
  33.     }  
  34. }  
  35.   
  36. //Strategy'subclass  
  37. class MD5Strategy implements EncryptStrategy {  
  38.     public void encrypt() {  
  39.         System.out.println("encrypt by MD5 algorithm.");  
  40.         //TODO MD5 algorithm HERE。这里我们没给出具体的算法。  
  41.     }  
  42. }  
  43.   
  44. /** 
  45.  * Context 
  46.  * 
  47.  */  
  48. class EncryptContext {  
  49.     //策略对象  
  50.     EncryptStrategy strategy;  
  51.          
  52.     public EncryptContext(EncryptStrategy strategy) {  
  53.         this.strategy = strategy;  
  54.     }  
  55.       
  56.     //执行具体的策略行为  
  57.     public void encrypt() {  
  58.         strategy.encrypt();  
  59.     }  
  60.       
  61. }  
public class Client {     /**      * Test Strategy Pattern      *      */     public static void main(String[] args) {         //2种不同的策略                  //使用DES策略(算法)         EncryptContext context = new EncryptContext(new DesStrategy());         context.encrypt();                  //使用MD5策略(算法)         context = new EncryptContext(new MD5Strategy());         context.encrypt();              } } /** * Strategy & subclass * */ interface EncryptStrategy {     public void encrypt(); } //Strategy'subclass class DesStrategy implements EncryptStrategy {     public void encrypt() {         System.out.println("encrypt by DES algorithm.");         //TODO DES algorithm HERE。这里我们没给出具体的算法。     } } //Strategy'subclass class MD5Strategy implements EncryptStrategy {     public void encrypt() {         System.out.println("encrypt by MD5 algorithm.");         //TODO MD5 algorithm HERE。这里我们没给出具体的算法。     } } /** * Context * */ class EncryptContext {     //策略对象     EncryptStrategy strategy;            public EncryptContext(EncryptStrategy strategy) {         this.strategy = strategy;     }          //执行具体的策略行为     public void encrypt() {         strategy.encrypt();     }      } 



执行Client,输出结果:
C:\Strategy>javac *.java
C:\Strategy>java Client
encrypt by DES algorithm.
encrypt by MD5 algorithm.
C:\Strategy>
我们可以发现,Client里可以动态的切换加密的算法。
21 septiembre

Prototype 模式

      Prototype 模式被翻译为原型模式,这里我们看到了创建对象的另一种方式.
      一般在程序中我们可以使用的对象创建方法有:
      1) 使用new关键字
      2) 使用对象序列化(serialize)创建对象
      3)  使用反射机制动态创建对象
      4)  使用clone方式创建对象
    
      这里的Prototype模式用的就是第4种方法,它通过复制一个已经存在的对象实例来创建新的对象实例,被复制的对象实例称为“原型”。原型模式多应用在创建复杂的或者耗时的实例,因为在这种情况下通过复制一个已经存在的对象使得程序的运行效率更高;或者值相等而命名不一样的同类数据。
     
      在Java里,原型模式主要就是使用clone()方法去实现,但是要注意的是clone()方法是浅克隆(shallow clone)的。细节部分可以参考clone()的JavaDoc注释。里面说明了clone()方法满足的条件:
      Object java.lang.Object.clone() throws CloneNotSupportedException
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object. The general intent is that, for any object x, the expression:
                       x.clone() != x
    will be true, and that the expression:
                       x.clone().getClass() == x.getClass()
     will be true, but these are not absolute requirements. While it is typically the case that:
             x.clone().equals(x)
      will be true, this is not an absolute requirement.
By convention, the returned object should be obtained by calling super.clone. If a class and all of its superclasses (except Object) obey this convention, it will be the case that     x.clone().getClass() == x.getClass().
By convention, the object returned by this method should be independent of this object (which is being cloned). To achieve this independence, it may be necessary to modify one or more fields of the object returned by super.clone before returning it. Typically, this means copying any mutable objects that comprise the internal "deep structure" of the object being cloned and replacing the references to these objects with references to the copies. If a class contains only primitive fields or references to immutable objects, then it is usually the case that no fields in the object returned by super.clone need to be modified.
The method clone for class Object performs a specific cloning operation. First, if the class of this object does not implement the interface Cloneable, then a CloneNotSupportedException is thrown. Note that all arrays are considered to implement the interface Cloneable. Otherwise, this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a "shallow copy" of this object, not a "deep copy" operation.
The class Object does not itself implement the interface Cloneable, so calling the clone method on an object whose class is Object will result in throwing an exception at run time.
   
      下面,我们来看看什么是深克隆和浅克隆的不同。
简单地说,深克隆就是对该对象所引由的所有对象都进行克隆。比如,一个对象有一个属性为类类型,这个时候如果使用的是浅克隆,那么就只是将这个属性字段的实例引用复制给了克隆对象的属性字段,所以源对象和拷贝对象的这个字段都是引用了同一个实例。而深克隆则是对这个字段也进行复制,而不是只更改引用。这里就有一个问题,如果这个字段也存在一个属性是类类型怎么办呢?
要解决这个问题就看你的应用程序的需要了,因为在Java里使用深拷贝都是将一个类实现Serializable接口,使用Java的“冷藏”和“解冻”方法去进行深克隆,所以,程序员完全可以根据应用的需要而设计深克隆层次深度。
     
20 septiembre

线程池

在现代的操作系统中,有一个很重要的概念――线程,几乎所有目前流行的操作系统都支持线程,线程来源于操作系统中进程的概念,进程有自己的虚拟地址空间以及正文段、数据段及堆栈,而且各自占有不同的系统资源(例如文件、环境变量等等)。与此不同,线程不能单独存在,它依附于进程,只能由进程派生。如果一个进程派生出了两个线程,那这两个线程共享此进程的全局变量和代码段,但每个线程各拥有各自的堆栈,因此它们拥有各自的局部变量,线程在UNIX系统中还被进一步分为用户级线程(由进程自已来管理)和系统级线程(由操作系统的调度程序来管理)。

  既然有了进程,为什么还要提出线程的概念呢?因为与创建一个新的进程相比,创建一个线程将会耗费小得多的系统资源,对于一些小型的应用,可能感觉不到这点,但对于那些并发进程数特别多的应用,使用线程会比使用进程获得更好的性能,从而降低操作系统的负担。另外,线程共享创建它的进程的全局变量,因此线程间的通讯编程会更将简单,完全可以抛弃传统的进程间通讯的IPC编程,而采用共享全局变量来进行线程间通讯。

  有了上面这个概念,我们下面就进入正题,来看一下线程池究竟是怎么一回事?其实线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态。可能你也许会问:为什么要搞得这么麻烦,如果每当客户端有新的请求时,我就创建一个新的线程不就完了?这也许是个不错的方法,因为它能使得你编写代码相对容易一些,但你却忽略了一个重要的问题――性能!就拿我所在的单位来说,我的单位是一个省级数据大集中的银行网络中心,高峰期每秒的客户端请求并发数超过100,如果为每个客户端请求创建一个新线程的话,那耗费的CPU时间和内存将是惊人的,如果采用一个拥有200个线程的线程池,那将会节约大量的的系统资源,使得更多的CPU时间和内存用来处理实际的商业应用,而不是频繁的线程创建与销毁。

  既然一切都明白了,那我们就开始着手实现一个真正的线程池吧,线程编程可以有多种语言来实现,例如C、C++、java等等,但不同的操作系统提供不同的线程API接口,为了让你能更明白线程池的原理而避免陷入烦琐的API调用之中,我采用了JAVA语言来实现它,由于JAVA语言是一种跨平台的语言,因此你不必为使用不同的操作系统而无法编译运行本程序而苦恼,只要你安装了JDK1.2以上的版本,都能正确地编译运行本程序。另外JAVA语言本身就内置了线程对象,而且JAVA语言是完全面像对象的,因此能够让你更清晰地了解线程池的原理,如果你注意看一下本文的标题,你会发现整个示例程序的代码只有大约100行。

  本示例程序由三个类构成,第一个是TestThreadPool类,它是一个测试程序,用来模拟客户端的请求,当你运行它时,系统首先会显示线程池的初始化信息,然后提示你从键盘上输入字符串,并按下回车键,这时你会发现屏幕上显示信息,告诉你某个线程正在处理你的请求,如果你快速地输入一行行字符串,那么你会发现线程池中不断有线程被唤醒,来处理你的请求,在本例中,我创建了一个拥有10个线程的线程池,如果线程池中没有可用线程了,系统会提示你相应的警告信息,但如果你稍等片刻,那你会发现屏幕上会陆陆续续提示有线程进入了睡眠状态,这时你又可以发送新的请求了。

  第二个类是ThreadPoolManager类,顾名思义,它是一个用于管理线程池的类,它的主要职责是初始化线程池,并为客户端的请求分配不同的线程来进行处理,如果线程池满了,它会对你发出警告信息。

  最后一个类是SimpleThread类,它是Thread类的一个子类,它才真正对客户端的请求进行处理,SimpleThread在示例程序初始化时都处于睡眠状态,但如果它接受到了ThreadPoolManager类发过来的调度信息,则会将自己唤醒,并对请求进行处理。
   首先我们来看一下TestThreadPool类的源码:


  //TestThreadPool.java
  1 import java.io.*;
  2
  3
  4 public class TestThreadPool
  5 {
  6 public static void main(String[] args)
  7 {
  8 try{
  9 BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  10 String s;
  11 ThreadPoolManager manager = new ThreadPoolManager(10);
  12 while((s = br.readLine()) != null)
  13 {
  14 manager.process(s);
  15 }
  16 }catch(IOException e){}
  17 }
  18 }


  由于此测试程序用到了输入输入类,因此第1行导入了JAVA的基本IO处理包,在第11行中,我们创建了一个名为manager的类,它给ThreadPoolManager类的构造函数传递了一个值为10的参数,告诉ThreadPoolManager类:我要一个有10个线程的池,给我创建一个吧!第12行至15行是一个无限循环,它用来等待用户的键入,并将键入的字符串保存在s变量中,并调用ThreadPoolManager类的process方法来将这个请求进行处理。

  下面我们再进一步跟踪到ThreadPoolManager类中去,以下是它的源代码:


  //ThreadPoolManager.java
  1 import java.util.*;
  2
  3
  4 class ThreadPoolManager
  5 {
  6
  7 private int maxThread;
  8 public Vector vector;
  9 public void setMaxThread(int threadCount)
  10 {
  11 maxThread = threadCount;
  12 }
  13
  14 public ThreadPoolManager(int threadCount)
  15 {
  16 setMaxThread(threadCount);
  17 System.out.println("Starting thread pool...");
  18 vector = new Vector();
  19 for(int i = 1; i <= 10; i++)
  20 {
  21 SimpleThread thread = new SimpleThread(i);
  22 vector.addElement(thread);
  23 thread.start();
  24 }
  25 }
  26
  27 public void process(String argument)
  28 {
  29 int i;
  30 for(i = 0; i < vector.size(); i++)
  31 {
  32 SimpleThread currentThread = (SimpleThread)vector.elementAt(i);
  33 if(!currentThread.isRunning())
  34 {
  35 System.out.println("Thread "+ (i+1) +" is processing:" +
  argument);
  36 currentThread.setArgument(argument);
  37 currentThread.setRunning(true);
  38 return;
  39 }
  40 }
  41 if(i == vector.size())
  42 {
  43 System.out.println("pool is full, try in another time.");
  44 }
  45 }
  46 }//end of class ThreadPoolManager


  我们先关注一下这个类的构造函数,然后再看它的process()方法。第16-24行是它的构造函数,首先它给ThreadPoolManager类的成员变量maxThread赋值,maxThread表示用于控制线程池中最大线程的数量。第18行初始化一个数组vector,它用来存放所有的SimpleThread类,这时候就充分体现了JAVA语言的优越性与艺术性:如果你用C语言的话,至少要写100行以上的代码来完成vector的功能,而且C语言数组只能容纳类型统一的基本数据类型,无法容纳对象。好了,闲话少说,第19-24行的循环完成这样一个功能:先创建一个新的SimpleThread类,然后将它放入vector中去,最后用thread.start()来启动这个线程,为什么要用start()方法来启动线程呢?因为这是JAVA语言中所规定的,如果你不用的话,那这些线程将永远得不到激活,从而导致本示例程序根本无法运行。
   下面我们再来看一下process()方法,第30-40行的循环依次从vector数组中选取SimpleThread线程,并检查它是否处于激活状态(所谓激活状态是指此线程是否正在处理客户端的请求),如果处于激活状态的话,那继续查找vector数组的下一项,如果vector数组中所有的线程都处于激活状态的话,那它会打印出一条信息,提示用户稍候再试。相反如果找到了一个睡眠线程的话,那第35-38行会对此进行处理,它先告诉客户端是哪一个线程来处理这个请求,然后将客户端的请求,即字符串argument转发给SimpleThread类的setArgument()方法进行处理,并调用SimpleThread类的setRunning()方法来唤醒当前线程,来对客户端请求进行处理。

  可能你还对setRunning()方法是怎样唤醒线程的有些不明白,那我们现在就进入最后一个类:SimpleThread类,它的源代码如下:

  //SimpleThread.java
  1 class SimpleThread extends Thread
  2 {
  3 private boolean runningFlag;
  4 private String argument;
  5 public boolean isRunning()
  6 {
  7 return runningFlag;
  8 }
  9 public synchronized void setRunning(boolean flag)
  10 {
  11 runningFlag = flag;
  12 if(flag)
  13 this.notify();
  14 }
  15
  16 public String getArgument()
  17 {
  18 return this.argument;
  19 }
  20 public void setArgument(String string)
  21 {
  22 argument = string;
  23 }
  24
  25 public SimpleThread(int threadNumber)
  26 {
  27 runningFlag = false;
  28 System.out.println("thread " + threadNumber + "started.");
  29 }
  30
  31 public synchronized void run()
  32 {
  33 try{
  34 while(true)
  35 {
  36 if(!runningFlag)
  37 {
  38 this.wait();
  39 }
  40 else
  41 {
  42 System.out.println("processing " + getArgument() + "... done.");
  43 sleep(5000);
  44 System.out.println("Thread is sleeping...");
  45 setRunning(false);
  46 }
  47 }
  48 } catch(InterruptedException e){
  49 System.out.println("Interrupt");
  50 }
  51 }//end of run()
  52 }//end of class SimpleThread

  如果你对JAVA的线程编程有些不太明白的话,那我先在这里简单地讲解一下,JAVA有一个名为Thread的类,如果你要创建一个线程,则必须要从Thread类中继承,并且还要实现Thread类的run()接口,要激活一个线程,必须调用它的start()方法,start()方法会自动调用run()接口,因此用户必须在run()接口中写入自己的应用处理逻辑。那么我们怎么来控制线程的睡眠与唤醒呢?其实很简单,JAVA语言为所有的对象都内置了wait()和notify()方法,当一个线程调用wait()方法时,则线程进入睡眠状态,就像停在了当前代码上了,也不会继续执行它以下的代码了,当调用notify()方法时,则会从调用wait()方法的那行代码继续执行以下的代码,这个过程有点像编译器中的断点调试的概念。以本程序为例,第38行调用了wait()方法,则这个线程就像凝固了一样停在了38行上了,如果我们在第13行进行一个notify()调用的话,那线程会从第38行上唤醒,继续从第39行开始执行以下的代码了。

  通过以上的讲述,我们现在就不难理解SimpleThread类了,第9-14行通过设置一个标志runningFlag激活当前线程,第25-29行是SimpleThread类的构造函数,它用来告诉客户端启动的是第几号进程。第31-50行则是我实现的run()接口,它实际上是一个无限循环,在循环中首先判断一下标志runningFlag,如果没有runningFlag为false的话,那线程处理睡眠状态,否则第42-45行会进行真正的处理:先打印用户键入的字符串,然后睡眠5秒钟,为什么要睡眠5秒钟呢?如果你不加上这句代码的话,由于计算机处理速度远远超过你的键盘输入速度,因此你看到的总是第1号线程来处理你的请求,从而达不到演示效果。最后第45行调用setRunning()方法又将线程置于睡眠状态,等待新请求的到来。

  最后还有一点要注意的是,如果你在一个方法中调用了wait()和notify()函数,那你一定要将此方法置为同步的,即synchronized,否则在编译时会报错,并得到一个莫名其妙的消息:“current thread not owner”(当前线程不是拥有者)。

  至此为止,我们完整地实现了一个线程池,当然,这个线程池只是简单地将客户端输入的字符串打印到了屏幕上,而没有做任何处理,对于一个真正的企业级运用,本例还是远远不够的,例如错误处理、线程的动态调整、性能优化、临界区的处理、客户端报文的定义等等都是值得考虑的问题,但本文的目的仅仅只是让你了解线程池的概念以及它的简单实现,如果你想成为这方面的高手,本文是远远不够的,你应该参考一些更多的资料来深入地了解它。

builder pattern

    当一个复杂对象的创建需要很多步骤的时候,这些步骤在不同的条件下是变化的.通过不同的builder创建"不同风格"的复杂对象.
    为什么不用类本身的构造器呢,构造函数应该只是对本身的数据进行初始化.
    为什么不用类本身的constructor呢,构造函数应该只是对本身的数据(state&data)进行初始化.
   

为何使用?
是为了将构建复杂对象的过程和它的部件解耦.注意: 是解耦过程部件.

因为一个复杂的对象,不但有很多大量组成部分,如汽车,有很多部件:车轮 方向盘 发动机还有各种小零件等等,部件很多,但远不止这些,如何将这些部件装配成一辆汽车,这个装配过程也很复杂(需要很好的组装技术),Builder模式就是为了将部件和组装过程分开.

如何使用?
首先假设一个复杂对象是由多个部件组成的,Builder模式是把复杂对象的创建和部件的创建分别开来,分别用Builder类和Director类来表示.

首先,需要一个接口,它定义如何创建复杂对象的各个部件:

public interface Builder {

  //创建部件A  比如创建汽车车轮
void buildPartA();
//创建部件B 比如创建汽车方向盘
void buildPartB();
//创建部件C 比如创建汽车发动机
void buildPartC();

//返回最后组装成品结果 (返回最后装配好的汽车)
//成品的组装过程不在这里进行,而是转移到下面的Director类中进行.
//从而实现了解耦过程部件
Product getResult();

}

用Director构建最后的复杂对象,而在上面Builder接口中封装的是如何创建一个个部件(复杂对象是由这些部件组成的),也就是说Director的内容是如何将部件最后组装成成品:

public class Director {

  private Builder builder;

  public Director( Builder builder ) {
this.builder = builder;
}
// 将部件partA partB partC最后组成复杂对象
//这里是将车轮 方向盘和发动机组装成汽车的过程
public void construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();

  }

}

Builder的具体实现ConcreteBuilder:
通过具体完成接口Builder来构建或装配产品的部件;
定义并明确它所要创建的是什么具体东西;
提供一个可以重新获取产品的接口:

public class ConcreteBuilder implements Builder {

  Part partA, partB, partC;
public void buildPartA() {
//这里是具体如何构建partA的代码

};
public void buildPartB() {
//这里是具体如何构建partB的代码
};
public void buildPartC() {
//这里是具体如何构建partB的代码
};
public Product getResult() {
//返回最后组装成品结果
};

}

复杂对象:产品Product:

public interface Product { }

复杂对象的部件:

public interface Part { }
 
我们看看如何调用Builder模式;

ConcreteBuilder builder = new ConcreteBuilder();
Director director = new Director( builder );

director.construct();
Product product = builder.getResult();

Builder模式的应用
在Java实际使用中,我们经常用到"池"(Pool)的概念,当资源提供者无法提供足够的资源,并且这些资源需要被很多用户反复共享时,就需要使用池.

"池"实际是一段内存,当池中有一些复杂的资源的"断肢"(比如数据库的连接池,也许有时一个连接会中断),如果循环再利用这些"断肢",将提高内存使用效率,提高池的性能.修改Builder模式中Director类使之能诊断"断肢"断在哪个部件上,再修复这个部件.

具体英文文章见:Recycle broken objects in resource pools
 
   
18 septiembre

设计模式_抽象工厂

 
    对设计模式的理解,我始终理解的不是很透彻. 今天读到一篇文章,感觉还不错,摘过来 分享了... ...
    在前面介绍的两个创建型模式里面,我们解决的都是有关"new"的问题,用它们来避免显式指定类创建对象。我写的也非常简单易懂,相信看过的朋友们都应该对简单工厂模式、工厂方法模式的意图、所能解决的问题及适用情景有一定的了解了。但是若要达到灵活运用,什么时候用,怎样用合适还不是看一篇文章就能解决的问题。呵呵..这需要你对OO的理解程度,你的项目开发经验等等许多方面的积累。一起努力喔。。
好了,咱们言归正传,通过对这两个模式的了解,我们掌握一种思想,就是在创建一个对象时,需要把容易发生变化的地方给封装起来,来控制变化(哪里变化,封装哪里),以适应客户的变动,项目的扩展。但是,我们在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作,同时由于需求的变化,这“一系列相互依赖的对象”也要改变,如何应对这种变化呢?如何像简单工厂模式、工厂方法模式一样绕过常规的"new",然后提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?可能有人会说,你也可以将这些对象一个一个通过工厂方法模式来解决呀?但是,我们试想,既然是一系列相互依赖的对象,它们是有联系的,每个对象都这样解决,你又如何来保证他们的联系呢?举一个例子:Windows桌面主题,当你更换一个桌面主题的时候,系统的开始按钮、任务栏、菜单栏、工具栏等等都变了,而且是一起变的,他们的色调都还很一致,难道类似这样的问题,怎么来解决呢?它的天敌就是抽象工厂模式。
    意图:
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

参考者:
也就是该模式中的各个类或对象之间的关系:
  • 抽象工厂(Abstract Factory)
    声明生成一系列抽象产品的方法
  • 具体工厂(Concrete Factory)
    执行生成一系列抽象产品的方法,生成一系列具体的产品
  • 抽象产品(Abstract Product)
    为这一系列的某一种产品声明接口
  • 具体产品(Product)
    定义具体工厂生成的具体产品的对象,实现产品接口
  • 客户(Client)
    我们的应用程序客户端(不要理解成人),使用抽象产品和抽象工厂生成对象。

抽象工厂模式UML图
抽象工厂模式

抽象工厂模式在生活中的实例
咱们继续拿怎么穿衣服来说明这个抽象工厂模式。
就拿你来说吧。工作了,为了参加一些聚会,肯定有两套或多套衣服吧,比如说有商务装(成套,一系列具体产品)、时尚装(成套,一系列具体产品),甚至对于一个家庭来说,可能有商务女装、商务男装、时尚女装、时尚男装,这些也都是成套的,即一系列具体产品。咱们假设一种情况(现实中是不存在的,要不然,没法进入共产主义了,但有利于说明抽象工厂模式),在你的家中,某一个衣柜(具体工厂)只能存放某一种这样的衣服(成套,一系列具体产品),每次拿这种成套的衣服时也自然要从这个衣柜中取出了。用OO的思想去理解,所有的衣柜(具体工厂)都是衣柜类的(抽象工厂)某一个,而每一件成套的衣服又包括具体的上衣(某一具体产品),裤子(某一具体产品),这些具体的上衣其实也都是上衣(抽象产品),具体的裤子也都是裤子(另一个抽象产品)。

分析:
要好好去读上面那个实例,虽然有点绕嘴,其实只要用心去读,分清了抽象工厂模式的各个角色,对理解设计模式是非常重要的。理解头绪,然后接合简单工厂模式、工厂方法模式对工厂家族的了解,再加上抽象工厂模式的意图,头脑中差不多有一个雏型了吧。好了,咱们一起来分析一下。。
先把各个角色揪出来。
抽象工厂:虚拟的衣柜,它只是个概念而已。在项目中可能是一个接口或抽象类,定义规则,取出上衣,裤子。
具体工厂:具体的存在的衣柜,它用于存放某一种成套的衣服,换句话说,这种成套的衣服都是从这个衣柜中取出的。在项
              目中继承于抽象工厂,实现抽象工厂中的方法,取出具体产品,某一件上衣,某一条裤子。
抽象产品:虚拟的衣服,也只是个概念。在项目中可能是多个接口或抽象类,定义规则,有什么特性,起什么作用。
具体产品:具体的实际存在的产品,它指的就是用于组装成某一套衣服的某一件上衣或裤子。它继承自某一个抽象产品。实
              现抽象产品中制定的规则,特性。
它们之间怎么联系呢?客户在用的时候,依赖的又是什么呢?
客户在要的时候,首先要说出你要的什么系列的衣服,然后根据它的要求生成一个具体工厂的实例,剩下的工作就都是这个倒霉的具体工厂了,它会根据自己的实现生成一个上衣,生成一个裤子,然后把它交给客户。客户在这一过程中并不知道具体工厂都做了什么。也就是说,客户只依赖于抽象工厂和抽象产品了。在初始化的时候会用到一次具体工厂类名,我们根据.NET特有的反射机制又可以把这个在客户端唯一的具体的非抽象类放到一个应用程序配置文件中,防止它变化。
这就符合了设计模式中的“开放--封闭”原则,依赖倒转原则,里氏代换原则等等。

小结一下:
抽象工厂模式堪称gof23种设计模式精典模式之一,它能够解决诸如:通过显示指定类创建对象,紧耦合,对对象表示或实现的依赖等等一些问题,有关设计模式的设计原则,所能解决的问题,详见OO与设计模式的原则、目标 。
抽象工厂模式适用于对“一系列相互依赖的对象”的创建工作,这些对象是相互依赖的,是有联系的。如果仅为一个对象的创建则用简单工厂模式或工厂方法模式完全可以实现,没有必要用抽象工厂模式。
由于抽象工厂模式的客户端只依赖于抽象工厂,抽象产品,在初始化过程中仅用到一次具体工厂我们又把它放在了app.config中了,完全依赖接口,这样不仅在系统的扩展性方面好,而且可以提高团队开发效率。两个团队只要彼此了解定义的接口,抽象类,可以并行开发。举个例子,就拿博客园来说吧,我们在用自己的博客空间时,可以随时的换皮肤,这个换皮肤是不是典型的抽象工厂模式吗?如果是,它的各个角色又是什么呢?我认为是的。换一下皮肤,你博客页面上的各个样式都变了,而且这里各个样式都同属于你选定的这一个皮肤。而每个样式都又是独立的,它们组合起来就成了一款皮肤。我们来揪出来各个角色。
抽象工厂:皮肤
抽象产品:样式
具体工厂:某一款皮肤,皮肤名即为具体工厂的类名
具体产品:某一个样式。
虽然不存在这样的接口与类,但是它确实是抽象工厂模式的一个应用。抽象工厂制定都有哪些样式名,而具体工厂来实现这些样式名中的样式,而具体工厂中用到的各个样式都是一个具体产品。这也是我的理解,如兄弟们有不同的见解,欢迎发表意见,共同探讨。
确定过各个角色之后,就可以说一下为什么提高效率了。不论dudu在设计博客园时用什么工具或语言,它与泸江博客只要约定好所有用到的样式名就可以了。而泸江博客就可以根据要求单独去做每一款皮肤了。

优点:

隔离了具体类的生成,客户不需要知道怎样生成了每一个具体产品,什么时间生成的。它将客户与具体的类分离,依赖于抽象类,耦合性低。
一个产品族中的多个对象被设计成一起工作,它能够保证客户端始终只使用一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是非常实用的一种设计模式。 
它有利于更换产品系列,由于客户端只依赖于抽象类,具体类也被写到应用程序配置文件中,更换产品系列时,只须更改一下具体工厂名就行了。
缺点:

难以支持新种类的产品。难以扩展抽象工厂以生产新种类的产品。这是因为抽象工厂几口确定了可以被创建的产品集合,支持新种类的产品就需要扩展该工厂接口,这将涉及抽象工厂类及其所有子类的改变。
应用情景:

同一个产品族的产品在一起使用时,而且它们之间是相互依赖的,不可分离
系统需要由相互关联的多个对象来构成
你想提供一组对象而不显示它们的实现过程,只显示它们的接口
系统不应当依赖某一些具体产品类。
应用场景举例:

游戏开发中的多风格系列场景
系统更改皮肤
支持多种观感标准的用户界面工具箱(Kit)。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/dadalan/archive/2009/02/06/3866871.aspx