Perfil de 福亮欢迎您来到我的地盘!FotosBlogListasMás ![]() | Ayuda |
欢迎您来到我的地盘! |
|||||||||||
|
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角色。 代码:
执行Client,输出结果: C:\Strategy>javac *.java 我们可以发现,Client里可以动态的切换加密的算法。C:\Strategy>java Client encrypt by DES algorithm. encrypt by MD5 algorithm. C:\Strategy> 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模式就是为了将部件和组装过程分开. 如何使用? 首先,需要一个接口,它定义如何创建复杂对象的各个部件:
用Director构建最后的复杂对象,而在上面Builder接口中封装的是如何创建一个个部件(复杂对象是由这些部件组成的),也就是说Director的内容是如何将部件最后组装成成品:
Builder的具体实现ConcreteBuilder:
复杂对象:产品Product:
复杂对象的部件:
我们看看如何调用Builder模式;
ConcreteBuilder builder = new ConcreteBuilder(); Builder模式的应用 "池"实际是一段内存,当池中有一些复杂的资源的"断肢"(比如数据库的连接池,也许有时一个连接会中断),如果循环再利用这些"断肢",将提高内存使用效率,提高池的性能.修改Builder模式中Director类使之能诊断"断肢"断在哪个部件上,再修复这个部件. 具体英文文章见:Recycle broken objects in resource pools18 septiembre 设计模式_抽象工厂 对设计模式的理解,我始终理解的不是很透彻. 今天读到一篇文章,感觉还不错,摘过来 分享了... ...
在前面介绍的两个创建型模式里面,我们解决的都是有关"new"的问题,用它们来避免显式指定类创建对象。我写的也非常简单易懂,相信看过的朋友们都应该对简单工厂模式、工厂方法模式的意图、所能解决的问题及适用情景有一定的了解了。但是若要达到灵活运用,什么时候用,怎样用合适还不是看一篇文章就能解决的问题。呵呵..这需要你对OO的理解程度,你的项目开发经验等等许多方面的积累。一起努力喔。。
好了,咱们言归正传,通过对这两个模式的了解,我们掌握一种思想,就是在创建一个对象时,需要把容易发生变化的地方给封装起来,来控制变化(哪里变化,封装哪里),以适应客户的变动,项目的扩展。但是,我们在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作,同时由于需求的变化,这“一系列相互依赖的对象”也要改变,如何应对这种变化呢?如何像简单工厂模式、工厂方法模式一样绕过常规的"new",然后提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?可能有人会说,你也可以将这些对象一个一个通过工厂方法模式来解决呀?但是,我们试想,既然是一系列相互依赖的对象,它们是有联系的,每个对象都这样解决,你又如何来保证他们的联系呢?举一个例子:Windows桌面主题,当你更换一个桌面主题的时候,系统的开始按钮、任务栏、菜单栏、工具栏等等都变了,而且是一起变的,他们的色调都还很一致,难道类似这样的问题,怎么来解决呢?它的天敌就是抽象工厂模式。 意图:
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 参考者: 也就是该模式中的各个类或对象之间的关系:
抽象工厂模式UML图 抽象工厂模式在生活中的实例 分析: 小结一下: 优点: 隔离了具体类的生成,客户不需要知道怎样生成了每一个具体产品,什么时间生成的。它将客户与具体的类分离,依赖于抽象类,耦合性低。 难以支持新种类的产品。难以扩展抽象工厂以生产新种类的产品。这是因为抽象工厂几口确定了可以被创建的产品集合,支持新种类的产品就需要扩展该工厂接口,这将涉及抽象工厂类及其所有子类的改变。 同一个产品族的产品在一起使用时,而且它们之间是相互依赖的,不可分离 游戏开发中的多风格系列场景
|
||||||||||
|
|