你在用什么思想编码:事务脚本 OR 面向对象?

最近在公司内部做技术交流的时候,说起技能提升的问题,调研大家想要培训什么,结果大出我意料,很多人想要培训:面向对象编码。于是我抛出一个问题:你觉得我们现在的代码是面向对象的吗?有人回答:是,有人回答否。我对这个问题的回答是:语法上,是了,但是架构上或者思想上,不是。我们现在的大部分代码,如果要死扣一个名词的话,那就是:事务脚本。

1:最开始的事务脚本

在 Martin Fowler 的书中,存在一个典型的 应用场景,即“收入确认”(Revenue Recognition)。该“收入确认”的描述:

一家软件公司有3种产品,其售价策略分别为,第一种:交全款才能卖给你;第二种,付三分之一,就给你,60天后,再给1/3,90天后给完全部;第三种,付1/3,就给你,30天后给1/3,60天后给完。

但是,关于这个描述,我打算多啰嗦几句,而且个人觉的这个啰嗦非常之紧要,因为它影响到了我们的设计。以下是啰嗦的部分:

“收入确认”,在概念上,确实是产品的入账策略,实际上,Martin
的代码,也是这么去实现的,不同的产品有不同的入账策略。不过,数据库实现,RevenueRecognition
这个表记录的是“产品的某个合同根据产品类型所计算出来的:应该执行的入账日及金额”,即策略是跟着合同走的,而不是跟着产品走的。这很有意思,如果你精读此部分,这种矛盾就会一直纠结在你心头。同时,我们又不得不时刻提醒自己存在的这个需求。

现在,关于这个场景,如果我们理解了 产品 合同 RevenueRecognition 之间的关系,我们就很能理解了数据库是被设计成这样的:

其概念模型为如下:

好了,现在我们来看看什么是事务脚本,对的,就用代码来说话。在原文中, Martin
举了两个例子,但是精读之后,我打算将其颠个倒,把原文中的示例2讲在前头。因为示例2,很好的表达了什么才是作者或者译者眼中的“收入确认”,以及我眼中的“收入策略”。

第一个要实现的功能,即第一个事务脚本描述如下:

根据合同 ID,找到该合同,并根据合同类型得到应该在哪天收入多少钱,并插入数据库。

从该描述中,我们知道,这个脚本最应该发生在签订合同时。因为合同一旦签订,就应该记录什么时候应该收到客户端多少钱。代码如下:

class RecognitionService
{
    dynamic dal = null;

   
    // 计算哪天该入账多少并插入

    public void CalculateRevenueRecognitions(long
contactNumber)
    {

        DataSet contractDs =
dal.FindContract(contactNumber);

        double totalRevenue =
(double)contractDs.Tables[0].Rows[0]["ID"];

        DateTime dateSigned =
(DateTime)contractDs.Tables[0].Rows[0]["DateSigned"];

        string type =
(string)contractDs.Tables[0].Rows[0]["Type"];

        if(type ==
"S")    // 电子表格类
       
{
            //
the sql "INSERT INTO REVENUECONGNITIONS (CONTRACT,AMOUNT,RECOGNIZEDON) VALUES
(?,?,?)"

           
dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned);

           
dal.InsertRecognition(contactNumber, totalRevenue / 3,
dateSigned.AddDays(60));

           
dal.InsertRecognition(contactNumber, totalRevenue / 3,
dateSigned.AddDays(90));
        }else
if(type == "W")    // 文字处理

        {   

           
dal.InsertRecognition(contactNumber, totalRevenue, dateSigned);

        }else if(type ==
"D")    // 数据库
       
{   

           
dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned);

           
dal.InsertRecognition(contactNumber, totalRevenue / 3,
dateSigned.AddDays(30));

           
dal.InsertRecognition(contactNumber, totalRevenue / 3,
dateSigned.AddDays(60));
        }

    }   
}

第二个需求是:计算某合同在某个日期前的应该有的入账。

class RecognitionService
{
    dynamic dal = null;

        
    //
得到哪天前入账了多少
    public double RecognizedRevenue(long
contractNumber, DateTime asOf)
    {

        // the sql "SELECT AMOUNT FROM
REVENUECONGNITIONS WHERE CONTRACT=? AND RECOGNIZEDON <=?";

        DataSet ds =
dal.FindRecognitionsFor(contractNumber, asOf);

        double r = 0.0;

        foreach(DataRow dr in
ds.Tables[0].Rows)
        {

            r +=
(double)dr["AMOUNT"];
        }

       

        return r;

    }
}

从上面的代码,我们可以看出什么才是 事务脚本:

1:采用面向过程的方式组织业务逻辑;
2:没有或尽量少的实体类;
3:一个方法一件事情,故有大量业务类或方法;

4:能与行数据入口表数据入口很好协作;

2:事务脚本之变体

也许上面的代码多多少少让大家嗤之以鼻,认为现在很少会这样来写代码了。那么,我们来看看下面这段代码:

class RecognitionBll
{
    dynamic dal = null;

   
    // 计算哪天该入账多少并插入

    public void CalculateRevenueRecognitions(long
contactNumber)
    {

        List<Contact> contracts =
dal.FindContract(contactNumber);

        double totalRevenue =
(double)contracts[0].Id;
       
DateTime dateSigned = (DateTime)contracts[0].DateSigned;

        string type =
(string)dal.FindContractType(contactNumber);

        // 上面这行代码你还可能会写成

        // string type =
(string)dal.contracts[0].ProductType;

        // 或者

        // string type =
(string)dal.contracts[0].Product.Type;

        if(type ==
"S")    // 电子表格类
       
{
            //
the sql "INSERT INTO REVENUECONGNITIONS (CONTRACT,AMOUNT,RECOGNIZEDON) VALUES
(?,?,?)"

           
dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned);

           
dal.InsertRecognition(contactNumber, totalRevenue / 3,
dateSigned.AddDays(60));

           
dal.InsertRecognition(contactNumber, totalRevenue / 3,
dateSigned.AddDays(90));
        }else
if(type == "W")    // 文字处理

        {   

           
dal.InsertRecognition(contactNumber, totalRevenue, dateSigned);

        }else if(type ==
"D")    // 数据库
       
{   

           
dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned);

           
dal.InsertRecognition(contactNumber, totalRevenue / 3,
dateSigned.AddDays(30));

           
dal.InsertRecognition(contactNumber, totalRevenue / 3,
dateSigned.AddDays(60));
        }

    }
   
    //
得到哪天前入账了多少
    public double RecognizedRevenue(long
contractNumber, DateTime asOf)
    {

        // the sql "SELECT AMOUNT FROM
REVENUECONGNITIONS WHERE CONTRACT=? AND RECOGNIZEDON <=?";

        List<RevenueRecognition>
revenueRecognitions = dal.FindRecognitionsFor(contractNumber, asOf);

        double r = 0.0;

        foreach(RevenueRecognition rr
in revenueRecognitions)
        {

            r +=
rr.Amount;
        }

       

        return r;

    }
}

public class Product
{
    public long Id;

    public string Name;
    public
string Type;
}

public class Contact
{
    public long Id;

    public long ProductId;
    public
string ProductType;
    public Product Product;

    public double Revenue;
    public
DateTime DateSigned;
}

public class RevenueRecognition
{
    public long
ContactId;
    public double Amount;
   
public double RevenuedOn;
}

在这个事务脚本的变种中,我们看到了所有人写过代码的影子:

1:有了实体类了,所以看上去貌似是面向对象编码了;

2:看到了 “三层架构” 了,即:实体层、DAL层、业务逻辑层等;

但是,它仍旧是 事务脚本 的!唯一不同的是,它光鲜的把 DataSet 变成了 List<Model>
了!

3:什么是面向对象的?

那么,什么是面向对象的编码,面向对象的一个很重要的点就是:“把事情交给最适合的类去做”,并且“你得在一个个业务类之间跳转,才能找出他们如何交互”。这确实是个不那么简单的话题,而本文的主旨也仅在于指出,如果我们的代码中还没有
工作单元 映射 缓存 延迟加载 等等概念,即便我们编码再熟练,也仅仅是在熟练的 面向过程编码。

时间: 2024-11-06 03:34:17

你在用什么思想编码:事务脚本 OR 面向对象?的相关文章

事务脚本-领域模型

Martin Fowler定义是: 事务脚本,将所有逻辑组织在一个单一过程,进行数据库直接调用,每个业务请求都有自己的事务脚本,并且是一个类的公开方法. 领域模型,是一系列相互关联的对象,每个对象代表一定意义的独立体,既可以一起以一种大规模方式协作:也可以小到以单线方式运行. 事务脚本总体来说:就像直奔主题,平铺直叙,就功能谈功能,直接没有回旋余地:领域模型给人感觉好像肚子里就那么点货而领域模型则象是文人骚客,上了一个档次,会使用美妙表达方式,有余地. 比如唐诗:清明时节雨纷纷,路上行人欲断魂:

.NET应用架构设计—表模块模式与事务脚本模式的代码编写

阅读目录: 1.背景介绍 2.简单介绍表模块模式.事务脚本模式 3.正确的编写表模块模式.事务脚本模式的代码 4.总结 1.背景介绍 要想正确的设计系统架构就必须能正确的搞懂每个架构模式的用意,而不是胡子眉毛一把抓.现在有一个现象是什么呢,项目的结构从表面上看是很不错,层分的很合理,其实对业务系统来说也就那么几种层设计方法,但是现在很多项目的逻辑架构的设计不是理想,有很多概念大家并不是很了解,当然也许每个人对技术的追求不同罢了.不管你追求不追求,事实我们还是要去往正确的方向努力才对的. 很多人包

PHP面向对象之事务脚本模式

/* 事务脚本模式: 类似于thinkphp中的model层,或者说就是操作数据库的类. 个人觉得实践中使用起来还是挺简单方便的,就是SQL语句写死了的话,灵活性就不够. 示例代码如下: */ namespace woo\process; abstract class Base{ static $DB; //pdo对象 static $stmts = array(); //sql语句句柄 function __construct (){ $dsn = \woo\base\ApplicationR

事务脚本

本文摘抄自<.NET企业级应用架构设计> 业务逻辑层的模式的发展历史 历史上,事务脚本是第一个广泛应用的业务逻辑模式. 后来出现了基于表数据的表模块模式,仍然属于过程式模式,但是加入了一些面向对象思维. 在面向对象开发兴起之后,出现了基于对象的业务逻辑模式,最简单的对象模型就像是数据库表的数据模型,这里的对象就是数据库中的记录,并加了一些额外的方法,这种模式通常叫做活动记录模式. 随着业务逻辑的复杂性越大,软件的抽象程度越高,这时就应该从领域着眼,创建一个领域驱动的对象模型,这种模式通常叫做领

常见编码解码脚本

在平时我们会遇到各种各样的编码,在这里,我总结了一些常见的编码,并不是很全 尝试着做了个编码解码的汇总,并且写了个脚本出来,由于python功底不是很强,所以可能会有不到之处,还望各位多多指正 附上脚本: 1 #-*-coding:utf-8-*- 2 #author:hell0_w 3 #本人博客:http://hell0w.cnblogs.com/ 4 5 import base64 6 import bubblepy 7 import urllib 8 import quopri 9 im

监控MySQL长事务脚本

监控长事务的脚本 #!/bin/bashmysql -N -uroot -p'密码' -e "select now(),(UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(a.trx_started)) diff_sec,b.id,b.user,b.host,b.db,d.SQL_TEXT from information_schema.innodb_trx a inner join information_schema.PROCESSLIST b on a.TRX_

Oracle 查看 使用 UNDO 段的事务脚本

查看oracle undo segment段的信息: SELECT T1.USN, T2.NAME, T1.STATUS, T1.LATCH, T1.EXTENTS, T1.WRAPS, T1.EXTENDS FROM V$ROLLSTAT T1, V$ROLLNAME T2 WHERE T1.USN = T2.USN; 检查事务使用undo segment的情况: SELECT s.username, s.sid, pr.PID, s.OSUSER, s.MACHINE, s.PROGRAM,

两大编程思想(面向过程,面向对象)

1. 面向过程编程(POP) 面向过程:分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了. 例如:把大象装进冰箱(面向过程,就是按照我们分析好的步骤,按照步骤解决问题) 1. 打开冰箱门, 2. 把大象装进去, 3. 关上冰箱门 2. 面向对象编程(OOP) 面向对象:是把事务分解成为一个个对象,然后由对象之间分工与合作. 例如:把大象装进冰箱(面向对象是以对象来划分问题,而不是步骤) 先找出对象,并写出这些对象的功能: 1. 大象 (对象)

编码心得

你在用什么思想编码:事务脚本 OR 面向对象? 开始的时候需求是 有时候sql语句查出一个属性,后面还要封装处理.然后推送给搜索. 后来的需求改成去掉这个属性 代码里面封装处理的地方我去掉了,但是之前sql语句中select的地方没有去掉,结果就还是推送了. 去掉这个属性的时候为什么会漏掉? 编码心得