`
helloyesyes
  • 浏览: 1306848 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

业务逻辑的强类型化

阅读更多
业务逻辑中,很多逻辑上不同类型的东西,到了编程语言中,时常会退化成一种类型。一个最简单的例子就是货币。通常在我们编程时,采用一种类型,如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一书中,对此有详细的讲解。
分享到:
评论

相关推荐

    业务逻辑业务逻辑业务逻辑业务逻辑

    业务逻辑设计的目的是为了实现业务流程的自动化、简化和优化,以提高业务效率和降低成本。 根据提供的文件信息,我们可以看到该业务逻辑设计包含了多个级别的设计描述,从 Level 0 到 Level 2 设计描述,每个级别都...

    细说业务逻辑

    是否应该在业务逻辑层进行数据的格式化处理,这是一个常见的争议点。通常认为,数据格式化应由表示层处理,但有时为了提高效率和减少重复代码,业务逻辑层也可能承担这部分责任。 #### 数据合法性及完整性验证 ...

    类型化数据集应用实例

    类型化数据集是训练各种机器学习模型的理想选择,包括但不限于线性回归、逻辑回归、决策树、随机森林、支持向量机(SVM)、神经网络等。这些模型可以用于预测、分类、聚类等多种任务。 五、模型评估与优化 在建立...

    类型化DataSet登陆

    4. 强类型接口:在类型化DataSet中,可以为DataTable添加强类型属性,例如`public UsersTable Users { get; set; }`,这样在代码中可以直接使用`Users`对象进行操作。 5. 实现登录逻辑:在业务逻辑层,调用DAL的...

    EMP开发后台业务逻辑总结.doc

    EMP Explorer作为开发工具,提供了图形化的界面来支持数据字典的编辑、服务定义的创建以及节点定义的管理,使得开发者能更直观地进行业务逻辑的构建和维护。 总结来说,EMP开发的后台业务逻辑涉及数据字典的规划与...

    基于 React 的企业管理系统开发经验,带你学习如何抽象复杂业务逻辑,帮助团队实现效能提升.pdf

    ### 基于React的企业管理系统开发经验:抽象复杂业务逻辑以提升团队效能 #### 前言 在当今数字化转型的时代背景下,企业管理系统扮演着至关重要的角色。它们不仅需要高效地处理日常运营中的大量数据,还要能灵活...

    Python3.x+Pyqt5实现绘图界面(matplotlib绘图;graphicview控件中)和业务逻辑分离案例04_自己写的,有UI界面源代码

    在本案例中,我们探讨了如何使用Python3.x和PyQt5库来创建一个具有图形界面的应用程序,其中集成了matplotlib绘图功能,并且实现了业务逻辑与用户界面的分离。这个名为"Python3.x+Pyqt5实现绘图界面(matplotlib绘图...

    C# 强类型数据集

    4. **业务逻辑**:处理数据验证、事务处理和业务规则。 5. **示例讲解文档**:可能是PDF或HTML格式,详细解释了示例项目中的关键概念和技术。 **四、如何创建和使用强类型数据集** 1. **打开Visual Studio**:创建...

    电信设备-一种业务逻辑层向表示层反馈控制信息的方法.zip

    4. **接口标准化**:确保业务逻辑层与表示层之间的接口清晰、规范,可以方便地支持多种类型的表示层实现,比如Web界面、移动应用等。 5. **错误处理和重试机制**:在信息传输过程中可能出现错误,方法可能包含了...

    Scott Mitchell 的ASP_NET 2_0数据教程之二:创建一个业务逻辑层

    为了保持组织结构,创建两个子文件夹,一个用于DAL,一个用于BLL,将类型化数据集移动到DAL文件夹,并在BLL文件夹中创建四个类文件。 **方法实现** 每个BLL类将包含与TableAdapter对应的方法,但在此基础上增加...

    PetShop 4.0 详解之五(PetShop之业务逻辑层设计)

    新版本引入了多项特性,如System.Transactions用于事务管理,泛型强类型集合增强了数据操作的效率,以及ASP.NET 2.0的成员身份和配置文件功能,提供了更简洁的用户界面和管理机制。 **体系结构抽象** PetShop 4.0...

    Python3.x+ChartDirector实现数据可视化界面和业务逻辑分离案例01_自己写的,有UI界面源代码

    在这个案例中,我们讨论的是如何利用Python3.x和ChartDirector 6.1来实现数据可视化界面,并且将界面与业务逻辑进行有效的分离。 首先,数据可视化是数据分析中的关键环节,它能帮助用户更好地理解复杂的数据集。...

    C#三层代码生成器(生成模型层,数据访问层,业务逻辑层)

    它通过ADO.NET、Entity Framework等ORM(对象关系映射)工具将数据库操作抽象化,使得业务逻辑层可以专注于业务处理,而不必关心具体的数据库细节。 3. **业务逻辑层(Business Logic Layer, BLL)**:这是应用的...

    Ruby-ActiveInteraction管理特定于应用程序的业务逻辑

    6. **支持多种输出类型**:交互不仅可以返回简单的值,还可以返回复杂的对象,如数据库记录或自定义对象,使得业务逻辑的实现更加灵活。 7. **易于扩展**:ActiveInteraction提供了一个强大的扩展机制,允许你...

    详解票据业务运行逻辑和演变历程.doc

    随着金融市场的发展,票据业务经历了从传统纸质到电子化的转变,提高了效率,降低了风险。政策法规的完善,如《商业汇票承兑、贴现与再贴现管理暂行办法》,促进了市场规范。同时,银行对风险控制的要求不断提高,...

    Python3.x+PyQtChart实现数据可视化界面(PyQtChart绘图;还有保存图片)和业务逻辑分离案例01_自己写的,有UI界面源代码

    在本案例中,我们将深入探讨如何使用Python3.x版本结合PyQtChart库来创建一个数据可视化界面,并实现图像保存功能,同时确保业务逻辑与用户界面的分离。PyQtChart是PyQt5框架的一部分,用于创建高质量的2D图表,非常...

    ASP.NET MVC5网站开发之业务逻辑层的架构和基本功能 (四)

    在ASP.NET MVC5框架中,业务逻辑层(Business Logic Layer,BLL)是应用程序的核心部分,它负责处理业务规则、数据验证以及与数据存储层的交互。本篇将深入探讨Ninesky.Core项目中实现的业务逻辑层的架构和基本功能...

    用来删除所有业务逻辑的命令 iOS

    在iOS开发中,业务逻辑的组织和管理是一个关键任务,特别是在大型项目中。"用来删除所有业务逻辑的命令 iOS" 提供了一种解决方案,通过RBCommander库来简化这一过程。RBCommander是由Roshan Nindrai创建的,它是一个...

    五层逻辑架构设计(转)

    3. **业务逻辑层** (Business Logic Layer) 4. **数据访问层** (Data Access Layer) 5. **数据和存储管理层** (Data and Storage Management Layer) 这五个层次各自承担不同的职责,通过相互协作实现复杂的应用程序...

Global site tag (gtag.js) - Google Analytics