业务逻辑中,很多逻辑上不同类型的东西,到了编程语言中,时常会退化成一种类型。一个最简单的例子就是货币。通常在我们编程时,采用一种类型,如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) {
returnc1._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];
returnc1;
}
template<int ty, int tp>
inline currency<ty> operator+(currency<ty>& c1, const currency<tp>& c2) {
currency<ty> t(c1);
t+=c2;
returnt;
}
请注意==和+操作符中的的货币转换运算,每次都是将第二操作数货币转换成第一操作数货币后再进行运算操作。第一参数和第二参数的类型不同,因此允许不同货币进行计算。这可以进一步简化代码,完全以逻辑的方式编程。
template<int ty>
inline currency<ty>& operator*=(currency<ty>& c1, const double q) {
c1._val*=q;
returnc1;
}
template<int ty>
inline currency<ty> operator*(currency<ty>& c1, const double q) {
currency<T, ty> t(c1);
t*=q;
returnt;
}
template<int ty>
inline currency<ty>& operator*=(const double q,currency<ty>& c1) {
returnoperator*=(c1, q);
}
template<int ty>
inline currency<ty> operator*(const double q,currency<ty>& c1) {
returnoperator*(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一书中,对此有详细的讲解。
分享到:
相关推荐
1数据中心逻辑架构设计 1 1.1数据中心逻辑架构 1 1.1.1源数据层 指服务于企业各业务系统的基层单元数据,这些数据支持了企业各类业务的应用,但 存在数据分散、局部性强、不利于企业级的数据分析、应用;建设数据...
相同的操作,不同产品线的业务逻辑不同,例如下单,横向来说,不同产品线、不同类型的组合有几十种业务逻辑分支,纵向说,不同产品线所需步骤也不相同,这种情况下代码如何组织? 3、业务需求多,往往一个需求过来要...
此存储库包含业务逻辑服务的代码。 问题描述 这个应用程序旨在创建一项服务,告诉用户在地图上的特定位置附近可能会发现什么类型的食品卡车。 此外,用户可以输入他/她想在用户当前位置附近找到的食物类型或食物...
1数据中心逻辑架构设计 1 1.1数据中心逻辑架构 1 1.1.1源数据层 指服务于企业各业务系统的基层单元数据,这些数据支持了企业各类业务的应用,但 存在数据分散、局部性强、不利于企业级的数据分析、应用;建设数据...
业务框架在单线程中平均每秒可以执行 1152 万次业务逻辑代码即联调文档、JSR380验证、断言 + 异常机制 = 更少的维护成本。框架具备智能的同进程亲和性;开发中,业务代码可定位与跳转。架构部署灵活性与多样性:既可...
从实践项目中提炼出了基于构件软件开发(Component Based Software Development,CBSD)的多租户个性化方法框架,包括流程、扩展类型和技术支撑等,对多租户应用系统的业务逻辑、界面逻辑和数据实体三个部分的个性化...
1、使用场景主要是为了针对核心流程比较统一,并且固定的业务情况下,但是存在接入渠道或者业务场景存在少量特性化处理。 2、较少if/else保证代码的清晰整洁,并且改动个别业务的情况下不影响其他业务的逻辑。 3、...
一个帮助程序库,通过自动生成GraphQLSchema并通过Sequelize模型管理graphQL,您可以专注于业务逻辑。 安装 npm install sequelize-graphql-schema 先决条件 该软件包假设您已经安装了graphql并sequelize (两个...
这个项目的规模较大,包含了大量的业务逻辑和数据处理,因此将后台管理系统的代码转化为Kotlin将是一个庞大的工程。但是,通过逐步迁移和优化代码,可以逐步完成这一转化过程,并确保项目的稳定性和性能优化。总的来...
强大的数据库访问类dbhelper 2.0 ...5、使用了线程本地存储,使之能支持在业务逻辑层显式控制事务。 6、注释较为详细,配置十分简单,方法较为简洁(加上注释总共200多行代码)。 7、开源,容易修改。
GoView 是一个Vue3搭建的低代码数据可视化开发平台,将图表或页面元素封装为基础组件,无需编写代码即可完成业务需求。 它的技术栈为:Vue3 + TypeScript4 + Vite2 + NaiveUI + ECharts5 + Axios + Pinia2 + PlopJS...
7、说出几个业务逻辑漏洞类型? 8、文件包含漏洞 9、业务逻辑漏洞,用户任意密码重置有什么例子,因为什么因素导致的? 10、渗透测试过程中发现一个只能上传z文件的功能,有什么可能的思路? 11、试述DHCP初始化租约...
平台化定位,进行了业务隔离设计,方便一套系统支撑不同玩法的业务类型和便于定制化扩展。前后端分离,通过服务接入层进行路由适配转发。天然的分库分表,消息解耦和分布式缓存设计,支持弹性扩容,以支持大数据高...
流 安装 将此行添加到您的应用程序的Gemfile中: gem "flow" ... 可以定义几种类型的数据,例如argument , option和output 。 $ rails generate flow:state Charge # app/states/charge_state.
目前,省级政务管理平台中共包括四类逻辑存储单元:组织机构用户管理数据库、权 限访问控制管理数据库、业务表单构建数据库和业务流程构建数据库,分别用来存储平 台的基础配置数据、业务数据和非结构化数据,...
data 目录之下存放平台本体的 mapping mapping及数据库的连接, log ic 目录下包含 action( 平台提供的 action action定义 ), code (平台提供的业务逻辑 ), fn (平台提供 的函数定义 ), 平台级应用模块是指...
Flowcharts- 适用于更复杂的业务逻辑,使您能够通过多个分支逻辑运算符以更多样化的方式集成决策和连接活动。State Machines?- 适用于大型项目;?他们在执行中使用有限数量的状态,这些状态由条件(转换)或活动触发...
这个流程是自定义表单,展现的是基于子表控件自定义开发的个性化子表,子表类型SheetGridView的使用; 业务场景 使用子表数据项的场合,使用子表控件来呈现自定义的效果。 设计过程 使用自定义表单实现; 演示过程 ...
Spring提供了丰富的处理器类型,在真正处理业务逻辑前,有些处理器会事先执行两项预处理工作: 1)将HttpServletRequest请求参数绑定到一个POJO对象中; 2)对绑定了请求参数的POJO对象进行数据合法性校验; ④ ...