您的位置:首页 > 其它

基于Petri网的敏捷开发软件过程模型及实践--3.敏捷开发软件过程网

2014-04-02 19:20 381 查看

3.敏捷开发软件过程网

3.1标记扩展Petri网

文献【6】提出了一种描述软件过程模型的Petri网,本文在此基础上利用软件设计模式对软件过程网进行了扩展,使敏捷开发软件过程建模更严格清晰,并且避免了高级Petri网扩展的复杂性,可以使用以往的Petri网分析工具。本文定义了敏捷开发软件过程网及其属性,首先参考文献[7]、[8]给出标记扩展Petri网模型。
定义3.1
标记PETRI网:标记PETRI网是一个四元组N=(P,T,F,L),其中,
(1)P:有限库所集合,库所元素表示系统的状态也成为条件(图3.1)。



图3.1 库所
(2)T:有限变迁集合,变迁元素也成为事件,事件的发生改变条件的状态(成真与否),引起信息在网上流动。且P



T=



,P



T



(图3.2)。



图3.2 变迁
(3)F:F



(P



T)



(T



P),为连接库所和变迁的有向弧线“



”为笛卡尔积(图3.3)。
(4)dom(F)



cod(F)=P



T,其中dom(F)={x|



y:(x,y)



F},cod(F)={y|



x:(x,y)



F}



图3.3 有向弧
(5)L:T



A为标记函数,A为变迁的字符标记。
(6)N有两个特殊的库所



(图3.4a)







,N有两个特殊的变迁







,其中



={i}



{



}



={o}



·o={



}。





图3.4 a(开始库所) b(结束库所)
(7)如果在N上增加一个变迁



连接o和i,即·



={o},



={i},L(



)=E,用



表示,称



为N的扩展网,则



为强连通的。
·t,t·,·p,p·分别代表变迁和库所的前集和后集,(N,



)表示标示PETRI网



表示N的初始标示,M为定义在P上的多重集合,同时有M(p:p



P)



N。其他定义,如变迁规则,可达变迁,有限性,安全性等与经典PETRI网类似[8]。
在敏捷软件开发过程中我们使用用例图、依赖倒置、测试驱动、策略模式、模板方法、命令模式的软件设计模式来规范迭代过程,所以我们将模型中在普通的变迁之上增加了六类标记扩展的变迁即:基于用例图的变迁



(图3.5a)、基于依赖倒置的变迁



(图3.5b)、基于测试驱动开发的变迁



(图3.5c)、基于策略模式的变迁



(图3.5d)、基于模板方法模式的变迁



(图3.5e)和基于命令模式的变迁



(图3.5f)。













图3.5 a.



b.



c.



d.



e.



f.




定义3.2 敏捷开发软件过程网:一个表示PETRI网SN=(N,



),其中N=(P,T,F,L)称为敏捷开发软件过程网,当且仅当:T=



,其中



表示基于用例图变迁集,



表示基于依赖倒置变迁集,



表示基于测试驱动开发变迁集,



表示基于基于策略模式的变迁集,



表示基于模板方法模式的变迁集,



表示基于命令模式的变迁集,



表示基于依赖倒置、策略模式、模板方法和命令模式交的变迁集,表示在该变迁集中使用到这几种模式。

3.2变迁集说明

3.2.1基于用例图的变迁说明

根据素材形成用例图的过程中应合理使用各种用例关系,产生规范的用例图。下面对用例的基本关系作出的简单说明。
(1)关联关系
关联关系是用例执行者与用例之间的关系,如果某个执行者可以对某个用例进行操作,他们之间就具有关联关系。如图“用户”有一个功能为“添加产品”,因此可在执行者“用户”和用例“添加产品”之间建立一个关联关系。



图4.1
(2)包含关系
如果多个用例都具有一部分相同的行为,可以将这部分相同的行为作为一个单独的用例抽象出来,与原来的用例形成了包含关系。如“用户”在添加、删除产品操作前需要先登录,登录是添加、删除流程的基本组成部分,因此“添加”和“删除”包含用例“登录”。包含关系能够更加清晰的描述多个用例的相同行为。



图4.2
(3)扩展关系
如果一个用例在执行过程中可能会使用到另一个用例,或者使用一个新的用例对原有用例的行为进行扩展时可以使用扩展关系。如果一个用例在执行过程中可能会使用到另一个用例,或者使用一个新用例对原有的用例的行为进行扩展时可以使用扩展关系,如果“用户”在添加商品时发现该产品的分类信息不存在,则可以增加产品分类;如果该产品的分类已经存在则无需增加该产品分类,这时“添加产品分类”可以作为“添加产品”的扩展用例。



图4.3

3.2.2基于依赖倒置原则的变迁说明

(1)层次化设计
“所有结构良好的面向对象架构都具有清晰的层次定义,每个层次通过一个定义良好的、受控的接口向外提供了一组内聚的服务。”[9]按照这个理解我们可以设计出类似图4.1所示的结构。



图4.1
如图所示高层的表示层ShoppingCart类使用了底层的业务逻辑层ValueCaculate类,而类ValueCaculate又使用了更低层的数据层ProductRepository类。这看起来貌似是符合逻辑的,但它却存在一个隐患:即表示层对于其下一直到数据层的改动都是敏感的。这种依赖还具有传递性,表示层依赖于某些依赖于数据层的层次,依次表示层传递性的依赖于数据层,这在敏捷开发过程迭代中是非常不好的。高层的模块包含了系统中的重要策略选择和业务模型,正是这些高层模块完成了需求所需要的功能,如果高层模块依赖于底层模块,那么对底层模块的改动就会直接影响到高层模块,从而迫使高层模块改动。这是不合乎逻辑的,应该是高层的实现需求的业务模型和策略选择的模块去影响底层细节实现模块的,高层模块应该优先并独立于包含细节实现的低层模块,不论怎么设计高层模块都不应该依赖于低层模块。但我们在平时的设计中还大量应用这种高层依赖低层的模式,在敏捷开发的迭代过程中需求改变时便很难及时地对高层的策略选择和业务模型进行改动,因为你在设计这些之前不得不优先考虑如何对其传递依赖的低层模块进行改动,以满足尚未实现的高层的需要,这时我们就需要一种设计模式来解除这种传递依赖关系。
(2)依赖倒置原则
图4.2展示了一种更为合适的层次化结构,每一个较高的层次都为它所需要的服务声明一个抽象接口,较低的层次实现了这些接口,每个高层类都通过该抽象接口使用下一层,这样高层就不依赖于低层,低层反而依赖于高层中声明的抽象服务接口。这不仅解除了Shopping类对于ValueCalculate类的依赖关系,也解除了对ProductRepository类的传递依赖关系。这不仅仅是依赖关系的倒置,也是接口所有权的倒置,高层类拥有抽象的接口,低层类则从这些接口中实现,通过这种倒置的接口所有权,对于业务逻辑层和数据层的所有改动都不会影响到表示层。 并且通过这种倒置的接口所有权,需求改变时便可以根据高层的策略选择和业务逻辑改变抽象的服务接口,低层模块则实现服务接口来对高层提供服务,这也符合我们由上至下的设计逻辑。我们敏捷开发过程产生的大多数类都是不稳定的,我们不想依赖于这些不稳定的类,通过对抽象的接口的依赖隔离了他们的不稳定性。根据依赖倒置原则我们创建了一个更容易迭代、更灵活的结构。



图4.2

3.2.3基于测试驱动开发的变迁

测试驱动开发(test-driven development TDD)[11],是由Kent Beck等提出的于传统的程序设计不同的方法。它将单元测试和程序设计有机地融合在一起,用单元测试来指导程序设计。
使用测试驱动开发的优势:
(1)保证了代码设计的正确性,避免了过度设计保证了简洁性。
编写单元测试不单是一种测试行为,更是一种设计行为。根据系统需求需要完成的功能设计单元测试,由测试来决定需要实现那些程序代码,即保证实现没有偏离设计的轨道,有保证了程序的简洁性。
(2)提高代码质量,实现能编译的文档。
敏捷开发由于缺乏对文档的重视一直为人所诟病,单元测试更是一种编写文档的行为。单元测试可以作为文档的一种很有价值的形式,如果想知道一个类中的函数是如何工作的,会有一个单元测试展示给你看。单元测试就像是一套使用说明,帮助其他程序员了解怎样使用代码,并且这种文档时可编译运行的,相对于普通文档有更强的说服力。
(3)使用合适的测试模式和工具,简化设计实现过程。
在测试的实现过程中使用Mock Object(模拟对象)的思想,它允许组件独立进行测试,可以通过模拟数据层进行独立的单元测试,不必实现一个真实的数据层,如图4.3所示。也就是说只要业务逻辑层实现了数据层的接口,就可以进行业务逻辑层的开发测试。



图4.3
我们选择开源的Mock moq作为模拟工具,它是使模拟更快、更简单的一个框架。它可以创建经过定制的模仿,只包含足以帮助我们进行测试的功能,避免了因为太过复杂的模仿实现而使工作无法进行。使用moq创建模仿有两个阶段,第一个阶段是创建一个新的Mock<T>(<T>代表泛型,即这里需要模仿的类型)。第二个阶段是建立该类型需要实现的动作。

3.2.4基于模板方法模式的变迁

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
谈到基于模板方法模式的变迁,就要谈到继承的概念,早在20世纪90年代初期—也就是面向对象发展的初期人们就非常看重继承这个概念,继承关系蕴含的意义是非常深远的。
使用继承我们可以基于差异编程,也就是说对于某一个满足了我们大部分需要的类,可以创建一个他的子类,并只改变其中我们不期望的部分,只是继承一个类,就可以重用该类的代码!通过继承,我们可以建立完整的软件结构分类,其中每一层都可以重用该层次以上的代码,这是一个美丽的新世界。
关键技术:抽象类(abstract)的主要目的是为它的子类定义公共接口。一个抽象类将把它的部分或全部操作的实现延迟到子类中,因此,一个抽象类不能被实例化。在抽象类中定义却没有实现的操作成为抽象操作(abstract operation)。非抽象类称为具体类(concrete class)。子类能够重定义(override)父类定义的操作,重定义使得子类能接管父类对请求的处理操作,类继承允许你只需要简单的扩展其他类就可以定义新类,从而可以很容易地定义具有相近功能的对象族。
下面我们来看一个例子:



如图我们建立了一个AddEmployeeTransaction的抽象类,在这个类中我们抽象除了员工类共同的属性:名称、ID号、地址,针对钟点工员工、薪水员工和提成员工三个具体类,他们有自己不同的属性比如钟点工员工有每小时报酬率,薪水员工有薪水,提成员工有提成率和薪水两个属性。

public abstract class AddEmployeeTransaction:Transaction

{

private readonly int empid;

private readonly string name;

private readonly string address;

public AddEmployeeTransaction(int empid, string name, string address)

{

this.empid = empid;

this.name = name;

this.address = address;

}

protected abstract PaymentClassification MakeClassification();

protected abstract PaymentSchedule MakeSchedule();

public void Execute()

{

PaymentClassification pc = MakeClassification();

PaymentSchedule ps = MakeSchedule();

PaymentMethod pm = new HoldMethod();

Employee e = new Employee(empid, name, address);

e.Classification = pc;

e.Schedule = ps;

e.Method = pm;

PayrollDatabase.AddEmployee(empid, e);

}

}
像大多数美丽新世界一样,继承最终也被证明是不是万能的,到1995年,人们清楚地认识到继承非常容易被过度使用,而且过度使用的代价是非常高的,继承是一种非常强的关系,派生类不可避免的要和他们的基类绑定再一起。所以我们减少了对继承的使用,常常使用组合或者委托来代替它,所以我们选择了策略模式。

3.2.5基于策略模式的变迁

定义一系列的算法,把它们一个个封装起来,并且使它们可互相替换。本模式可以使得算法独立于使用它的具体类。
策略模式使用了一种非常不同的方法来倒置通用算法和具体实现之间的依赖关系,我们将通用的算法放入一个接口中,我们从接口中定义一系列的算法,把他们一个个封装起来,并且使它们可相互替换,本模式使得算法可独立于使用它的类对象而变化。一个以这种方法封装的算法称为一个策略
许多相关类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
需要使用一个算法的不同变体,例如你可能会定义一些反映不同的空间时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式。策略模式消除了一些条件语句提供了用条件语句选择所需的行为以外的另一种选择,当不同的行为堆砌在一个类中,很难避免使用条件语句来选择合适的行为,将行为封装在一个个独立的strategy类中消除了这些条件语句。一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的策略类中以替代这些条件语句。
关键技术:委托(delegation)是一种组合方法,它使组合具有与继承同样的复用能力,在委托方式下,有两个对象参与处理一个请求,接收请求的对象将操作委托给它的代理者(delegate)。这类似于子类将请求交给它的父类处理,接收请求的对象将自己传给被委托者(代理人),使被委托的操作可以引用接受请求的对象。委托的主要优点在于它便于运行时刻组合对象操作以及改变这些操作的组合方式。一个替代继承的方法 易于切换、易于理解、易于扩展。
下面的例子中演示了一个表示统计一些产品总价功能的接口和接口委托的方法的具体实践。



通过引入IValueCalculate接口,先通过继承接口实现了FValueCalculator类,保证了ShoppingCart与FValueCalculator之间没有直接的依赖性,在运行时刻才将FValueCalculator注入到ShoppingCart类中

public class Product

{

public int ProductID { get; set; }

public string Name { get; set; }

public string Description { get; set; }

public decimal Price { get; set; }

public string Category { get; set; }

}

public interface IValueCalculator

{

decimal ValueProducts(params Product[] products);

}

public class FValueCalculator : IValueCalculator

{

public decimal ValueProducts(params Product[] products)

{

decimal sum=0;

foreach (Product pd in products)

{

sum += pd.Price;

}

return sum;

}

}

public class ShoppingCart

{

private IValueCalculator calculator;

public ShoppingCart(IValueCalculator calcParam)

{

calculator = calcParam;

}

public decimal CalculateStockValue()

{

Product[] products ={

new Product(){Name="足球鞋",Price=275M},

new Product(){Name="T恤",Price=75M},

new Product(){Name="足球",Price=85M},

new Product(){Name="羽毛球",Price=25M}

};

decimal totalValue = calculator.ValueProducts(products);

return totalValue;

}

}
为了验证我们的算法正确性我们针对FValueCalculator类编写了测试函数,验证该类的正确性:

[TestMethod]

public void TestFValueCalculator()

{

FValueCalculator fv = new FValueCalculator();

ShoppingCart sc = new ShoppingCart(fv);//实例化ShoppingCart类时将计算的方法委托给FValueCalculator类。

decimal sum=sc.CalculateStockValue();

Assert.AreEqual((decimal)460.00,sum );

}

运行测试结果如下图顺利通过



这时使用这个统计系统的超市搞了一个打折活动要求在不影响ShoppingCart决策类的情况下使用一个新的IValueCalculate接口实现算法来对商品进行打折处理,我们新建下面LinqValueCalculator类:

public class LinqValueCalculator : IValueCalculator

{

private decimal discount;

public LinqValueCalculator(decimal dis)

{

this.discount = dis;

}

public decimal ValueProducts(params Product[] products)

{

return products.Sum(p => p.Price)*discount;

}

}

为LinqCalculator编写单元测试验证:

[TestMethod]

public void TestLinqValueCalculator()

{

LinqValueCalculator lv = new LinqValueCalculator((decimal)0.8);

ShoppingCart sc = new ShoppingCart(lv);// 实例化ShoppingCart类时将计算的方法委托给LinqValueCalculator类。

decimal sum = sc.CalculateStockValue();

Assert.AreEqual((decimal)(460 * 0.8), sum);

}
可以看出在系统运行时刻通过策略模式通过接口将具体算法委托给实现类,使算法独立于使用它的具体类。

3.2.6基于命令模式的变迁

将一个请求封装为一个对象,从而使你可用不同的请求对类进行参数实例化,对请求排队或记录请求日志,以及支持可取消的操作。
当用户决定增加一个新雇员时,该用户必须详细指明成功创建一条雇员记录所需要的所有信息。在使用这些信息前,系统需要对这些信息进行验证语法和语义上的正确性。Command模式可以协助完成这项工作。Command对象储存了还未验证的数据,实现了实施验证的方法,并且实现了最后执行事务操作的方法。这实现了实体上解耦和时间上解耦,这给我们带来的好处在于很好地解除了从用户获取数据的代码、验证并操作数据的代码以及业务对象本身之间的耦合关系。



例如,在图13.5中,AddEmployeeTransaction类包含有和Employee类相同的数据字段,同时它还持有一个指向PaymentClassification对象的指针。这些数据字段和对象是根据用户指示系统增加一个雇员时指定的信息创建出来的。

public interface Transaction

{

void Execute();

}

public abstract class AddEmployeeTransaction:Transaction

{

private readonly int empid;

private readonly string name;

private readonly string address;

public AddEmployeeTransaction(int empid, string name, string address)

{

this.empid = empid;

this.name = name;

this.address = address;

}

protected abstract PaymentClassification MakeClassification();

protected abstract PaymentSchedule MakeSchedule();

public void Execute()

{

PaymentClassification pc = MakeClassification();

PaymentSchedule ps = MakeSchedule();

PaymentMethod pm = new HoldMethod();

Employee e = new Employee(empid, name, address);

e.Classification = pc;

e.Schedule = ps;

e.Method = pm;

PayrollDatabase.AddEmployee(empid, e);

}

}

public class AddSalariedEmployee:AddEmployeeTransaction

{

private readonly double salary;

public AddSalariedEmployee(int id, string name, string address, double salary):base(id,name,address)

{

this.salary = salary;

}

protected override PaymentClassification MakeClassification()

{

return new SalariedClassification(salary);

}

protected override PaymentSchedule MakeSchedule()

{

return new MonthlySchedule();

}

}

3.3基于Petri网的敏捷开发过程模型

本文在以上Petri网相关理论的基础上结合敏捷软件过程的相关知识提出了一种基于Petri网的敏捷软件开发过程模型,如图3.6所示,并在第四节中对各个变迁进行了实践验证。



图3.6敏捷软件过程网
敏捷软件过程网是描述实际应用中的过程定义和过程演化的,它拥有PETRI网的有限性,安全性等属性。同时,实际过程中应保证过程设计的合理性。本文定义软件网库所中的托肯用来表示条件,只具有二值得布尔量,所以软件过程网是安全的,其次,该软件过程是可以适当结束的。网络系统中不包含死变迁。根据文献[7]的定义2.4、2.5可以证明敏捷软件过程网是完全合理的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: