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

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:什么是面向对象的?

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

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

  1. ssas脚本组织程序_微服务架构:从事务脚本到领域模型

    图1 Order Service具有六边形架构.它由业务逻辑和一个或多个与其他服务和外部应用程序连接的适配器组成 图1显示了一个典型的服务架构.业务逻辑是六边形架构的核心.业务逻辑的周围是入站和出站适 ...

  2. php事务 面向对象,关于PHP面向对象的事务脚本模式

    下面为大家带来一篇PHP面向对象之事务脚本模式(详解).内容挺不错的,现在就分享给大家,也给大家做个参考. 如下所示: /* 事务脚本模式: 类似于thinkphp中的model层,或者说就是操作数据 ...

  3. php事务 面向对象,PHP面向对象之事务脚本模式(详解)

    如下所示: /* 事务脚本模式: 类似于thinkphp中的model层,或者说就是操作数据库的类. 个人觉得实践中使用起来还是挺简单方便的,就是SQL语句写死了的话,灵活性就不够. 示例代码如下: ...

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

    阅读目录: 1.背景介绍 2.简单介绍表模块模式.事务脚本模式 3.正确的编写表模块模式.事务脚本模式的代码 4.总结 1.背景介绍 要想正确的设计系统架构就必须能正确的搞懂每个架构模式的用意,而不是 ...

  5. php事务讲解,PHP面向对象之事务脚本模式(详解)

    /* 事务脚本模式: 类似于thinkphp中的model层,或者说就是操作数据库的类. 个人觉得实践中使用起来还是挺简单方便的,就是SQL语句写死了的话,灵活性就不够. 示例代码如下: */ nam ...

  6. 中国省市区县行政编码sql脚本

    中国省市区县行政编码sql脚本 1.初始化数据库 截至2020年2月,中国省市区县行政编码sql脚本: 链接: https://pan.baidu.com/s/1GLQy7fpqPOsqMLuIn12 ...

  7. 【Java设计模式 面向对象设计思想】一 再谈面向对象和封装、抽象、继承、多态四大特性

    回看最初的目标:[Java设计模式 学习目标及大纲]高质量代码的标准及实现路径在这篇Blog里我们明确了什么是高质量的代码:易维护.易读.易扩展.灵活.简洁.可复用.可测试,也知道高质量代码的达成路径 ...

  8. utf8编码转换脚本

    差不多去年的这个时候吧,由于项目组的编码从GBK转向UTF-8,让我来负责转换所有的源文件,当时frank给了我一个转换的脚本,可以过滤文件类型来转换,由于是frank自己使用的工具,所以也没怎么注意 ...

  9. 服务器页是指包含什么脚本程序的网页,XSS攻击的本质就是被攻击者访问的页面返回页面中,包含了未经编码的脚本代码,如等信息。而浏览 - 众答网问答...

    相关题目与解析 XSS攻击产生的原理是攻击者通过向Web页面里插入(),从而达到特殊目的. SQL注入和XSS跨站攻击,是WEB安全中常见攻击类型,稳居OWASPTOP10.针对SQL注入和XSS跨站 ...

最新文章

  1. R语言可视化散点图(scatter plot)图中的标签和数据点互相堆叠丑死了,ggrepel包来帮忙:文本标签(label)相互排斥,远离数据点,远离绘图区域的边缘。
  2. 第一天开通博客,记录自己在编程道路上的点点滴滴
  3. 前端JS——滑动滑块验证登录(源码及效果)
  4. C# AutoResetEvent
  5. 未来5年,中国会有多少企业营收能达到1000亿美元以上?
  6. vuebaidumap 删除覆盖物_VUE BAIDU MAP覆盖物 - 自定义覆盖物手记
  7. api demo 京东商品详情_jd-demo
  8. HDU-3072-IntelligenceSystem(tarjan,贪心)
  9. 《了不起的NodeJS》书籍笔记一
  10. gmail api 发送邮件_Node定时自动发邮件功能
  11. Lena图像分解成小块与从小块合成
  12. 深度学习实战 第7章循环神经网络笔记
  13. excel当前时间增加几天或者几小时
  14. 龙芯3A3000 PCI 硬件问题
  15. 十三、商城 - 商城架构-分布式Dubbo(1)
  16. Zxing图片识别 从相册选二维码图片解析总结
  17. 如何批量调整论文中公式大小--mathtype
  18. 因果推断 | 因果关系推断-系列电子书资源
  19. 在 Kubernetes 上运行 GitHub Actions Self-hosted Runner
  20. 记录解决问题--Linux服务器连接超时

热门文章

  1. oracle往据,指定日期查询数Oracle据库
  2. dll oem证书导入工具_恶意代码分析之反射型DLL注入
  3. mysql varchar int_MySQL中int、char、varchar的性能浅谈
  4. etsi计算机应用场景,ETSI发布最新版DVB数据广播规范,包括五大应用场景
  5. python之sys模块详解_(转)python之os,sys模块详解
  6. 大数据技术:分布式系统和分布式事务
  7. apache配置文件httpd.conf----小白福利
  8. Ansible之使用角色一键部署httpd并检查部署结果
  9. 各种排序实现以及稳定性分析
  10. 2018-北航-面向对象-前三次OO作业分析与小结