-
2005-01-28
关注,AOP
AOP的核心概念是关注点,我开始关注AOP。
Why AOP?
a) AOP一般观点
一般在开发系统时,我们可以大致的把系统的需求分类为核心模块级需求和系统级需求。很多系统级需求一般来说是相互独立的,但它们一般都会横切许多核心级模块。以一个电信领域的短信网关系统为例,系统的核心级需求是短信的收发,话单处理等,而其系统级的需求包括日志,校验以及性能问题等。像日志这种系统级的需求就横切短信收发、话单处理等几乎所有网关核心级需求。虽然横切需求会跨越多个模块,但目前的技术倾向于使用一维的方法学来处理这种横切需求,把对应需求的实现强行限制在一维的空间里。这个一维空间就是核心模块级实现,其他横切需求的实现被嵌入在这个占统治地位的空间,换句话说,需求空间是一个n维空间,而实现空间是一维空间,这种不匹配导致了糟糕的需求到实现的映射。
b) AOP开发过程
AOP(面向方面的编程方式)将上面所述的横切需求和核心需求都通通称为关注点(concerns)。AOP认为每一个复杂的系统都可以看作是由多个关注点来组合实现的。在这样的情况下,利用AOP开发系统的过程就变成了“识别关注点”(方面分解)---〉“实现关注点”---〉“组装关注点”(方面的重组,又叫织入)。
AOP和OOP
a) AOP作为OOP的补充,它提供了另外一种考量程序结构(program structure)的方式。OO将系统分解成一系列具有继承体系关系的objects;而AOP则将系统分解为aspects or concerns。而且AOP可更好地将一些横切concerns模块化,便于复用和维护。
b) AOP,从其本质上讲,使你可以用一种松散耦合的方式来实现独立的关注点,然后,组合这些松散耦合的、模块化实现的横切关注点来搭建系统。与之对照,用OOP建立的系统则是用松散耦合的模块化实现的一般关注点来实现的。在AOP中,这些良好模块化的横切关注点的实现单元叫方面(aspect),而在OOP中,这些一般关注点的实现单元叫做类(class)。
c) 在AOP里,每个关注点的实现并不知道是否有其它关注点关注它,这是AOP和OOP的主要区别,在AOP里,组合的流向是从横切关注点到核心关注点,而OOP则恰恰相反。
d) 感觉AOP是在OOP后对那些objects重新分类的过程,将被广泛关注的objects(即横切于多个objects中的objects)提取出来,加工后再重新和原来核心objects组合起来。
AOP基本概念
下面是一些AOP基本概念的解释,摘录自AspectJ的FAQ和Spring AOP的Online book
a) Aspect(方面): A modularization of a concern for which the implementation might otherwise cut across multiple objects。
b) Joinpoint(连接点): Join points are well-defined points in the execution of a program. Not every execution point is a join point: only those points that can be used in a disciplined and principled manner are。
c) Advice(通知): Advice is code that executes at each join point picked out by a pointcut There are three kinds of advice: before advice, around advice and after advice. As their names suggest, before advice runs before the join point executes; around advice executes before and after the join point; and after advice executes after the join point. The power of advice comes from the advice being able to access values in the execution context of a pointcut.
d) Pointcut(横切点): A set of joinpoints specifying when an advice should fire. An AOP framework must allow developers to specify pointcuts: for example, using regular expressions. A pointcut picks out join points . These join points are described by the pointcut declaration. Pointcuts can be defined in classes or in aspects, and can be named or be anonymous.
e) Weaving(织入):将Aspects组装成为最终系统。这个织入过程可以在编译期完成,也可以在运行时完成。 -
2005-01-28
Boost_1_32_0版源代码编译
著名的C++准标准库boost在2004年末发布了1.32.0版本,作为C++的忠实Fans怎能“袖手旁观”,趁闲暇时download it and build it。
[注]:由于没有公司Unix服务器的管理员权限,所以只能在自己的Windows平台上编译了。
1、前提
a) 下载Boost_1_32_0源码包(http://sourceforge.net/project/showfiles.php?group_id=7586 ),由于在Windows平台下编译,我们可以选择下载boost_1_32_0.exe文件;
b) 安装Windows下的GCC版本(这里我们使用开源的GCC作为编译工具,因为其Free),我选择了MinGW-3.2.0-rc-3(http://sourceforge.net/forum/forum.php?forum_id=438862 );
c) 下载Boost专用build工具bjam,在Windows平台下我们可以得到已经编译好的bjam.exe(http://sourceforge.net/project/showfiles.php?group_id=7586&package_id=72941 ),你可以 下载boost-jam-3.1.10-1-ntx86.zip文件。
2、目录结构
a) 运行下载后的boost_1_32_0.exe文件,选择解压缩到的目录即可。如D:\boost_1_32_0;
b) 将下载的bjam.exe拷贝到上述目录中。
3、设置环境变量
a) 由于默认情况下,MinGW会安装在C:\MinGW下,一旦你选择其他目录,你就需要自己设置Path。将%Your_MinGW_Install_Path%\bin加入到你的系统Path变量中。
4、Build
在命令行下进入到boost源码包存放的目录,如D:\boost_1_32_0,然后运行:
bjam “-sTOOLS=gcc” install
编译由此开始,如果你的系统没有装Python相关的东东,在编译的最开始会提示你一些warnings,无须理它,让它继续吧!默认的boost安装路径为C:\boost,我们这里采用默认路径。
5、Build结果
编译的过程是漫长的(一般配置的机器2小时左右),需要你耐心的等待。
编译后结果:在C:\Boost下生成两个文件夹include和lib,在D:\ boost_1_32_0中则多了一个bin目录。
6、应用boost
a) 设置boost库
如果你使用的IDE(比如Microsoft Visual Studio或者MinGW Developer Studio(http://petra.hos.u-szeged.hu/~aking/www.parinya.ca/download/MinGWStudioFullSetupPlus-2.05.exe )),你可以在IDE的include和lib路径中直接设置boost库的位置。如果你用的是命令行工具,对于复杂的项目构建估计你需要写你自己的Makefile了。
b) 使用boost的例子
//HelloBoost.cpp
#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>
using namespace std;
using namespace boost;
int main()
{
string s = "Hello Boost,This is a test";
tokenizer<> tok(s);
for(tokenizer<>::iterator beg=tok.begin(); beg != tok.end(); ++beg)
{
cout << *beg << "\n";
}
}
//Compile: g++ -o HelloBoost.exe HelloBoost.cpp -I"C:\Boost\include\boost-1_32"
//output:
Hello
Boost
This
is
a
test
现在你可以尽情去研究boost,体会它的无比强大了。
-
2005-01-22
Hello AOP!
论坛上有人评价Java在过去的2004有两大值得称赞的技术发展,一个是Java在IOC模式上的成熟应用,再就是Java的AOP框架。
好消息
2005年一月份传来好消息,AspectJ与AspectWerkz合力打造AOP框架,两个开发团队将合力开发一个全新的面向方面编程(Aspect-Oriented Programming,AOP)平台,在其中融合双方的长处和经验。这两家都是业界重要的开源AOP实现,不过走了不同的技术路线:AspectJ一直坚持“预编译+源码生成”,AspectWerkz则是“元数据+运行时织入”的代表。关于两种技术路线、两种产品的争论一直是AOP社群的热点话题,如今两个开源组织决定彻底解决这个困扰。两家合并之后的第一个产品将是AspectJ 5,其中既有AspectJ风格的、基于语言扩展的AOP,也有AspectWerkz风格的、基于XML(和JSR-175 annotation)的AOP。随后,双方还会继续融合各自的长处和经验,努力提供一个完善而统一的AOP平台。
下载了AspectJ和AspectWerkz,并分别看了一下各自的FAQ,得到2点结论:
Ø 现在的AOP技术走势是动态织入;
Ø AspectWerkz没有添加新的语法之类的东西,它使用的是标准的Java语法,上手简单,而AspectJ则构建一套自己的特殊语法,上手不易。
所以在对AOP进行感性认识的阶段,我更倾向于使用AspectWerkz。
Hello AOP!
其实每个人在学习一门新的语言或技术的时候,都会经历一个“成就感驱动的”的学习过程。拿C为例,几乎每个初学者都会在几乎不懂任何C语法的情况下,照猫画虎地写出一个“Hello World”程序来。Why? Because he/she needs this feeling!如果连这样的一个小小的程序都运行不起来或者说出现满屏的错误,初学者的心理就会受到很大影响,也可能这个学习过程会就此结束。OK!作为一个AOP的初学者,我们也来个“Hello AOP”,使用AspectWerkz。
上面已经提到,AspectWerkz使用标准Java语法,只要你学过Java,you have got the passport of the first gate to AOP。
搭建AOP环境
Ø 下载AspectWerkz包(http://aspectwerkz.codehaus.org/index-aw.html )后解压到一个目录(假设为D:\aw_2_0_2),然后设置环境变量ASPECTWERKZ_HOME=D:\ aw_2_0_2。
Ø 设置PATH环境变量
PATH=%ASPECTWERKZ_HOME%\bin
Ø 设置CLASSPATH环境变量
CLASSPATH=%ASPECTWERKZ_HOME%\lib\aspectwerkz-2.0.RC2.jar;%ASPECTWERKZ_HOME%\lib\aspectwerkz-jdk14-2.0.RC2.jar;%ASPECTWERKZ_HOME%\lib\dom4j-1.4.jar;%ASPECTWERKZ_HOME%\lib\qdox-1.4.jar;%ASPECTWERKZ_HOME%\lib\concurrent-1.3.1.jar;%ASPECTWERKZ_HOME%\lib\trove-1.0.2.jar;%ASPECTWERKZ_HOME%\lib\jrexx-1.1.1.jar;%CLASSPATH%
开始AOP
1)这里我们要在屏幕打印出“Hello AOP!”,看如下代码:
//HelloAOP.java
public class HelloAOP {
public static void main(String args[]) {
HelloAOP ha = new HelloAOP();
ha.test();
}
public void test() {
System.out.println("Hello AOP!");
}
}
编译HelloAOP.java文件:javac HelloAOP.java
2)现在我要在输出“Hello AOP!”前后做一些工作,这些工作在运行时会得到调用机会,如果使用AOP术语,我们可以说我们要编写我们的aspect,这个aspect会在运行时被weave into (织入)HelloAOP class。
//MyAspect.java
import org.codehaus.aspectwerkz.joinpoint.JoinPoint;
public class MyAspect {
public void beforeTesting(JoinPoint joinPoint) {
System.out.println("before testing...");
}
public void afterTesting(JoinPoint joinPoint) {
System.out.println("after testing...");
}
}
javac MyAspect.java
3)织入过程并不简单,我们需要撰写一个描述文件来将aspect和其织入的class中的信息联系起来。
//aop.xml
<aspectwerkz>
<system id="AspectWerkzExample">
<aspect class="MyAspect">
<pointcut name="testMethod" expression="execution(* HelloAOP.test(..))"/>
<advice name="beforeTesting" type="before" bind-to="testMethod"/>
<advice name="afterTesting" type="after" bind-to="testMethod"/>
</aspect>
</system>
</aspectwerkz>
4)run it
aspectwerkz -Daspectwerkz.definition.file=aop.xml HelloAOP
//output:
before testing...
Hello AOP!
after testing...
Everything is fine!
-
2005-01-19
Build ArgoUML and AndroMDA
由于Dominoo近期策略的改变,所以我开始关注和研究UML和MDA领域较为出名的开源项目。我准备先拿ArgoUML和AndroMDA“开刀”。
ArgoUML
Ø 简介:ArgoUML is a modelling tool that helps you do your design using UML and it is the winner of 13th annual productivity award of software development。
Ø 主页:http://argouml.tigris.org
AndroMDA
Ø 简介:AndroMDA is an open source MDA framework。
构建(build)方法
In my opinion,常见开源项目构建的两种方法:
Ø Get the source code from cvs and build it in an IDE;
Ø Download the source code package and build it with your patience。
第二个方法显然比较困难,不过这里我将采用之。
Build ArgoUML
1. 前提
Ø 下载ArgoUML源码包(ArgoUML-0.16.1-src.zip)及相关库文件(JAR文件);
Ø 安装JDK1.4以上版本(不要使用JDK5.0);
Ø 安装Ant1.4.1以上版本。
2. 目录结构
Ø src_new:存放ArgoUML源码文件(src_new是解开源代码包后默认的目录名,如果你要换目录名,请注意修改src_new/build.xml文件中相关名字)
Ø lib:存放构建ArgoUML所需的相关JAR文件,包括nsuml.jar, ocl-argo.jar, gef.jar, i18n.jar, antlrall.jar, toolbar.jar,xerces.jar,jmi.jar,log4j.jar。
3. 设置环境变量
Ø 设置JAVA_HOME环境变量;(一般安装JDK后会自动设置);
Ø 设置lib中相关JAR文件的CLASSPATH;
4. 修改src_new\build.bat文件
Ø 设置ANT_HOME=${Your_Ant_Install_Path};//如ANT_HOME=D:\OpenSource\Ant-1.6.2
5. 运行build.bat
在命令行下进入src_new目录,敲入build即可。
6. Build结果
在src_new目录所在的目录下会创建build目录,所有编译生成的.class文件都会放在build\classes中相应的子目录中。
7. 小技巧
由于一旦编译出错就会在屏幕上打印出太多的信息,而Windows命令行窗口会自动删除以前过多的输出信息,这样就为我们差错带来不便,建议使用命令管道,如build > build_output.txt,这样build过程所输出的信息就会重定向到build_output.txt文件中,便于我们查找出错之处。
Build AndroMDA
1. 前提
Ø 下载AndroMDA源码包(andromad-src-3.0M3.zip);
Ø 安装JDK1.4以上版本;
Ø 安装Ant;
Ø 安装Maven1.0以上版本。
2. 目录结构
Ø andromda-src-3.0M3:存放AndroMDA源码文件
3. 设置环境变量
Ø 设置JAVA_HOME环境变量;(一般安装JDK后会自动设置);
Ø 设置ANT_HOME环境变量;ANT_HOME=${Your_Ant_Install_Path};
Ø 设置MAVEN_HOME环境变量:MAVEN_HOME=${Your_Maven_Install_Path};
Ø 设置Path环境变量:在Path环境变量中加入%MAVEN_HOME%\bin;
Ø 设置MAVEN_OPTS环境变量:MAVEN_OPTS=-XX:MaxPermSize=128m -Xmx512m。
4. 创建项目的build.properties文件
在andromda-src-3.0M3目录下创建build.properties文件,文件内容如下:
## ----------------------------------------------------------
## ${user project home}/build.properties
## ----------------------------------------------------------
maven.proxy.host = xx.xx.xx
maven.proxy.port = 8080
maven.proxy.username = xx
maven.proxy.password = xx
maven.repo.remote = http://public.planetmirror.com/pub/maven,
http://mirrors.sunsite.dk/maven/,
http://www.ibiblio.org/maven/
#maven.proxy.username =
#maven.proxy.password =
如果你是通过代理服务器上网,请注意代理服务器的设置。
5. Build
在命令行下进入andromda-src-3.0M3目录,敲入Maven即可。
万里长征终于走出了第一步!看到屏幕上的BUILD SUCCESSFUL,心里还是蛮痛快的^_^。
-
2005-01-17
Dominoo项目日记(四)
大家都是对软件充满无限激情的人,大家都致力于能开发出能被大家所广泛接受和使用的软件。可是激情归激情,我们还得脚踏实地。项目遇到了障碍,我们需要改变思路。
从04年的12月末到现在,大家好像都不约而同地沉寂了下来,好像大家都累了或者都在反思着某些事情,大家好像都有一个感觉就是我们对我们所要做的并不是那么的熟悉和了解。我们并不能详细而又清晰的勾勒出我们要做的东西的轮廓。想象一下一个没有吃过“葱烧海参”的厨师做出的“葱烧海参”会是个什么样子呢?也许可以吃,但最大的可能是变了味儿。在Dreamhead的题为“Dominoo的方向”的mail中也提到“创新的前提是什么?绝不是凭空制造。了解已有的东西,在此基础之上找到不足之处,再图改进。”的确我们了解的太少,我坚信我们要做的东西会是一个“innovation”,但是想归想,真正做起来我们还是要考虑方法的,还是那句话“脚踏实地,厚积而勃发”。
针对以上项目出现的问题,我们改变了思路。Dreamhead语“对于Dominoo而言,对于现在的我们而言,我们面对的是一大片开阔地,虽然那里已有人迹,但我们尚未过去。”我们要走过去看看,看看人家的做法,做到知己知彼。
我们决定从下列角度切入:
Ø 了解工具:现有的开源UML工具、MDA工具
Ø 了解新技术理论:AOP、DSL、LOP等
Ø 了解基础技术:语言特性、编译器技术。
-
2005-01-16
饮水机的加热保护
小小饮水机,里面也有值得思考的东西。
起因
昨晚回到寝室感到口渴,顺手按下了门旁饮水机的加热开关,到洗漱间洗了把脸,拿着水杯出来接水,看到加热灯已经变成绿色(我们的饮水机的加热指示灯在加热过程中是红色的,加热结束后会变成绿色,我想大多数饮水机都是这样的)我就按下热水出水开关接热水,可是却不见热水流出,这时我才注意到原来水桶里已经没水了。当时想的是这样无水加热会不会烧坏饮水机,恰好旁边有一装满水的新桶,我快速将之换上,心里想着那个绿灯肯定会马上变红,可是等了半天绿灯还是绿灯,没有任何重新加热的迹象,我很是感到奇怪,我尝试着按下热水出水开关,烧开的热水不竭的流到我的杯子中,当时就是感到挺困惑的,但是也没多想,咕噜噜的喝着烧开的热水,真是解渴亚。
加热保护
今天早上在来公司路上,突然灵光一现,想到了这个问题:“这可能就是饮水机的加热保护在作怪”。实际上在我加热的时候饮水机里的加热槽里是有水的,但是为了防止用户将这里的水全部用光,导致无水加热,饮水机在用户没有换新桶之前是不会放出这里的水的,这也是在警告用户该换桶了。直到用户换完新桶,饮水机的担心解除了,才自然把水流到你的杯子中。
猜想饮水机加热关注点
其实饮水机只需关注是否有新水源,如果有,一切OK,你想加热就加热,你想出水就出水;一旦没有新水源了,你可以加热,但是你是喝不到热水的。
-
2005-01-16
深入Java底层
在一个朋友的书架上发现王森著的《Java深度历险》一书,看了书的前言了解该书是关于Java底层技术内幕的。怀着好奇心浏览了一下,谈不上有太多收获,但也记下了一些自认为有益的两点。
Java xxx
我们在命令行下敲入:“java xxx”后会发生什么呢?
流程如下:
1. 找到JRE;
2. 找到JVM.dll;
3. 启动JVM,并进行初始化;
4. 产生Bootstrap Loader;
5. 载入ExtClassLoader;(Ext – Extended)
6. 载入AppClassLoader;
7. 加载xxx类。
书中提到Bootstrap Loader、ExtClassLoader和AppClassLoader构成了Java的“类加载器继承体系--class loader hierarchy”,其中Bootstrap Loader是由C++编写的,其他两个是由Java写的。之所以成为“继承体系”是因为这三个loader之间是有联系的。Bootstrap Loader负责加载ExtClassLoader,后者ExtClassLoader就将其parent置为Bootstrap Loader。AppClassLoader较为特殊,虽然由Bootstrap载入,但是其parent却置为ExtClassLoader。其原因是为了实现“委托模型”。简述“委托模型”就是当类加载器有加载类的需求时,会先请求其parent使用其搜索路径帮助加载,如果其parent找不到,才使用自己的搜索路径进行加载。如上述所说当ExtClassLoader想载入AppClassLoader类时它首先请求其parent “Bootstrap Loader”帮忙,Bootstrap Loader将AppClassLoader载入后,由于这个载入是ExtClassLoader请求的,所以AppClassLoader的parent还是置为ExtClassLoader而不是Bootstrap Loader。
类的加载流程
类加载的时候遵循一个原则:“类加载器会依类的继承体系从上至下依次加载”。举个例子:“如果C继承了B并实现了接口I,而B有继承自A”,则类加载器在加载C时,加载的次序会是AàBàIàC,(注:interface会如同class一样被Java编译器编译为独立的.class文件)
其实我对底层的东西并未给与太多的关注,如果在哪个项目中需要我去了解底层的话,我会去很好的学习。
-
2005-01-12
Mix-in in Ruby
在Matz的一篇PPT“Object-Oriented scripting in Ruby”中,Matz提到Ruby提供一种语言机制Mix-in,在其PPT中如是描述的“No Multiple Inheritance,but Mix-in”、“Mix-in is as strong as multiple inheritance,but simple”。
“module” in Ruby
在C++、Java、C#中都有namespace的概念,是用来隔离代码,防止代码冲突的。在Ruby中是采用“module”这个关键字来完成namespace功能的。
//considering the following code:
//MicrosoftCompiler.rb
module Microsoft
def Microsoft.compile
print "This is microsoft's compiler", "\n"
end
end
//SunCompiler.rb
module Sun
def Sun.compile
print "This is Sun's compiler", "\n"
end
end
//TestCompiler.rb
require "D:\\Ruby\\MicrosoftCompiler.rb"
require "D:\\Ruby\\SunCompiler.rb"
Microsoft.compile
Sun.compile
//output:
>ruby TestComplier.rb
This is microsoft's compiler
This is Sun's compiler
>Exit code: 0
module除了体现namespace的概念外,从上面代码中我们还可以看到在两个module中定义的method和调用这个method时都在其前面加上其所在module的名字了。这样定义的函数叫module method。但是不是所有要使用module中函数都要显式的加上module名字呢?答案:不是。请往下看。
Mix-in :Another wonderful use of “module”
由module method的定义和调用方式让我们联想到class method(注意不是class instance method),也让我们从module联想到class了。但是A module是不能像class那样拥有实例的,module毕竟还不是class,但是我们可以在class的定义中“include”一个module,这样会发生什么呢?
//considering the following code:
module Mammal # 哺乳动物
def suckle # 哺乳
print "I can suckle my baby" ,"\n"
end
end
module Flyable #可飞行的
def fly #飞行
print "I can fly", "\n"
end
end
class Chiropter #蝙蝠
include Mammal #蝙蝠是哺乳动物
include Flying #蝙蝠可以飞行
end
aChiropter = Chiropter.new
aChiropter.suckle
aChiropter.fly
//output:
>ruby TestMixin.rb
I can suckle my baby
I can fly
>Exit code: 0
如上面代码得出的结果我们可以看出,一旦一个class中include了一个module,那么所有module中的methods就好像成为class’s instance methods一样,这就是mix-in,看起来被mix-in的modules的行为更像是这个class的super class,通过这种方法我们可以间接实现多重继承。但是注意看看被mixin的module中的函数是如何定义的?和前面代表namespace概念的module中的method定义不同的是在于定义时method name前不见了module name,这样定义出来的method叫module instance method,虽然module不能实例化,但是一旦被mixin到某个class中,这些module instance method的使用就等价于class instance method了。
总结:
1. Module的两种用法
Ø 体现namespace概念;
Ø 被mixin到class中间接的实现类的多重继承。
2. Module中的两种method定义和用法
module module_name
def module_name.module_method_name
# module method , for representing namespace concept
end
def module_instance_method_name
# module instance method
# Once the module has been mixed in,
#the method will be equal to a class instance method
end
end
-
2005-01-09
由一段话想到的...
在Java视线论坛的Python/Zope版,浏览了管理员robbin发的题目为“我眼中的Python”的帖子,感触颇深。
Robbin如是说:“做为一种严谨的,编译式的,面向对象语言,Java总是给我一种须正襟危坐,须一板一眼的按照OOAD的规则编程,才敢在键盘上敲下字符的感觉。即使编写一个最小规模的程序,我也不能够接受把所有的code塞到main里面的做法。Java似乎以不怒自威的威严使我不敢随意编码,不敢玷污Java的严谨。于是我即使写一个很简单的JDBC程序,也要一板一眼的try catch finally,一层层的处理Connection,PreparedStatement和ResultSet。”
Robbin的这番话真是说到我的心坎上了,也是在最近才发现居然“不敢”写Java代码了,自从看了Bob大叔的那本经典“敏捷软件开发”后,面对那么多经典的OO规则,生怕自己写出的代码太滥,虽然有refactoring坐镇,但是心里还是发虚。
像C++、Java等一些语言,都是诞生于OO开始起步的年代,身上难免都散发着“学院派OO”的味道,所以也常被人们称为学院派的语言。今天我们已经步入了被称为“后OO时代”的阶段,此时此刻C++、Java等传统的学院派反倒让人们感到了一丝沉重。人类都有一种叛逆的行为,不愿意被束缚,所以有些人开始尝试像Python、Ruby等OO时代后期才诞生的面向对象的动态类型语言,并开始喜欢上这些有着“轻盈身段”并且“毫无包袱”的新生代了。Now我对Ruby、Python等动态语言的认识也仅仅是停留在“Hello World!”这种级别上的,一切关于它们的论述也都只是道听途说。也许不在规则束缚下的生活是美好的。
这几天一直都不想编码,除了看书就是将看书后的想法写下来,这也是最近我的blog数量变多的一个原因。任何事情走到了极点都会调转方向,也许从明天开始我将“沉溺”在代码的世界中。
-
2005-01-09
开放与封闭
敏捷设计最基本原则:“开放封闭原则(OCP,Open-Close Principle)”
回顾SRP
在开始谈OCP之前,我们还是简单回顾一下Bob大叔在其书中所论述的敏捷设计的第一个原则“单一职责原则(SRP,Single Responsibility Principle)”。
Bob大叔在其书中将职责理解为“变化的原因”。一般当需求变化时,该变化就会反映为类的职责的变化。按书中所述“如果一个类的职责过多,会使职责间产生耦合,一个职责的变化可能会削弱或抑制另一个类完成其他职责的能力,导致该设计的脆弱性。”根据这点论述得出结论:“就一个类而言,应该仅有一个引起它变化的原因”。
简单举个书中的例子理解一下(这里不作详细说明):
//Modem.java
public interface Modem{
void dial(String portNo);
void hangup();
void send(char c);
void recv();
}
缺点:Modem有两个职责,分别是连接管理和数据通信。经过单一职责分解后==〉
//DataChannel.java
public interface DataChannel{
void send(char c);
void recv();
}
//Connection.java
public interface Connection{
void dial(String portNo);
void hangup();
}
//Modem.java
public class Modem implements DateChannel , Connection{
//...
}
曾经在大学的时候用MFC开发程序,当然现在看起来那些程序的设计很糟糕。其中一个典型的缺点就是类的职责分配不明确,当时我常常在一个界面类中写入业务逻辑代码,比如email处理等等。也看过很多网友的MFC代码,出现此类糟糕设计的还是很多的。
OCP-Open for extension,Closed for modification
引用书中论述“软件实体(类、模块、函数等等)应该是可以扩展的,但是不可修改”。
如何做到OCP?-- 抽象
让A模块依赖一个接口或一个抽象类,这样对A模块的修改可以是Closed的。那么如何对A模块进行扩展呢?我们可以通过扩展那个接口的多种实现或者继承那个抽象基类做到这点。
Template Method模式的应用可以很好的解释上面的论述。
//某一个功能模块A是这样依赖一个抽象类AbsBase的:
void callSomething(AbsBase a){
a.templateMethod();
}
//AbsBase的定义,一个即开放又封闭的基类。
public abstract class AbsBase{
public abstract void method1();
public abstract void method2();
public void templateMethod(){
method1();
method2();
}
}
下面我们就可以通过AbsBase的子类来扩展功能模块A的行为了
public class DerivedClass extends AbsBase{
public void method1(){
//...
}
Public void method2(){
//...
}
}
从上面可以看出Template method模式可以帮助我们去OCP。
请时刻想着“抽象”这一实现OCP的基本原则。
在看书的过程中,我感觉到有时候不能为了OCP而去OCP,还需考虑实际情况和系统的整体架构。作者在书中也提到“仅对程序中呈现出的频繁变化的那些部分做抽象。拒绝不成熟的抽象和抽象本身一样重要。”










