面向对象之数值的悲剧(8月28日会议有感)

简介:

枚举的悲剧

 

枚举基本用法大家应该都很熟悉,在我们去避免硬编码,增强代码可读性,可维护性方面很好用。不过话说枚举底层还是比较复杂的,以前看过一些关于枚举的说明。至少C#里边的枚举是很复杂的,本身是结构类型(值类型)的,而却继承与类System.Enum(引用类型),而引用类型System.Enum派生与值类型System.ValueType。够乱吧,其实话说我也没搞很明白,所以大家千万不要随随便便就说我很了解某个东西之类的话。


好吧,转入正题(写东西喜欢扯,没办法 ╮(╯▽╰)╭)。


先说一下枚举所带来的问题。例如,我们有个类型变量,需要存储到数据库中。当然存储到数据库中的时候,表里边存的肯定还是数值(因为枚举终究还是数值)。


例如定义枚举 
public enum Language
{
SimplifiedChinese,,
English,
French,
Japanese
}


然后假定我们有一个用户的名单,这里边有个字段记录这该用户所使用的语言。那么数据库中记录的就可能是 A 0  B 1  C 3  D 0  E 0  F  1 。OK,现在我们需要添加一种语言,我们在枚举中要加一个繁体中文TraditionalChinese,我想很有可能代码会变成成这样子。
public enum Language
{
SimplifiedChinese,
TraditionalChinese,//新添加的繁体中文 
English,
French,
Japanese
}
好吧,悲剧就这样诞生了。


数据中所有原来已英语为语言的人都改为繁体中文了!!是不是很崩溃,而且,这个bug的代码错误还隐藏的如此之深,是一个枚举量的顺序所导致的。因为TraditionalChinese 和 1具有了对应关系。


当然这个问题解决起来不是那么复杂,目前,如果不变枚举,有两种方法:

  1. 直接在添加新的枚举量的时候,补在Japanese后边,不改变原来的顺序
  2. 硬性指定各个枚举的值

    public enum Language
{
SimplifiedChinese=0,
TraditionalChinese=4,
English=1,
French=2,
Japanese=3
}


我不会誓死阻止你使用第一个方法,但是很不建议,因为你无法防范你的朋友不这样做或者忽略这么一条怪异的理由。对于第二个方法,比第一方法来说会好一点(可是看着还是有那么一点不爽),或者,你可以加一个标注“各个已有的值不可修改”╮(╯▽╰)╭

其他解决方案

  1. 既然是由于数据库中存储数值所导致的,那么我们为什么不直接把枚举.ToString()存进去呢?取出的时候,再做一次强制转换,这样不仅解决了上述问题,而且还是在查看数据库是可读性也增强了不少。
  2. 换掉枚举

这里说一下换掉枚举,即枚举的替代


我们可以使用静态常量来代替枚举。如下


public static  class Language
{
public static readonly string SimplifiedChinese = "SimplifiedChinese";
public static readonly string TraditionalChinese = "TraditionalChinese";
public static readonly string English = "English";
public static readonly string French = "French";
public static readonly string Japanese = "Japanese";
}


调用的时候,也可以像枚举一样,Language.English,可读性和可维护性都是不错的。我们还可以定义一些方法,实现枚举类似的功能。我们也可从.NET的一些设计上看到这些影子 如WebRequestMethods.Http.Put 等为const string(我还没有去想用const string 和 static readonly string 的差异,不过实现思想一样)
。在Android中一些设计上也有类似的思想,例如R类的设计。 
那么,在数据库中存储的也就是这些变量对应的字符串,这个地方和上一种方案是相同的,变数值为字符串。


关于枚举就暂时说到这里。

索引的悲剧

 

问题的起因在于,我们要做一个菜单,菜单都有索引,比如,最初 索引 0 1 2 3 对应的操单操作是 增,删,改,查。在代码中 我们很肯能会依据索引值来判断要执行什么操作(啊,这是多么危险啊)。后来又多一个操作为 清空数据。我们想把它放在菜单项的第一个位置,这样,原有的索引已被打乱,0 1 2 3 所表述的意义已经发生变化了,导致出错。同样的,做一个下拉列表也是同样的问题。解决方案不复杂,通常这里边会提供一个value,我们在创建item 时,会用这个value来唯一标识该item.这样,在添加新数据时,他也有自己唯一的value。当然这个value做的时候,也有一些代码的技巧。比如我们 先定义一些常量,让他们的名字分别标识每个item,然后,将这些item的value赋为这些变量。


像是这样:


private readonly string MenuItemAdd = "0";
private readonly string MenuItemDel = "1";
private readonly string MenuItemUpdate = "2";
private readonly string MenuItemSelect = "1";
private readonly string MenuItemClear = "1";

 

MenuItem item = new MenuItem("增?加", MenuItemAdd);
MenuItem item = new MenuItem("增?加", MenuItemDel);


当然你可以用一个类去维护这些readonly string(做这个类的时候,千万不要就是一个真正的单独类,这样化,你的项目中必将有很多零散的小类,也会使你崩溃,假如你做的是一个Android 应用程序,或许你可以定义ActivitiesStr类,里边在定义一个 DataMangActivityStr类,这个类里边再写DataMenuItemValueStr类,这样统一维护会好点,至少,我这么认为。)


Ok,这些问题讲完了,大家会说这与面向对象有关系么?还什么面向对象之数值的悲剧,这不是标题党么?


我们来分析一下这些上述问题是怎么出现的,他们的共性是什么

抽象分析

 

面向对象与数值的悲剧

 

上述两个问题,很明显,很有 相同之处,基本上为 我们原要以一个数值代表某种含义,然而由于某种改变,这个数值本省代表的含义发生了变化,这样导致出现错误。细想一下,其实我们这种想法还是很普遍的,做标记啊,什么的,传参数是定义1 标识执行A,传2时执行B,或者男为1,女为2,终究都是我们用一个数值(一个不具有实际意义的东西)去代表另外一个具有实际意义的东西。我们又为什么要用 一些不具有实际意义的1 2 3去代表 具有实际意义的物体呢?数字使我们发明出来描述客观世界的没有错,比如说,我们说这里有几个人,有几张桌子,这都没有错。但是,男,女问题,操作类型问题,他们本身根本不具有数值的意义,只不过使我们在为了方便标识二者时候,给他们的代号,在现实世界中,这个对应是根本不存在的。换句话说,这些都是我们人为强加给这些事物的性质,一旦发生改变,我们临时加给这些事物的性质就无法对抗新的变化,导致错误的出现。我们只是在写程序,而不是在描述这个世界,所以,我们的程序永远应对不了这个世界的变化。可我们是如何解决这些问题呢?


想想为枚举量强制赋固定数值的方案,English怎么变,代表的都是英语,然而1 这个数字是无辜的,他要么代表一个人,一张桌子,是一个代表数量东西,与英语没什么关系。所以,一旦后来的人不小心改了以前的某个枚举量的值(因为他不知道这个规则啊,他不可能想像1 与英语的关系,更不能想象把1改成2了英语就变成法语了)。如果我们用一些具有实际意义的string来标识,string 本身就是一个描述性的东西。比如,我描述这是一个desk,那么谁来都知道这是一个desk,(如果我说1,正常的他就不知道是什么意思)。这就是静态常量的解决方案。那个menuitem也是一样的问题,或许更好的是,我们将value也换做是一个字符串?(但至少有一个MenuItemUpdate = "2" 就比原来好点了,只是这个2又让人郁闷了一下 )。这些问题还是很有意思的,不过也常常使人纠结。


我写了这么多,不知道各位看官有没有明白我在说什么。


或许,我们应该去努力描述这个世界,而不是去做程序,去实现某个功能。客观世界是什么,就是什么,我一直这么想。


本文转自HDDevTeam 51CTO博客,原文链接:http://blog.51cto.com/hddev/651914,如需转载请自行联系原作者
相关文章
|
21小时前
|
云安全 人工智能 自然语言处理
|
5天前
|
搜索推荐 编译器 Linux
一个可用于企业开发及通用跨平台的Makefile文件
一款适用于企业级开发的通用跨平台Makefile,支持C/C++混合编译、多目标输出(可执行文件、静态/动态库)、Release/Debug版本管理。配置简洁,仅需修改带`MF_CONFIGURE_`前缀的变量,支持脚本化配置与子Makefile管理,具备完善日志、错误提示和跨平台兼容性,附详细文档与示例,便于学习与集成。
314 116
|
8天前
|
数据采集 人工智能 自然语言处理
Meta SAM3开源:让图像分割,听懂你的话
Meta发布并开源SAM 3,首个支持文本或视觉提示的统一图像视频分割模型,可精准分割“红色条纹伞”等开放词汇概念,覆盖400万独特概念,性能达人类水平75%–80%,推动视觉分割新突破。
556 51
Meta SAM3开源:让图像分割,听懂你的话
|
20天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
5天前
|
人工智能 Java API
Java 正式进入 Agentic AI 时代:Spring AI Alibaba 1.1 发布背后的技术演进
Spring AI Alibaba 1.1 正式发布,提供极简方式构建企业级AI智能体。基于ReactAgent核心,支持多智能体协作、上下文工程与生产级管控,助力开发者快速打造可靠、可扩展的智能应用。
|
4天前
|
弹性计算 人工智能 Cloud Native
阿里云无门槛和有门槛优惠券解析:学生券,满减券,补贴券等优惠券领取与使用介绍
为了回馈用户与助力更多用户节省上云成本,阿里云会经常推出各种优惠券相关的活动,包括无门槛优惠券和有门槛优惠券。本文将详细介绍阿里云无门槛优惠券的领取与使用方式,同时也会概述几种常见的有门槛优惠券,帮助用户更好地利用这些优惠,降低云服务的成本。
264 132
|
8天前
|
机器学习/深度学习 人工智能 自然语言处理
AgentEvolver:让智能体系统学会「自我进化」
AgentEvolver 是一个自进化智能体系统,通过自我任务生成、经验导航与反思归因三大机制,推动AI从“被动执行”迈向“主动学习”。它显著提升强化学习效率,在更少参数下实现更强性能,助力智能体持续自我迭代。开源地址:https://github.com/modelscope/AgentEvolver
396 29
|
14天前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
崩溃堆栈全是 a.b.c?Native 错误查不到行号?本文详解 Android 崩溃采集全链路原理,教你如何把“天书”变“说明书”。RUM SDK 已支持一键接入。
708 224