面向对象软件开发的敏捷过程Word下载.docx
《面向对象软件开发的敏捷过程Word下载.docx》由会员分享,可在线阅读,更多相关《面向对象软件开发的敏捷过程Word下载.docx(10页珍藏版)》请在冰豆网上搜索。
fragility对系统的改动会导致系统中和改动的地方在概念上无关的许多地方出现问题。
出现新问题的地方和改动的地方没有概念上的关联,难以排错,排错的过程中又会引入更多的&
臭虫&
。
牢固性immobility很难解开系统的纠结,使它成为其他系统中重用的组件。
系统中包含了对其他系统中有用的功能,当其他人想复用这个功能到新的系统时,剥离出独立的组件的难度远远大于重新实现的难度,在时间和进度的压力下,大多数人只有选择拷贝涂鸦的方式来实现新系统的功能。
粘滞性:
viscosity做正确的事情比错误的事情要困难。
程序完成正常的功能总是倾向于得到不正确的结果。
不必要的复杂性:
needlesscomplexity设计中包含有不具有任何直接好处的基础结构。
为了预防后期维护更改需求的对源码的修改,在设计之初放置了那些处理潜在变化的代码来保持软件的灵活性,这样的结果是软件中包含了很多复杂的结构,理解起来更加困难。
不必要的重复:
needlessrepetition设计中包含有重复的结构,而该重复的结构可以使用单一的抽象进行统一。
对鼠标右键(剪切,复制,粘贴)的滥用,使得完成同一或类似的代码片断出现在系统各处。
如果原始的代码段完成的功能需要变化,或者存在错误,排错和增加新的功能变得非常困难。
晦涩性:
opacity很难阅读,理解。
没有很好的表现出意图。
以上讨论了系统构架的臭味,下面讨论微观层次上代码的臭味:
代码的臭味
重复代码:
重复的代码使得更改功能和排错更加困难。
同样的模块错误会在拷贝粘贴的程序各处多次出现。
过长的函数:
程序越长越难于理解,这已经是软件业开发的常识。
越难理解的程序,使用维护的成本就越大。
如果一个函数的行数超过一页,很少有人能够在看到下一页的时候还清楚的记得函数开头的变量定义,理解和查错更加困难。
过大类:
在一个类中完成几乎所有需要的功能。
十项全能的人是不存在的,软件也一样。
过长的参数列:
如果一个函数(方法)的调用参数过长,使用这个函数的调用过程也一定是困难的。
想象一下,调用一个十个以上参数存储过程会有多么痛苦。
这还只是开始,如果任一个参数的定义(名称,类型)发生轻微的变化,函数的调用客户端会有多么大的改动。
其他的臭味还有发散式变化,散弹枪修改,依恋情结,数据泥团,基本型别偏执,复杂的switch分支语句,平行的继承体系,冗赘类,夸夸其谈的未来性,令人迷惑的暂时值域,过度耦合的消息链,中间转手人,狎昵关系,异曲同工的类,不完美的程序库类,纯数据类(数据哑元),子类不需要父类的某些特性,过多注释。
详细的讨论可以参见《重构》的介绍。
面向对象软件设计的原则:
总体原则:
1.针对于接口(抽象)编程,而不要针对于实现(具体)编程。
举例来说:
操作系统是对逻辑计算机的抽象,通过操作系统的抽象我们不需要考虑具体使用的硬件配置,可以在较高的层次上进行更高生产力的应用。
再如:
汇编语言对机器的0,1代码进行了抽象,大大加快了开发效率,后来使用的高级语言和第四代语言模型驱动抽象的级别更高,生产力也更高。
java和.net实现于一个抽象的软件虚拟机,进一步使开发出来的组件可以跨平台和操作系统。
通过抽象出数据访问层(持久化层),可以使业务逻辑和具体的数据库访问代码分离,更换数据库提供商对已有的组件没有影响。
具体实现可以参照hibernate实现和dao(数据访问对象)模式。
优势:
1)降低程序各个部分之间的耦合性,使程序模块互换成为可能。
调用的客户端无需知道具体使用的对象类型,只要对象有客户希望的接口就可以使用,对象具体是如何实现这些接口的,客户并不需要考虑。
2)简化了程序各个部分的单元测试,将需要测试的程序模块中通过重构提炼出抽象的接口,然后编制和接口一致的Mock类,测试就会变得很容易。
如果应用了测试优先的方法,从简化客户端调用的角度,还可以经过抽象改善软件模块的设计。
3)模块的部署升级由于模块之间的耦合度降低变得更加容易。
相关的设计模式有创建型模式中的工厂模式,结构型模式中的代理模式和组合模式等。
2.对象组合优于类继承。
面向对象为软件开发引入了三大工具:
继承,多态和重载。
继承使得程序员可以快速的通过扩展子类来增加功能,但是由于继承是在编译时确定的,因此增加的功能较多时,继承不够灵活,还有可能出现&
子类爆炸&
的局面(为了完成新添功能,不得不在继承体系中添入大量的之间只有细微差别的子类,掌握使用扩展都会变得非常困难)。
而通过对象的组合,可以动态透明的添加功能。
相关设计模式有装饰模式和代理模式等。
3.分离变化。
前面说过需求是在不断变化的,不同的变化可能是仓库安全库存的计算方法,可能是报表和数据的展现形式,通过把这些不同的变化识别并分离出来不同的对象委托,简化了客户端的调用和升级。
相关的设计模式有命令模式,观察者模式,策略模式,状态模式,模版方法模式等。
具体原则:
1.单一职责原则srp(singleresponsibilityprinciple):
一个模块的功能应该尽可能的内聚。
如果一个类发生了变化,引起变化的原因应该有且只有一个。
每一个类承担的职责都是一个变化的轴线,需求变化时,会体现为类的职责的变化。
如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化会影响这个类完成其他职责的能力,会出现前面所说的软件的臭味之一脆弱性。
相关的设计模式有
2.开放封闭原则ocp(openclosedprinciple):
一个模块应该对功能的扩展开放,支持新的行为,对自身的更改封闭。
每次对模块的修改都可能会引入新的错误和新的依赖。
因此扩展新功能时,已经编好的模块源码和二进制代码都是不应该修改的。
相关的设计模式有适配器模式,桥接模式,访问者模式等。
3.Liskov替换原则lsp(liskovsubtitleprinciple)子类型必须可以替换掉他的基类型。
一个基类的多个子类型之间要完成动态的替换,各个子类型必须都可以被他们的基类型替换,这样他们之间动态替换后,客户端调用的代码就不需要冗赘的switch类型判断代码。
如果子类型无法替换基类型,将会导致在派生类对象作为基类对象进行传值时的错误。
这样多态机制处于瘫痪状态了。
相关设计模式为组合模式。
4.依赖倒置原则dip(dependentinverseprinciple)高层模块不应该依赖于底层模块,抽象不应该依赖于细节,细节应该依赖于抽象。
假定所有的具体类都是回变化的,因此如果一个客户端依赖于(调用或声明)具体的类型,那么当这个具体的类型变化时,依赖的客户端业必须同时进行修改。
这些具体的更改可能出现在使用了某个特定的网络协议,特殊的系统api调用,特定的数据库储存过程等,这些用法或多或少都会使客户端调用和具体类型成为铁板一块,比较难于重用。
Java社区中比较热门的j2ee轻量级容器框架spring就很好的实现了本原则。
5.接口隔离原则isp(interfacesegregationprinciple)不应该强迫客户依赖于它们不使用的方法。
因为每一个实现接口的对象必须实现所有接口中定义的方法。
如果接口的粒度比较小,实现接口的对象可以使用一种即用即付的方式动态实现接口。
每个接口的粒度很小,复用起来也非常容易。
这体现了一个趋势:
为了更好的实现重用,接口,函数,模块和类等倾向于更容易使用的&
小&
体积。
敏捷软件开发的宣言和实践:
软件开发项目的失败使得人们开始思考软件开发的过程,人们希望通过引入严格的过程控制产生软件生命周期中各个阶段的文档和制品来保证软件的质量。
比较出名的业界实施方法论有cmmi(能力成熟度模型)和rup(瑞理统一过程),这些方法论都是重型的。
举例来说,没有经过剪裁的Rup实现起来,至少需要在全周期完成四十个以上的制品文档,文档的编写维护和源代码的同步等需要非常多的资源,十人以下的开发团队一般没有精力、时间、人员配置完成这些制品。
失控的过程的膨胀迫使人们寻找一种快速工作,相应变化的&
敏捷的&
方法。
敏捷团队提倡通过团队成员之间充分有效的沟通统一大家的目标,结伴的方式完成开发技术的团队内传承,使用&
够用就好&
的轻量甚至免费的工具管理过程。
可以正常工作的代码摆在首要地位,只有必要的时候才生产必要的文档。
强调和客户面对面地交流合作,积极地响应客户需求的变化而不是遵循机械的计划。
使用较短的迭代周期,近早和持续提交有价值的软件给客户来验证并修正和用户需求的吻合程度。
提倡可以持续的稳定的开发节奏,长期&
小步快走&
的方式代替突然的&
百米冲刺&
保持设计最优,最简单的设计并且持续改进,不断调整。
下面通过一个简单的测试驱动示例,并经过重构完成设计的更改。
详细的例子见重构。
影片出租店的程序,计算每一位顾客的消费金额并打印报表。
操作者告诉程序:
顾客租用了哪些影片,租期多长,程序根据租赁时间和影片类型(普通片,儿童片和新片)。
除了计算费用,还要为常客计算点数,点数的计算会由于租片种类是否为新片而有所不同。
根据上述描述,我们画出简单的类图其中影片和租借都是简单的纯数据类。
packagechapter01;
publicclassMovie{
publicstaticfinalintCHILDRENS=2;
//影片类型
publicstaticfinalintREGULAR=1;
publicstaticfinalintNEW_RELEASE=0;
privateStringtitle;
//名称
privateintpriceCode;
//价格代码
publicMovie(Stringtitle,intpriceCode){
this.title=title;
this.priceCode=priceCode;
}
publicintgetPriceCode(){
returnpriceCode;
publicvoidsetPriceCode(intpriceCode){
publicStringgetTitle(){
returntitle;
publicclassRental{
privateMoviemovie;
privateintdayRented;
publicRental(Moviemovie,intdayRented){
this.movie=movie;
this.dayRented=dayRented;
publicintgetDayRented(){
returndayRented;
publicMoviegetMovie(){
returnmovie;
}
publicclassCustomer{
privateStringname;
privateVectorrentals=newVector();
publicCustomer(Stringname){
this.name=name;
publicStringgetName(){
returnname;
publicVectorgetRentals(){
returnrentals;
@SuppressWarnings("
unchecked"
)
publicvoidaddRental(Rentalrental){
rentals.add(rental);
publicStringstatement(){//计算费用
doubletotalAmount=0;
//总和
intfrequentCount=0;
//常客积点
Enumerationrental=rentals.elements();
Stringresult="
rentalrecordfor"
+getName()+"
\n"
;
while(rental.hasMoreElements()){
Rentaleach=(Rental)rental.nextElement();
//取得一批租借记录
doublethisAmount=0;
//根据类型,计算价格
switch(each.getMovie().getPriceCode()){
caseMovie.REGULAR:
thisAmount+=2;
if(each.getDayRented()&
gt;
2)
thisAmount+=(each.getDayRented()-2)*1.5;
break;
caseMovie.CHILDRENS:
thisAmount+=1.5;
3)
thisAmount+=(each.getDayRented()-3)*1.5;
caseMovie.NEW_RELEASE:
thisAmount+=(each.getDayRented())*3;
//添加常客积点
frequentCount++;
if(each.getMovie().getPriceCode()==Movie.NEW_RELEASE&
amp;
each.getDayRented()&
1)
//显示此笔数据
result+=
"
\t"
+each.getMovie().getTitle()+
+String.valueOf(frequentCount)+"
totalAmount+=thisAmount;
//打印结尾
result+="
amountowned"
+String.valueOf(totalAmount)+"
youearned"
frequentCount\n"
returnresult;
下面是对应的测试用例
publicclasstestCustomerStatementextendsTestCase{
MoviechildrenMovie=newMovie("
HARRYPOTTY"
Movie.CHILDRENS);
MovieregularMovie=newMovie("
Titanic"
Movie.REGULAR);
MovienewMovie=newMovie("
IceAge2"
Movie.NEW_RELEASE);
RentalchildRental=newRental(childrenMovie,3);
RentalnewRental=newRental(newMovie,5);
RentalregRental=newRental(regularMovie,5);
Customertom=newCustomer("
Tom"
);
protectedvoidsetUp()throwsException{
super.setUp();
tom.addRental(childRental);
tom.addRental(newRental);
tom.addRental(regRental);
publicvoidtestCaustomerName(){
assertEquals("
tom.getName());
publicvoidtestIteratorSize(){
assertEquals(3,tom.getRentals().size());
publicvoidtestIteratorName(){
Enumerationrentals=tom.getRentals().elements();
while(rentals.hasMoreElements()){
Rentaleach=(Rental)rentals.nextElement();
switch(each.getMovie().getPriceCode()){
System.out.println("
childrens"
each.getMovie().getTitle());
assertEquals(3,each.getDayRented());
new"
assertEquals(5,each.getDayRented());
regular"
publicvoidtestOutput(){
System.out.println(tom.statement());
}