浅谈“数据>函数>宏”

现在的程序设计哲学中,“数据>函数>宏”是一种流行趋势,即数据优于函数,函数优于宏。宏排在末位,不足为奇。对于宏的使用,有一种指导方法是:不到不得已的时候,不要使用宏。因此,我们在此只讨论“数据>函数”。

一、“数据>函数”的几个例子

1. REST风格服务架构

近两年,REST流行,人们纷纷抛弃RPC,转向REST。众所周知,REST风格服务的抽象工具是资源,即数据;而RPC服务的抽象工具是过程,即函数。这从这两种服务提供的API就能够看出:前者API主要以uri 定义资源,而后者的则是以 函数名+参数列表 来调用过程。从RPC到REST,是宏观服务方向看“数据>函数”的例证。

2. 消息队列

越来越多的企业,使用消息队列服务来实现各类服务间的解耦,而不再是服务与服务之间的直接过程调用。而消息队列传递的“消息”就是“数据”——这是宏观服务之间的“数据>函数”。在新版的jdk9中,提供了消息发布订阅的框架——这是程序代码模块中的“数据>函数”。

二、“数据>函数”的优点

1. 解耦

从耦合性来说,两个模块或服务之间通过数据传递带来的耦合性要小于通过函数调用所带来的耦合性。

[   A    ]        [       B      ]
[ b(x,y)-]--------[(defn b [x y])]
[        ]        [              ]

A、B模块间通过函数调用耦合

[      ]                  [     ]
[   A  ] - {:x 1 :y 2} -  [  B  ]
[      ]                  [     ]

A、B模块间通过数据耦合

从上面两个图,可以明显的看出:同样是处理x,y两个数据,通过函数调用的形式,A模块中必须明确写死B模块中函数名和参数,一旦B模块中函数的定义(函数名,参数个数)发生改变,A模块中相应的代码也必须修改;另一方面直接函数调用这种耦合方式,还可能带来技术层面的耦合。比如A模块要想直接调用B模块的函数,可能会要求A、B模块都使用同一只实现语言,否则,就必须使用一些特殊的方式来实现调用。而这些对于用数据来耦合的方式来说,是不存在的。A模块仅仅是扔出一定数据结构的数据,它不用直接调用B模块的代码,它只要求B模块具有处理这种数据结构数据的能力,因此在B模块中可以以任意合适的方式来处理这种数据,

2. 灵活性

使用数据来表达模块间(或服务间)的接口,其灵活性要高于用函数表达的接口。典型的代表就是RESTful API和RPC服务的API。

因为RPC的抽象工具是过程,这种抽象理念很容引导我们只关注于当前过程需要什么,定义过程性的接口。例如,当前需求是要获取一个用户的年龄。使用RPC服务,很可能就定义出,如下接口:

int user-age(string username);

当需求改变,要获取用户的地址,则又要添加接口:

string user-address(string username);

而REST风格的服务则是以资源作为抽象工具,且它的数据交互是粗粒度的,这种理念使得我们会充分考虑如何定义和表达资源,比如user这一资源,会充分考虑user资源的各个属性,哪些属性应该暴露出去,以及如何被定位到。也就是说客户端访问user资源时,会得到该资源的所有被暴露出来的数据。比如,可以设计接口如下:

发送请求:
GET /users/{username}

返回数据:
{:age 20
 :address "宇宙地球中国"
 :sex :female}

对这个接口进一步优化:可选择资源属性

发送请求:
GET /users/{username}?attrs=age,sex

返回数据:
{:age 20
 :sex :female}

可以看出,即使后面需求增加,需要获取用户的性别,身高等,都不需要再额外添加接口了,灵活性大大增加。

那对于程序内部模块间的耦合,有什么例子证明数据>函数呢?可以参考一种设计模式:命令模式。

比如同样是上面的需求,要调用专门管理用户数据的模块中的一个API,获取用户年龄和地址,使用函数表达的方式:

int user-age(string username);
string user-address(string username);

而使用数据表达的方式可以为:

query-execute (^Map params)

其中参数params是一个map结构,如:

{:method "user-info"
 :params {:attrs [:age :address];; 指明要得到的用户哪些属性的数据
          :username "username"};;访问用户数据所需要的参数
}}

实现时,可以根据:method指定的命令“user-info”来调用相应的函数(假定为user-info,也可以是任意能够处理params参数的函数)进行处理。如:

(defn handler
  [{:keys [method params] :as req-data}]
  (case method
    "user-info" (user-info params)
    ))

返回值同样是一个map结构,如:

{:age 20
 :address "宇宙世界中国"}

这样的好处是,只暴露了一个query-execute接口,其中具体要调用哪个方法,该方法对应的参数,都通过数据结构 {:method ... :params ...}来表达。这也是非常灵活的一种方式,只要你的模块能够处理这样一个数据结构就行。使用map这种数据结构,可以根据不同的处理函数使用不同的字段来表达参数,即使模块要暴露无数接口,中query-execute加上map这个数据结构就可以灵活应对。

3. 可维护性

可维护性和灵活性是一致的,你的接口足够灵活,则可维护性就高,反之则低。在上面已有所提及,就不在赘述。

三、总结

“数据>函数”在程序设计中已然越来越流行,通过java9的新特性和REST服务的风行,就可以窥见。但是程序设计的好坏,除了和这些设计理念有关之外,更重要的是自己对于程序的抽象能力(对需求的抽象、资源的抽象、行为的抽象等等)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值