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

业务逻辑的强类型化(续)

阅读更多

作为一个好事者,我希望能够给我周边的人讲解这种技术。他们对C++很不熟悉,但熟悉C#。于是,我打算把这种技术移植到C#中,以便於讲解。说做就做。

我建了一个C#项目,把代码拷贝过去,然后着手修改,这样可以省些事。我立刻便遇到了问题。C#有泛型,相当于模板,但不支持非类型泛型参数,即int CurrType,只允许用一个类型作为泛型参数。这样我们就不能使用C++中耍的手法了(typedef currency<n>)。退而求其次,直接用类实现货币类型:

class RMB

{

public double _val;

}

class USD

{

public double _val;

}

这样太繁琐了,很多重复。我们可以用一个基类封装_val,货币类从基类上继承获得:

class CurrBase

{

public double _val;

}

class RMB : CurrBase

{

}

class USD : CurrBase

{

}

货币类都是空的,它们的存在只是为了创建一个新的类型。

现在处理货币转换问题。C#不能重载operator=,所以只能使用一个helper函数泛型asign代替:

class utility

{

public static void asign<T1, T2>(T<chmetcnv w:st="on" unitname="C" sourcevalue="1" hasspace="True" negative="False" numbertype="1" tcsc="0">1 c</chmetcnv>1, T<chmetcnv w:st="on" unitname="C" sourcevalue="2" hasspace="True" negative="False" numbertype="1" tcsc="0">2 c</chmetcnv>2)

where T1 : CurrBase

where T2 : CurrBase

{

c1._val = c2._val * utility.e_rate[c2.CurID(),c1.CurID()];

}

}

这个asign函数是个泛型,泛型参数分别代表了两个操作数,函数中执行了货币转换。为了能够在汇率表中检索到相应的汇率,我们必须为基类和货币类定义抽象函数:

public abstract class CurrBase

{

public double _val=0;

public abstract int CurID();

}

public class RMB : CurrBase

{

public override int CurID()

{

return 0;

}

}

基类中声明了CurID()抽象方法,并在货币类中定义。这样,便可以用统一的方式进行货币转换了:

asign(rmb_, usd_);

还行,尽管不那么漂亮,但也还算实用。不过,当我多看了几眼代码后,便发现这里根本没有必要使用泛型。完全可以利用OOP的多态实现同样的功能:

public static void asign(CurrBase c1, CurrBase c2)

{

c1._val = c2._val * utility.e_rate[c2.CurID(),c1.CurID()];

}

不过也没关系,使用方式还没有变,代码反而更简单了。使用泛型毕竟不是我们的根本目的,对吧?

现在轮到运算符了。不过我不知该把泛型运算符定义在哪里。按C#文档里的要求,运算符必须是类的static成员。但我的泛型运算符是针对许多个货币类的,定义在任何一个中,对其他类似乎不太公平。于是,我决定尝试将其定义在基类里:

public abstract class CurrBase

{

public static CurrBase operator+<T1, T2>(T<chmetcnv w:st="on" unitname="C" sourcevalue="1" hasspace="True" negative="False" numbertype="1" tcsc="0">1 c</chmetcnv>1, T<chmetcnv w:st="on" unitname="C" sourcevalue="2" hasspace="True" negative="False" numbertype="1" tcsc="0">2 c</chmetcnv>2)

where T1 : CurrBase

where T2 : CurrBase

{

}

}

编译器立刻还我以颜色:操作符根本不能是泛型!好吧,不能就不能吧,继续退而求其次,用OOP

public abstract class CurrBase

{

public static CurrBase operator+(CurrBase c1, CurrBase c2)

{

}

}

不过,这次让步让得有点离谱。当我写下这样的代码时,编译器居然不认账:

rmb_=rmb_+usd_;

错误信息是:错误 CS0266: 无法将类型“st_in_cs.CurrBase”隐式转换为“st_in_cs.RMB”。存在一个显式转换(是否缺少强制转换?)

我非得采用强制类型转换,才能过关:

rmb_=(RMB)(rmb_+usd_);

太夸张了,这样肯定不行。于是,我被迫在每个货币类中定义operator+

class RMB : CurrBase

{

public RMB operator+(RMB c1, USD c2)

{

}

public RMB operator+(RMB c1, UKP c2)

{

}

}

这可不得了,我必须为每对货币类定义一个+操作符,+操作符的总数将会是货币类数量的平方!其他的操作符每个都是货币类数的平方。我可受不了!

好在,可爱的OOP为我们提供了一根稻草,使得每个货币类的每个操作符只需定义一个:

class RMB : CurrBase

{

public RMB operator+(RMB c1, CurrBase c2)

{

}

}

这样,任何货币类都可以作为第二操作数参与运算,而操作符只需定义一个。这样的工作量,对于一个逆来顺受的程序员而言,还是可以接受的。很好,代码不出错了:

rmb_=rmb_+usd_;

但当我写下如下代码时,编译器又开始抱怨了:

ukp_ = rmb_ + usd_;

还是要求显示转换,除非我们为UKP定义隐式类型转换操作符:

class UKP

{

public static implicit operator UKP(RMB v)

{

}

}

光有RMB的不行啊,还得有USD的、JPD…。不过这样的话,我们必须为每一个货币类定义所有其它货币类的类型转换操作符。又是一个组合爆炸。到这里,我已经黔驴技穷了。谁让C#不支持=操作符重载和操作符模板化呢。没办法,只能忍着点了。

不过,如果我们能够降低点要求,事情还是有转机的。如果我们不通过操作符,而是采用static成员方法,进行货币的运算的话,就可以省去很多代码了:

public class utility

{

public static T1 asign<T1, T2>(T<chmetcnv w:st="on" unitname="C" sourcevalue="1" hasspace="True" negative="False" numbertype="1" tcsc="0">1 c</chmetcnv>1, T<chmetcnv w:st="on" unitname="C" sourcevalue="2" hasspace="True" negative="False" numbertype="1" tcsc="0">2 c</chmetcnv>2)

where T1 : CurrBase, new()

where T2 : CurrBase

{

c1._val = c2._val * utility.curr_rate[c2.CurID(),c1.CurID()];

return c1;

}

public static T1 add<T1, T2>(T<chmetcnv w:st="on" unitname="C" sourcevalue="1" hasspace="True" negative="False" numbertype="1" tcsc="0">1 c</chmetcnv>1, T<chmetcnv w:st="on" unitname="C" sourcevalue="2" hasspace="True" negative="False" numbertype="1" tcsc="0">2 c</chmetcnv>2)

where T1 : CurrBase, new()

where T2 : CurrBase

{

T1 t=new T1();

t._val=c1._val + c2._val *

utility.curr_rate[c2.CurID(),c1.CurID()];

return t;

}

}

这里,我还是使用了泛型,因为这些函数需要返回一个值,只有使用泛型,才能返回一个明确的类型,以避免强制转换的要求。于是,赋值和计算的代码就成了:

asign(jpd_, asign(ukp_, add(rmb_, usd_)));//也就是jpd_=ukp_=rmb_+usd_

的确是难看了点,但是为了能够少写点代码,这也只能将就了。

好了,我尽了最大的努力,试图在C#中实现强类型、可计算的货币系统。尽管最终我可以在C#中开发出一组与C++具有相同效果的货币类(除了赋值操作以外),但需要我编写大量的代码,实现各种计算操作,以及货币类之间的类型转换操作(组合爆炸)。相比C++中总共200来行代码,的确复杂、臃肿得多。

我并不希望把这篇文章写成“C++ vs C#”,(尽管我很高兴看到C++C#J)。我希望通过对这样一个代码优化任务,显示不同技术运用产生的结果。同时,也可以通过这两种实现尝试的对比,了解泛型编程的作用,以及泛型编程对语言提出的要求。

毋庸置疑,C++采用了纯粹的泛型编程,因此可以对问题进行高度抽象。并利用问题所提供的每一点有助于抽象的信息,以最简的形式对问题建模。而作为以OOP为核心的语言C#,对泛型的支持很弱。更重要的是,C#的泛型对泛型参数的运用严重依赖於泛型参数的约束(where)。如果没有whereC#将泛型参数作为Object类型处理,此时泛型参数没有意义(我无法访问该类型的成员)。如果有了whereC#要求泛型参数必须同where中指定的类型有继承关系(如asign中的T1必须是CurrBase的继承类)。而泛型函数中对泛型参数的使用也局限在约束类型(即CurrBase)上。于是,我们可以直接用以基类(CurrBase)为参数的asign函数代替泛型版本的asign。由于C#对泛型参数的继承性要求,使得泛型被困住了手脚,无法发挥应用的作用。正由于这些问题,C++才采用了现在模板的形式,而没有采用同C#一样的泛型模式。

或许有人会说,既然OOP能解决问题(asign最初不需要泛型也行,但最终还需要泛型来控制返回值的类型),为什么还要GP呢?

对于这个问题,前面也给出了答案。由于C#的泛型不支持非类型泛型参数,因此迫使我们使用传统OOP的手段:利用基类实现货币类的实现,定义货币类来创建新类型,使货币强类型化,利用虚函数提供货币独有信息。仅这一层面,OOP方式已经大大不如GP方式了,GP仅定义了一个模板,所有的货币类型都是通过typedef一句语句产生,无需更多的代码。而OOP方式要求必须为每一个货币编写一个类,代码量明显多于GP方式。

此后,C++通过重载一组操作符模板,实现货币的运算。而货币模板以及生成货币类型的typedef都无须任何改变。而在C#中,由于不支持泛型操作符,被迫定义大量的特定于类型的操作符。所有运算操作符,在每个货币类中都必须重载一次。而转型操作符,则必须在每个货币类中重载n-1次。

换句话说,有n种货币,有m个操作符(包括转型操作符),那么就需要定义n+1个类(包括基类),n×m+n×(n-1)个操作符。假设n=10m=10,那么总共需要11个类定义,190个操作符重载!如果每个类定义需要20行代码,而每个操作符重载需要5行代码,那么总共需要1170行代码。如果货币数量增加,总的代码数将以几何级数的方式增长。

上面的计算表明,尽管OOP可以解决问题,实现我们的目标,但所带来的开发量和维护量却是难以承受的。而且,OOP的方式扩展非常困难,随着系统规模的扩大,扩展将越来越困难。所有这些都表明一点,尽管OOP是软件工程的明星,但在实际情况下,很多地方存在着OOP无法解决或难以解决的问题。这也就是为什么业界的先锋人物不断拓展和强化泛型编程的原因。
分享到:
评论

相关推荐

    数据可视化智能软件主要性能指标.doc

    K 软件提供具备丰富的"矢量"行业图库集,图库集具有自己的属性方法,可提供用户编写 脚本逻辑,图库设计参考行业软件设计标准,图库包括工控场合常见的符号之外,还具备 行业定制化专用图库如油田、燃气、楼宇、新...

    LibraryAutomationSystem

    它应该根据签出图书馆书的人的借阅特权来执行签出规则(应用程序的“业务逻辑”)。 图书馆为两种不同类型的图书馆读者提供书籍:社区读者和学者学者。 社区顾客可能会在3周内检出书籍,并且一次最多可以检出5本书...

    asp.net知识库

    .NET 2.0里使用强类型数据创建多层应用 在MastPage中引用脚本资源 2.0正式版中callback的一些变化+使用示例(ASP.NET 2.0) asp.net 2.0 新特性 Visual Web Development 2005开发ASP.NET使用小技巧 ASP.NET 2.0 ...

    iBATIS实战

    1.2.3 业务逻辑层 12 1.2.4 持久层 13 1.2.5 关系数据库 15 1.3 使用不同类型的数据库 17 1.3.1 应用程序数据库 17 1.3.2 企业数据库 18 1.3.3 私有数据库 19 1.3.4 遗留数据库 20 1.4 iBATIS如何解决数据库的常见...

    Java范例开发大全 (源程序)

     实例7 逻辑运算符 14  实例8 位运算符 15  实例9 移位运算符 16  实例10 转型运算符 17  2.3 其他形式 18  实例11 常量与变量 18  实例12 各种进制的转换 19  实例13 Java中的进制与移位运算符 22 ...

    java范例开发大全(pdf&源码)

    实例7 逻辑运算符 14 实例8 位运算符 15 实例9 移位运算符 16 实例10 转型运算符 17 2.3 其他形式 18 实例11 常量与变量 18 实例12 各种进制的转换 19 实例13 Java中的进制与移位运算符 22 第3章 条件控制语句(教学...

    java范例开发大全源代码

     实例7 逻辑运算符 14  实例8 位运算符 15  实例9 移位运算符 16  实例10 转型运算符 17  2.3 其他形式 18  实例11 常量与变量 18  实例12 各种进制的转换 19  实例13 Java中的进制与移位...

    java范例开发大全

    实例7 逻辑运算符 14 实例8 位运算符 15 实例9 移位运算符 16 实例10 转型运算符 17 2.3 其他形式 18 实例11 常量与变量 18 实例12 各种进制的转换 19 实例13 Java中的进制与移位运算符 22 第3章 条件控制语句(教学...

    vb学生成绩管理系统

     要编写一个实用的数据库程序,必须系统地学习过数据库原理的知识,并首先设计出符合用户业务需求的数据库体系,然后才能利用某种语言,开发出针对这个数据库的交互程序:数据库应用程序。  2. 程序的主要功能  ...

    Java范例开发大全(全书源程序)

    实例7 逻辑运算符 14 实例8 位运算符 15 实例9 移位运算符 16 实例10 转型运算符 17 2.3 其他形式 18 实例11 常量与变量 18 实例12 各种进制的转换 19 实例13 Java中的进制与移位运算符 22 第3章 条件控制...

Global site tag (gtag.js) - Google Analytics