数据重构:自封装字段、以对象取代数组、以类取代类型码
好,咱们今天聊点实在的。数据重构这块,说白了就是让代码里的数据更听话、更好管。我这些年看过的代码里,十有八九的混乱都源于数据结构的随意堆砌。你想想看,一个数组里塞了名字、年龄、地址,全靠下标0、1、2来区分——这代码过两周你自己都看不懂。
这一章,我带你搞定三个最实用的数据重构手法:自封装字段、以对象取代数组、以类取代类型码。这三个技巧,我在项目中反复用过,每次都能让代码质量上一个台阶。
自封装字段:别直接碰你的数据
什么叫自封装?就是类内部的字段,也通过getter/setter来访问,而不是直接读写。听起来有点多此一举?嗯,我刚开始也觉得麻烦。
但有一次,我在维护一个支付系统时,有个字段叫status,直接暴露给内部所有方法用。后来业务改了,需要加个状态校验逻辑。我翻遍了整个类,找到所有直接赋值的地方,一个个改——漏了一个,线上出了bug。从那以后,我学乖了。
核心原则:即使是在类内部,也通过方法访问自己的字段。这样你可以在方法里加校验、加日志、加转换逻辑,而不影响调用方。
看个例子:
// 重构前:直接访问字段
typedef struct {
int age;
char name[64];
} Person;
void set_person_age(Person *p, int age) {
p->age = age; // 直接赋值,没有校验
}
// 重构后:自封装字段
typedef struct {
int age;
char name[64];
} Person;
int get_age(Person *p) {
return p->age;
}
void set_age(Person *p, int age) {
if (age < 0 || age > 150) {
fprintf(stderr, "年龄不合法: %d\n", age);
return;
}
p->age = age;
}
你看,加了校验逻辑后,调用方完全不用改。这就是自封装的好处——你可以在一个地方集中控制数据访问。
我的习惯:新写的结构体,所有字段都默认用getter/setter。等代码稳定了,再考虑哪些可以放开。这叫「先锁后开」,比「先开后锁」安全得多。
以对象取代数组:别让下标成为你的噩梦
数组本身没问题,但用数组来承载不同类型的数据,那就是灾难。我见过最夸张的代码:
// 噩梦版:全靠下标记忆
char *data[5];
data[0] = "张三"; // 名字
data[1] = "28"; // 年龄
data[2] = "北京"; // 城市
data[3] = "工程师"; // 职业
data[4] = "已婚"; // 婚姻状态
这种代码,写的时候很爽,读的时候想哭。你想想看,三个月后你回来维护,还记得data[3]是啥吗?
正确的做法,是用结构体来取代这种「魔法下标数组」:
// 重构后:用结构体取代数组
typedef struct {
char name[32];
int age;
char city[32];
char job[32];
int married; // 0:未婚 1:已婚
} PersonInfo;
PersonInfo p = {
.name = "张三",
.age = 28,
.city = "北京",
.job = "工程师",
.married = 1
};
这么一改,代码自解释能力直接拉满。你不需要注释,字段名本身就是最好的文档。
我曾经踩过的坑:有个项目用二维数组存配置,第一维是配置项,第二维是配置值。后来配置项从10个涨到50个,代码里全是data[i][j]这种写法,改一个配置要全局搜索替换。那次重构花了我整整两天。所以,别嫌麻烦,一开始就用结构体。
以类取代类型码:别用整数代表一切
类型码,就是用一个整数或枚举来表示某种分类。比如:
#define USER_TYPE_ADMIN 0
#define USER_TYPE_EDITOR 1
#define USER_TYPE_VIEWER 2
int user_type = USER_TYPE_ADMIN;
这种做法,C语言里太常见了。但问题在于:类型码本身没有行为,你需要在代码里到处写switch-case来判断类型。一旦类型多了,代码就变得又臭又长。
更好的方式,是用结构体+函数指针来模拟类的行为:
// 重构后:用结构体取代类型码
typedef struct User User;
// 定义用户类型的行为接口
typedef struct {
void (*show_permissions)(User *user);
int (*can_edit)(User *user);
int (*can_delete)(User *user);
} UserType;
struct User {
char name[64];
UserType *type; // 指向类型行为表
};
// 管理员类型的行为
void admin_show_permissions(User *user) {
printf("管理员:所有权限\n");
}
int admin_can_edit(User *user) { return 1; }
int admin_can_delete(User *user) { return 1; }
// 访客类型的行为
void viewer_show_permissions(User *user) {
printf("访客:只读权限\n");
}
int viewer_can_edit(User *user) { return 0; }
int viewer_can_delete(User *user) { return 0; }
// 类型实例
UserType admin_type = {
.show_permissions = admin_show_permissions,
.can_edit = admin_can_edit,
.can_delete = admin_can_delete
};
UserType viewer_type = {
.show_permissions = viewer_show_permissions,
.can_edit = viewer_can_edit,
.can_delete = viewer_can_delete
};
// 使用
User admin = {.name = "管理员", .type = &admin_type};
admin.type->show_permissions(&admin); // 输出:管理员:所有权限
这么一改,你再也不用写switch-case了。新增一种用户类型,只需要定义一组行为函数,挂到新的UserType实例上就行。代码的扩展性,一下子就打开了。
核心思想:类型码的本质是「分类」,而分类的目的是「不同的行为」。与其用整数来标记分类,不如直接把行为封装到类型对象里。这就是「以类取代类型码」的精髓。
三种手法的关系
这三种手法,其实是一个递进的关系:
- 自封装字段:管好单个字段的访问
- 以对象取代数组:把散乱的数据组织成结构
- 以类取代类型码:把分类逻辑变成行为对象
它们的目标是一致的:让数据更安全、更清晰、更容易扩展。
实战建议
这三种手法,我建议你按这个顺序来用:
- 先做自封装:给结构体加上getter/setter,哪怕一开始只是简单返回。这是最安全的起步。
- 再拆数组:看到长度超过3的「数据数组」,立刻想想能不能用结构体替代。下标超过3,代码的可读性就开始崩了。
- 最后处理类型码:当switch-case出现3次以上,或者类型码超过5个,就该考虑用类来取代了。
一个小技巧:重构的时候,不要一次性改完所有代码。先加一层封装,让新旧代码并存,测试通过后再逐步替换。这叫「并行运行」策略,我在大项目里屡试不爽。
数据重构,说白了就是给数据穿上「铠甲」。自封装是铠甲,结构体是铠甲,行为对象也是铠甲。铠甲越厚,代码越经得起折腾。你想想看,一个项目活个三五年,中间换三五拨人,数据结构稳不稳,直接决定了维护成本。
好了,这一章就到这里。记住我说的:别让数据裸奔,给它穿上衣服。