面向对象的企业开发(2)事务脚本设计模式代码讲解
2017-11-13 00:00
351 查看
注意:以下均为伪代码
前言: 上一篇企业开发设计模式(1)以文字来介绍了事务脚本设计模式。
下面我们来看一下代码讲解:
我们举得例子是,商业系统中常见的收入确认(Revenue Recognition)问题。涉及到何时将所收到的钱入账。比如,我在京东买东西。我可以直接付全款,在收到货物后选择确认收货,这笔钱就会立刻入卖家的账单;也可以打白条,分期24个月,这样,卖家会每个月收到24分之一的货款。
我们的例子是我司卖各种软件:Word、Excel和SQLServerDB。
Word 产品当天立即入账
Excel 当天入账1/3,60天1/3,90天1/3;
DB 当天入账1/3,30天1/3,60天1/3;
以此类推。因此可知收入确认的规则很多。
我们的收入确认模型的类关系文字描述如下:
每个软件产品对应多个合同;
每个合同对应多个收入确认方法,这些确认方法指出何时将收入的不同部分确认。
我们使用两个事务脚本:
一个用来计算合同的总收入确认;
一个用来查询某合同在指定日期前已经确认的收入额。
数据库中有三个表,分别记录着:
产品
合同
收入确认
第一个脚本用来计算在指定日期前的确认额。分两步进行:
首先在收入确认表中选择相应的行;
然后相加计算总数。
大多数使用事务脚本的代码都有直接用原生SQL对数据库进行操作。这里我们使用一个简单的表数据入口来封装SQL查询。由于本例逻辑很简单,因此只使用一个数据源入口(GateWay),而不是为每个表设立一个入口。在入口中定义了一个相应的find方法。
然后在从入口返回结果集,并使用脚本来计算总额:
第二个脚本用来进行合同的收入确认计算[/b]:
表数据入口通过原生SQL实现,以下为合同的查找器方法:
以下是对数据库插入操作的封装:
以下是用脚本计算一个的合同收入确认:
在Java系统中,收入确认服务通常是一个POJO类。
当把这个事务脚本的例子与领域模型模式(下一章就要写)中的例子比较时,第一反应就是前者要简单许多,开发速度更快。然而,当系统复杂度增加时,例如收入确认规则涉及许多方面,有很多变体(例如在双十一打5折,国庆节这几天打9折等等)。总之,当事务一旦变得复杂,就很难用事务脚本保持一种低冗余、易维护的设计。而通过,领域模型,引入面向对象设计,便可以使用通用设计模式里的策略模式(strategy pattern)来更换各种增改的收入确定规则。这就是为何 java OO 鼓吹者 宁愿在这种看似简单的情况下依然使用 领域模型模式 的原因。这也是为何很多py从业者,使用事务脚本模式代码,写出的大量冗余难维护的过程式代码,最终导致周六加班,每晚熬夜,导致脱发的原因。
当然了,对于某些逻辑简单且不会增改需求的场景,事务脚本模式还是应该使用的。
前言: 上一篇企业开发设计模式(1)以文字来介绍了事务脚本设计模式。
下面我们来看一下代码讲解:
我们举得例子是,商业系统中常见的收入确认(Revenue Recognition)问题。涉及到何时将所收到的钱入账。比如,我在京东买东西。我可以直接付全款,在收到货物后选择确认收货,这笔钱就会立刻入卖家的账单;也可以打白条,分期24个月,这样,卖家会每个月收到24分之一的货款。
我们的例子是我司卖各种软件:Word、Excel和SQLServerDB。
Word 产品当天立即入账
Excel 当天入账1/3,60天1/3,90天1/3;
DB 当天入账1/3,30天1/3,60天1/3;
以此类推。因此可知收入确认的规则很多。
我们的收入确认模型的类关系文字描述如下:
每个软件产品对应多个合同;
每个合同对应多个收入确认方法,这些确认方法指出何时将收入的不同部分确认。
我们使用两个事务脚本:
一个用来计算合同的总收入确认;
一个用来查询某合同在指定日期前已经确认的收入额。
数据库中有三个表,分别记录着:
产品
合同
收入确认
CREATE TABLE products (ID int primary key, name varchar, type varchar); CREATE TABLE contracts(ID int primary key, product int, revenue decimal, dateSigned date); CREATE TABLE revenueRecognitions(contract int ,amount decimal, recognizedOn date, PRIMARY KEY(contract, recogniedOn);
第一个脚本用来计算在指定日期前的确认额。分两步进行:
首先在收入确认表中选择相应的行;
然后相加计算总数。
大多数使用事务脚本的代码都有直接用原生SQL对数据库进行操作。这里我们使用一个简单的表数据入口来封装SQL查询。由于本例逻辑很简单,因此只使用一个数据源入口(GateWay),而不是为每个表设立一个入口。在入口中定义了一个相应的find方法。
class Gateway { private Connection db; private static final String findRecognitionsStatement = "SELECT amount FROM revenueRecognitions WHERE contract=? AND recognizedOn<=?"; public ResultSet findRecognitionsFor(long contractID,MFDate asof) throws SQLException{ PreparedStatement stmt = db.prepareStatement(findRecognitionsStatement); stmt.setLong(1, contractID); stmt.setLong(2, asof.toSqlDate()); ResultSet result = stmt.executeQuery(); return result; } }
然后在从入口返回结果集,并使用脚本来计算总额:
class RecognitionService { public Money recognizedRevenue(long contractNumber, MfDate asOf){ Money result = Money.dollars(0); ResultSet rs = db.findRecognitionFor(contractNumber, MfDate asOf); while (rs.next()){ result = result.add(Money.dollars(rs.getBugDecimal("amount"))); } return result; } }
第二个脚本用来进行合同的收入确认计算[/b]:
表数据入口通过原生SQL实现,以下为合同的查找器方法:
class Gateway { private Connection db; private static final String findContractStatement = "SELECT * FROM contract c, product p WHERE ID=? AND c.products=p.ID"; public ResultSet findRecognitionsFor(long contractID,MFDate asof) throws SQLException{ PreparedStatement stmt = db.prepareStatement(findContractStatement); stmt.setLong(1, contractID); ResultSet result = stmt.executeQuery(); return result; } }
以下是对数据库插入操作的封装:
class Gateway { private Connection db; private static final String insertRecognitionStatement = "INSERT INSERT revenueRecognitions VALUES (?,?,?)"; public ResultSet findRecognitionsFor(long contractID,Money amount,MFDate asof) { PreparedStatement stmt = db.prepareStatement(insertRecognitionStatement); stmt.setLong(1, contractID); stmt.setBigDecimal(2, amount.amount()); stmt.setDate(3, asof.toSqlDate()); ResultSet result = stmt.executeQuery(); stmt.executeUpdate(); return result; } }
以下是用脚本计算一个的合同收入确认:
class RecognitionService { public void calculateRevenueRecognitions(){ ResultSet contracts = db.findContract(contractNumber); contract.next(); Money totalRevenue = Money.dollars(contracts.getDate("dateSigned")); String type = contracts.getString("type"); if (type.equals("Word")){ Money[] allocation = totalRevenue.allocate(3); db.insertRecognition(contractNumber, allocation[0],recognitionDate); db.insertRecognition(contractNumber, allocation[1],recognitionDate.addDays(60)); db.insertRecognition(contractNumber, allocation[2],recognitionDate.addDays(90)); } else if (type.equals("Excel")){ ... } } else if (type.equals("Excel")){ ... } } }
在Java系统中,收入确认服务通常是一个POJO类。
当把这个事务脚本的例子与领域模型模式(下一章就要写)中的例子比较时,第一反应就是前者要简单许多,开发速度更快。然而,当系统复杂度增加时,例如收入确认规则涉及许多方面,有很多变体(例如在双十一打5折,国庆节这几天打9折等等)。总之,当事务一旦变得复杂,就很难用事务脚本保持一种低冗余、易维护的设计。而通过,领域模型,引入面向对象设计,便可以使用通用设计模式里的策略模式(strategy pattern)来更换各种增改的收入确定规则。这就是为何 java OO 鼓吹者 宁愿在这种看似简单的情况下依然使用 领域模型模式 的原因。这也是为何很多py从业者,使用事务脚本模式代码,写出的大量冗余难维护的过程式代码,最终导致周六加班,每晚熬夜,导致脱发的原因。
当然了,对于某些逻辑简单且不会增改需求的场景,事务脚本模式还是应该使用的。
相关文章推荐
- 面向对象的企业开发(1)事务脚本模式 Traction Script Pattern
- 【深入PHP 面向对象】读书笔记(二十二) - 企业模式(七) - 业务逻辑层与事务脚本
- PHP面向对象之事务脚本模式(详解)
- Java开发中的23种设计模式详解及代码和图解
- 实例讲解iOS应用的设计模式开发中的Visitor访问者模式
- Java开发中的23种设计模式详解及代码和图解
- 黄聪:如何判断VS开发C#是否为设计模式,以免编译之前操作窗体设计器代码自动运行
- 设计模式讲解与代码实践(十八)——中介者
- .NET应用架构设计―表模块模式与事务脚本模式的代码编写
- 设计模式讲解与代码实践(零)——序
- 设计模式之装饰模式(iOS开发,代码用Objective-C展示)
- 设计模式讲解与代码实践(四)——原型
- 设计模式之策略模式(iOS开发,代码用Objective-C展示)
- Strategy 设计模式 策略模式 超靠谱原代码讲解
- .NET应用架构设计—表模块模式与事务脚本模式的代码编写
- 设计模式讲解与代码实践(十六)——解释器
- 设计模式讲解与代码实践(十九)——备忘录
- 实例讲解设计模式中的命令模式在iOS App开发中的运用
- 关于JAVA中状态设计模式的讲解示例代码
- Strategy 设计模式 策略模式 超靠谱原代码讲解