业务逻辑的强类型化

 
业务逻辑中,很多逻辑上不同类型的东西,到了编程语言中,时常会退化成一种类型。一个最简单的例子就是货币。通常在我们编程时,采用一种类型,如double(有些系统中有专门的Currency类型,为了简便起见,这里使用double),来表示货币。
但是,随之而来的就是币种的问题。不同的币种间存在换算,也就是汇率的问题。比如我们有RMB和USD两种货币,分别表示人民币和美元。尽管都是货币(在代码中有相同的类型),我们却不能对他们直接赋值:
double rmb_;
double usd_=100;
rmb_=usd_;      //绝对不行,100美元可相当于768元人民币,尽管人民币在升值
必须进行汇率换算:
rmb_=usd_*e_rate;
e_rate就是汇率。这个谁都清楚。在逻辑上,100美元和768元人民币是等价的(假设今天的汇率是7.68),是可以兑换的。但在软件中,我们不能简单的赋值了事,必须做换算。
现在我们希望用代码直接表现逻辑上的意义,也就是用赋值操作:=,实现货币间的换算,该怎么做呢?啊对,没错,操作符重载。
我们可以重载operator=操作符,使其具备汇率换算的功能。(或许有人会提出异议,改变一个操作符已有的语义,是否违背大师们的教诲。但我个人认为,语义应当遵从业务逻辑,既然按照逻辑含义进行重载,不应该引发什么纠纷。否则还需要重载干吗?)但问题是,重载依赖于不同的类型,double operator=(double)的操作符定义是默认的,已经存在,无法以相同形式重载。再说,即便是可以,复制对象和被赋值对象的类型相同,如何区分两种类型的转换呢?
很明显,我们需要新的类型。typedef肯定是没指望的,因为它仅仅为一个类型起别名,并没有产生新的类型。所以,我们只能求助于类。我们可以以如下方式定义各种不同的货币类:
class RMB
{
public:
   double _val;
};
class USD
{
public:
   double _val;
};
这样,便可以针对不同货币重载operator=:
class RMB
{
public:
   RMB operator=(const RMB& v) {
       _val=v._val;
   }
   RMB operator=(const USD& v) {
       _val=v._val*e_rate; //货币换算
   }
public:
   double _val;
};
class USD
{
public:
   USD operator=(const USD& v) {
       _val=v._val;
   }
   USD operator=(const RMB & v) {
       _val=v._val/e_rate; //货币换算
   }
public:
   double _val;
};
这样,我们便可以对两种货币赋值了:
RMB     rmb_;
USD     usd_;
rmb_=usd_;      //带货币换算的赋值操作
根据这个方法,我们一直往下推,可以构造出各种各样的货币,并且定义它们之间的转换:
class UKP //英镑
{…}
class JPD //日元
{…}
不过有个问题,如果有10中货币,我们必须定义100个operator=的重载,而且都是些重复代码。这样太蠢了。得采用更好的方法才能实现我们的理想。
注意观察,每个货币类的代码都符合同一种模式,有很强的规律性。看出来了吧,这种情况非常适合使用C++的超级武器——模板。没错,说做就做:
template<int CurrType>
class Currency
{
public:
   double _val;
};
注意看,这里非常规地使用了模板的一个特性:非类型模板参数,就是那个int CurrType。模板参数通常都是一个类型,比如int什么的。但也可以是一个非类型的模板参数,就象这里的CurrType。传统上,非类型模板参数用于传递一个静态的值,用来构造模板类。但在这里,这个模板参数并没有被模板使用,也永远不会被使用。这个模板参数的作用就是“制造类型”:
typedef     Currency<0> RMB;    //人民币
typedef     Currency<1> USD;    //美元
typedef     Currency<2> UKP;    //英镑
typedef     Currency<3> JPD;    //日元
typedef本身不会产生新的类型,但是这里Currency< n>已经是完全不同的类型了。当一个模板被实例化成一个类的时候,只要模板参数的实参有所不同,便是一个不同的类型。我们利用了模板的这个特性,凭空制造出任意多个结构完全相同,但却是完全独立的类型。
好,下一步,便是重载operator=操作符。当然不能再做为每一对货币类型重载operator=的蠢事了。用一个成员函数模板就可以解决问题:
double e_rate[10][10];      //汇率表
 
template<int CurrType>
class Currency
{
public:
   template<int ct2>
   Currency<CurrType>& operator=(count Currency<ct2>& v) {
       _val=v._val * e_rate[ct2][CurrType];    //找出汇率表中相应的汇率,
                                               // 计算并赋值
   }
public:
   double _val;
};
操作符operator=的代码中,赋值对象v的值乘上一个汇率,这个汇率存放在汇率表中,通过模板参数CurrType和ct2检索(当然汇率表得足够大)。
这样,我们便可以随意地赋值,而无须关心货币转换的问题了:
///初始化汇率表
e_rate[0][0]=1;
e_rate[1][0]=7.68;
//使用货币
USD     usd_;
UKP     ukp_;
JPD     jpd_;
 
jpd_=usd_=ukp=rmb_;     //成功!一切顺心。
需要说明的是,汇率表并没有在声明时就初始化,是考虑到汇率经常变动,不应当作为常量写死在代码中。更进一步可以使用一个类封装成可变大小的汇率表,甚至可以用某个文件或数据库对其初始化。
问题当然还有,货币是要参与运算的,否则没有什么用处。所以,我们还得使这些货币具备基本的计算能力。货币的计算,根据业务逻辑大致应具备以下能力:
1.       +、-:两种货币的加法和减法,允许不同种货币参与计算,必须考虑转换操作,返回做操作数类型;
2.       *、/:货币乘上或除以一个标量值,这里设定为double。但两种货币不能相乘或相除。
3.       ==、!=:比较两种货币,允许不同种货币参与比较,但必须考虑转换操作。
还有其他的操作,暂不做考虑,毕竟这里的目的不是开发一个完整的货币系统。为了编码上的方便,这里同时还定义了四则运算的赋值版本:+=、-=、*=、/=。为了节省篇幅,这里只展示+、*和==的代码,其他代码类推:
template<int ty, int tp>
inline bool operator==(currency<ty>& c1, const currency<tp>& c2) {
   return c1._val==c2._val*curr_rate[tp][ty];
}
 
template<int ty, int tp>
inline currency<ty>& operator+=(currency<ty>& c1, const currency<tp>& c2) {
   c1._val+=c2._val*curr_rate[tp][ty];
   return c1;
}
template<int ty, int tp>
inline currency<ty> operator+(currency<ty>& c1, const currency<tp>& c2) {
   currency<ty> t(c1);
   t+=c2;
   return t;
}
请注意==和+操作符中的的货币转换运算,每次都是将第二操作数货币转换成第一操作数货币后再进行运算操作。第一参数和第二参数的类型不同,因此允许不同货币进行计算。这可以进一步简化代码,完全以逻辑的方式编程。
template<int ty>
inline currency<ty>& operator*=(currency<ty>& c1, const double q) {
   c1._val*=q;
   return c1;
}
template<int ty>
inline currency<ty> operator*(currency<ty>& c1, const double q) {
   currency<T, ty> t(c1);
   t*=q;
   return t;
}
 
template<int ty>
inline currency<ty>& operator*=(const double q,currency<ty>& c1) {
   return operator*=(c1, q);
}
template<int ty>
inline currency<ty> operator*(const double q,currency<ty>& c1) {
   return operator*(c1, q);
}
*操作符的参数只有一个是货币类型,另一个是double类型,表示数量。只有货币乘上数量才有意义,不是吗?*操作符包括两个版本,一个货币在前,数量在后;另一个数量在前,货币在后。为的是适应rmb_*1.4和1.4*rmb_两种不同的写法,算法是完全一样的。
现在,货币可以运算了:
usd_=usd_*3;    //同种货币运算
ukp_=rmb_*2.5;      ///计算後直接赋值给另一种货币
jpd_=ukp_=rmb_+usd_;    ///同上,但有四种货币参与运算
现在,货币运算非常方便了,不需要考虑货币种类,货币的转换是自动的,无需额外代码。
在简化代码的同时,也提供了操作上的约束,比如:
ukp_=rmb_*usd_;     ///编译错误。货币乘上另一种货币无意义!!!
这句代码会引发编译错误,因为我们没有为两种货币相乘提供*的重载。很明显,一种货币与另一种货币相乘是根本没有意义的。这里通过静态的重载类型检查,对施加在货币上的运算做出约束。促使违背逻辑的代码在第一时间被拦截,避免出现运行时错误。要知道,两种货币相乘,赋给另一个货币的错误是非常隐蔽的,只有盘库或结账的时候才会发现。
很好,这里我们利用了C++模板的一些特殊机制,以及操作符模板、操作符重载等技术,开发一个货币系统。这个系统可以用最简洁的语句实现各种货币的计算和转换功能。同时,还利用重载机制的强类型特性,提供了符合业务逻辑的操作约束。
货币运算只是一个简单的案例,但相关的技术可以进一步推广到更复杂的领域中。而且业务越复杂,所得到的收益越多。因此,充分理解并运用C++所带来的泛型编程功能,可以大大简化软件的开发、减少代码的错误,降低开发的成本。
这种技术适合用在一些逻辑上存在差异,但在物理上具备相同特征的实体上。一方面使这些实体在代码中强类型化,以获得重载和类型检测能力。由于代码中逻辑实体的对应类型强类型化,是我们可以通过重载和静态类型检测等技术手段,实现仅使用语言提供的要素,在代码中直接构造业务模型的能力。但手工对每一个逻辑实体进行强类型化,是费力的和琐碎的,并且存在着大量的重复劳动。此时,我们可以利用模板的抽象能力,反过来利用逻辑实体在物理上的共同特性,一次性构建抽象的模板,并利用模板实例化的一些特性,很方便地构造新的类型(仅仅一个typedef)。
这种技术进一步扩展后,可以有更高级的应用。一个经典的范例就是实现编译期的量纲分析。在 Template Meta-programming 一书中,对此有详细的讲解。 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
这是一门linux下c++通讯架构实战课程,针对c/c++语言已经掌握的很熟并希望进一步深造以将来用c++在linux下从事网络通讯领域/网络服务器的开发和架构工作。这门课程学习难度颇高但也有着极其优渥的薪水(最少30K月薪,最高可达60-80K月薪),这门课程,会先从nginx源码的分析和讲解开始,逐步开始书写属于自己的高性能服务器框架代码,完善个人代码库,这些,将会是您日后能取得高薪的重要筹码。本课程原计划带着大家逐行写代码,但因为代码实在过于复杂和精细,带着写代码可能会造成每节课至少要4~5小时的超长时间,所以老师会在课前先写好代码,主要的时间花费在逐行讲解这些代码上,这一点望同学们周知。如果你觉得非要老师领着写代码才行的话,老师会觉得你当前可能学习本门课程会比较吃力,请不要购买本课程,以免听不懂课程并给老师差评,差评也会非常影响老师课程的销售并造成其他同学的误解。 这门课程要求您具备下面的技能:(1)对c/c++语言掌握的非常熟练,语言本身已经不是继续学习的障碍,并不要求您一定熟悉网络或者linux;(2)对网络通讯架构领域有兴趣、勇于挑战这个高难度的开发领域并期望用大量的付出换取高薪;在这门课程中,实现了一个完整的项目,其中包括通讯框架和业务逻辑框架,浓缩总结起来包括如下几点:(1)项目本身是一个极完整的多线程高并发的服务器程序;(2)按照包头包体格式正确的接收客户端发送过来的数据包, 完美解决收包时的数据粘包问题;(3)根据收到的包的不同来执行不同的业务处理逻辑;(4)把业务处理产生的结果数据包正确返回给客户端;本项目用到的主要开发技术和特色包括:(1)epoll高并发通讯技术,用到的触发模式是epoll中的水平触发模式【LT】;(2)自己写了一套线程池来处理业务逻辑,调用适当的业务逻辑处理函数处理业务并返回给客户端处理结果;(3)线程之间的同步技术包括互斥量,信号量等等;(4)连接池中连接的延迟回收技术,这是整个项目中的精华技术,极大程度上消除诸多导致服务器程序工作不稳定的因素;(5)专门处理数据发送的一整套数据发送逻辑以及对应的发送线程;(6)其他次要技术,包括信号、日志打印、fork()子进程、守护进程等等;

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值