您的位置:首页 > 编程语言

面向对象的企业开发(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;

以此类推。因此可知收入确认的规则很多。

我们的收入确认模型的类关系文字描述如下:

每个软件产品对应多个合同;

每个合同对应多个收入确认方法,这些确认方法指出何时将收入的不同部分确认。

我们使用两个事务脚本

一个用来计算合同的总收入确认

一个用来查询某合同在指定日期前已经确认的收入额

数据库中有三个,分别记录着:

产品

合同

收入确认

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从业者,使用事务脚本模式代码,写出的大量冗余难维护的过程式代码,最终导致周六加班,每晚熬夜,导致脱发的原因。

当然了,对于某些逻辑简单且不会增改需求的场景,事务脚本模式还是应该使用的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息