面向对象的思考

该文章主要是为了解释,为什么c++中为什么要将变量放到class的private,而不是public中。

有的人会说,我将变量放到private中,还需要为每个变量写get,set接口,这不是自找麻烦嘛。

有的人会说,我将变量放到priavte中,也没什么用啊,用户还是可以通过hack拿到数据的。

首先表达我的观点,以上的说法是不正确的,首先class是面向对象的工具,而在面向对象的世界中,对象本身就不需要get,set接口。因为决定一个对象最关键的因素是它的行为,它并不关心你有哪些数据或者函数,对于外部来说,它只关心你可以做什么。举个例子,如果是饭店的场景,那么每个员工是什么对象取决于他们在做什么,在做饭的就是厨师对象,他必然有做菜这个行为,至于是用什么做菜,长得好不好看我们不关心;在收银的就是前台对象,它必然有算账这个行为,至于他是用笔算还是用电子记账我们不关心;在上菜收拾桌子的就是服务员对象,它必然有端菜这个行为,至于他是用盘子还是用推车我们也不关心。在饭店这个场景中,他们长得好不看好看,个子高不高,这些因素我们并不关心,属于实现细节,我们所关心的仅仅是它的行为,也就是说它在某种情况下会做什么事情。

就像我们平时看cppreference.com时,容器库中的描述,也不会定义每个容器应该有怎样的变量,只有对接口的描述。因为容器如何实现,包含哪些变量是属于实现细节。举个例子,对于std::string的实现,当拷贝时,我们可以有很多种策略,比如eager copy、COW、SSO,每种不同的实现可能都需要定义不同的变量。我们如果将这些变量放到private中,属于是将实现细节放到了接口中,这直接会将耦合度拉满,程序完全没有可扩展性。

下面具体解释一下面向对象的特性:

1.抽象

  抽象是将对象的基本行为进行概括,它有明显的区分与其他对象的特点、边界,并且这些和它所在的领域相关。抽象主要是将场景中最重要部分进行总结,而忽略那些细枝末节。例如进程是对运行程序的抽象,不同操作系统实现的进程细节可能完全不同,但是它们必然具有相同的行为(创建、执行、等待、终止等);地址空间是对存储器的抽象,甚至不同版本的OS地址空间的细节处理可能也大有不同,但它们必然也有相同的行为(地址映射、内存分配、内存保护等)。

下面写个成绩系统简单的示例:

struct Grade;
class GradeManager {
public:
    void record(Grade grade);
    Grade view(int id);
}

类似于这样简单的成绩管理系统,我们对它的抽象就是记录成绩和查询成绩,我们要记录成绩,最终的成绩到底是记录到excel、数据库、缓存、甚至是txt中,这个是属于实现细节,可能我们会根据不同的配置,让它去存储到不同的位置中。在查看成绩时,我们可能从数据库、缓存中查询,这些也是实现细节。我们的实现可能是这样的:

struct Grade;
class GradeManager {
public:
    void record(Grade grade);
    Grade view(int id);
private:
    void recordToDB(Grade grade);
    void recordToExcel(Grade grade);
    enum class RecordConfig{
        DB,
        EXCEL,
        TXT
    };
    RecordConfig recordConfig;
}

void GradeManager::record(Grade grade) {
    if(recordConfig==RecordConfig::DB) {
        recordToDB(grade);
    } else if(recordConfig==RecordConfig::EXCEL) {
        recordToExcel(grade);
    }
}

如果我们将这样的实现细节,也就是private全放到public中。一是会暴露出实现细节,从而代码的耦合性增加;二是修改难度大幅度提升,例如今后要将枚举中的某个配置移除,需要考虑到可能很多模块依赖于这个枚举值;三是增加错误的可能性,recordConfig已然属于哪个用户都想改就改,那么很容易导致程序错误;四是重构极其困难,内部的任何修改都可能会导致程序出现难以想象的错误。

2.封装

“隐藏是为了防止出现问题,而不是防止欺骗”。

当我们完成了对象的抽象后,就要去实现它,当然,我们的实现可能会有多种,例如上面的代码中的实现,就应该将其隐藏起来。封装主要包含了两个元素,一个是接口,一个是实现。也就是我们的public和private,但需要注意的是,不是说仅仅只把变量放到private中,而是把非接口的内容都放于private,包括一些便捷函数。对象的构建过程是这样的,我们首先先通过抽象将对象的基本行为定义出来,然后通过封装,将接口暴露给用户,暴露给外界,将实现细节进行隐藏。

在现实中,只有当暴露细节带来的好处远大于它带来的复杂性时,我们才会这样做。例如当我们仅在单线程使用shared_ptr且它的计数原子操作已经成为性能瓶颈时,我们可以使用__shared_ptr来替代它(__shared_ptr是g++中shared_ptr的基类,它可以自定义计数器的操作类型是单线程、原子、锁)。但是,封装也避免不了所有的问题,因为我们总是可以看到类的实现的。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值