c++ 面向对象的类设计

类的设计在于用恰到好处的信息来完整表达一个职责清晰的概念,恰到好处的意思是不多也不少,少了,就概念就不完整;多了,就显得冗余,累赘,当然特例下,允许少许的重复,但是,这里必须要有很好的理由。冗余往往就意味着包含了过多的信息,概念的表达不够精准,好比goto,指针,多继承这些货色,就是因为其过多的内涵,才要严格限制其使用。好像,more effective c++上说的,class的成员函数,应该是在完整的情况下保持最小化。但是,这里我们的出发点,是成员数据的完整最小化。

最小化的好处是可以保持概念最大的独立性,也意味着,可以用最小的代价来实现这个概念,也意味着对应用层的代码要求越少,非侵入式?好比c++11 noexcept取代throw(),好比从多继承中分化出来接口的概念,好比不考虑多继承虚继承的普通成员函数指针。又比如,如果不要求只读字符串以0结束,那么就可以把只读字符串的任何一部分都当成是只读字符串。类的对外功能固然重要,但是,类不能做的事情,也很重要。

首先是要有清晰的概念以及这个概念要支持的最基本运算,然后在此基础上组织数据,务求成员数据的最小化。当然,概念的产生,并非拍着脑袋想出来的,是因为代码里面出现太多那种相关数据的次数,所以就有必要把这些数据打包起来,抽象成一个概念。好比说,看到stl算法函数参数到处开始结束的迭代器,就有必要把开始结束放在一起。比如说,string_view的出现,这里假设其字符存储类型为char,string_view就是连续char内存块的意思,可以这样表示

struct string_view
{
     const char* textBegin;
     size_t length; //或者 const char* textEnd
};

这里的重点是,string_view里面的两个成员字段缺一不可,但是也不必再添加别的什么其他东西。然后,在这两个数据上展开实现一系列的成员函数,这里,成员函数和成员字段这两者,有一点点鸡生蛋生鸡的纠结,因为必要成员函数的集合(原始概念的细化),成员函数决定了成员字段的表示,而成员字段定下来之后,这反过来又能够验证成员函数的必要性。不管怎么说都好,成员函数的设计,也必须遵从最小完整化的原则。再具体一点,就是说但凡一个成员函数可以通过其他成员函数来实现,就意味着这个函数应该赶出类外,作为全局函数存在。当然,这也不是死板的教条,有些很特殊的函数,也可以是成员函数,因为成员函数的使用,确实很方便。

可能会有疑惑,感觉所有的成员函数其实都可以是全局函数。或者说,我们可以对每一个成员字段都搞一对set、get的函数,那么所有的其他成员函数就可以是全局函数的形式,很容易就可以遵守最小完整化的原则。当然,这是明显偷懒,拒绝思考的恶劣行为。与其这样,还不如就开放所有的成员字段,那样子就落入c语言的套路了。所以的法论是,一个函数,这里假设是全局函数,如果它的实现必须要访问到成员字段,不能通过调用该类的成员函数(一般不是get,set)来达到目的,或者,也可以强行用其他函数来完成任务,但是很麻烦,或者要付出时间空间上的代价,那么就意味着这个函数应该是该类的成员函数。

类的设计,就是必不可少的成员字段和必不可少的成员函数,它们一起,实现了对类的原始概念的完整表达,其他什么的,都不必理会。一个类如果不好写,往往意味着这个类的功能不专一,或者其概念不完整,这时,可以不要急着抽象,如果一个类有必要诞生,那么在代码的编写中,该类的抽象概念将一再重复出现,猿猴对它的理解也越来越清晰,从而,水到渠成地把它造出来。所有非需求推动,非代码推动的,拍着脑袋,想当然的造类行为,都是在臆造抽象,脱离实际生活的艺术,最终将被淘汰。

类的设计,其着眼点在于用必要的数据来完整表达一个清晰的概念。而继承,则是对类的概念进行细化,也就是分类,好比说生物下面开出来动物、植物这两个子类,就是把生物分成动物、植物这两类,继承与日常生活的分类不太一样,继承的分类方式是开放式,根据需要,随时可以添加新的子类别。整个类的体系,是一颗严格的单根树,任何类只能有一个根类。从任何类开始,只能有一条路径回溯到最开始的根类,java、C#中就是Object,所有的类都派生自Object,这是一棵大树。单根系下,万物皆是对象,这自然很方便,起码,这就从语言层面上直接支持c++ std的垃圾any了。而由于java、C#完善的反射信息,抛弃静态类型信息,也可以做动态语言层面上的事情,而c,c++的void*,所有的动态类型信息全部都在猿猴的大脑中。java平台上生存着大把的动态语言,而且,性能都还很不错。

相对很多语言来说,c++就是怪胎就是异数,自有其自身的设计哲学,它是多根系的,它不可能也没必要搞成单根系,当然,我们可以假设一个空类,然后所有的类都默认继承自这个空类。c++的所有类组成一个森林,森林里的树都长自大地。但是不管怎么说都好,只能允许单继承,千万不要有多继承,这是底线,千万千万不能违背(当然,奇技淫巧的场合,就不必遵守这个戒条,多继承千般不是,但是不可或缺,因为它可以玩出来很多花样,并且都很实用很必要)。最起码,单根系出来的内存布局直观可预测,一定程度上跨编译器,只有良好的内存布局,才有望良好的二进制复用。另外,父类对子类一无所知,不要引用到子类一丁点的信息,要保持这种信息的单向流动性。

在这种单根系的等级分明的阶级体系下,一切死气沉沉,没有一点点的社会活力。显然,只有同属于同一父类的类别之间,才能共享那么一丁点可怜的共性。如果没有接口捣乱,将是怎样的悲剧,最好的例子,mfc,真是厉害,没有用到接口,居然可以做出来严谨满足大多数需要的gui框架,没有接口,并不表示它不需要,因为mfc开了后门,用上了更厉害的玩意----消息发送,即便如此,mfc有些地方的基类还有依赖到子类,这就很让人无语了。

c++下,类的设计绝对不对儿戏,一定要清楚自己想要的是什么,抽象出来的概念才不会变成垃圾。大而全的类,远远不如几个小而专的细类。java,C#下的类开发很方便,但是粒度过大,把一揽子的东西都丢给你,强卖强买,反正只要类一定义,必然相应的就会出现一大坨完善的反射信息,而对象里面也包含了一些无关紧要的成员字段,而对象的访问,也全部都是间接引用的访问,虽然,现在计算机性能过剩,这些都无伤大雅。c++给了开发者最大的选择,而搞c++的猿猴,基本上都智力过剩,对于每种选择,都清楚其背后的代价以及所要到达的目的,所以虽然开发时候,存在心智包袱影响开发效率,但是,但内心就不会存在什么性能包袱的负罪感。就个人而言,还是喜欢c++这种最高自由度的语言,有时候,对于内存最细致的控制,可以得到更精简的设计,这里无关运行性能,好比说,在c++中,只要内存布局一致,即便是不同类型的对象,通过强制类型转换来统一对待,进而做匪夷所思之事,好比COM里面,为了聚合复用,一个类,竟然可以针对同一个接口提供两套实现方式。这种方便,在其他强类型语言中是不支持的。

某种意义上讲,c++在面向对象上提供的语言机制,就是为了方便地生成各种内存布局,以及此内存布局上所能支持的操作,虚函数用以生成一堆成员函数指针,继承则用以方便地生成一坨成员字段,……。所以,c++的面向对象就是面向内存布局地设计,而多继承、虚继承、模板这些鬼东西很容易就导致内存布局的失控,不过,如果使用得当,却又有鬼斧神工之奇效,创造出来其他语言所没有的奇迹。真的,论动态行为艺术,任何语言在c++这个大人面前都是幼儿园里的小学生。

为了引出接口,本座花大力气做科普。这也没办法,因为类虽然是基础,但是静态面向对象的精华,全部都在接口上。只有清晰明确类的功能职责,才能理解接口的必要性以及其多样性。那么,可不可以只有接口,没有类的。可以,就好像com那样子,而代价是,使用起来,各种不方便。这个世界,从来就不存在包治百病之万能药。什么事情都能做的意思就是什么都做不好。

(0)

相关推荐

  • 分享一下8年C++面向对象设计的经验体会

    六年前,我刚热恋"面向对象"(Object-Oriented)时,一口气记住了近十个定义.六年后,我从几十万行程序中滚爬出来准备写点心得体会时,却无法解释什么是"面向对象",就象说不清楚什么是数学那样.软件工程中的时髦术语"面向对象分析"和"面向对象设计",通常是针对"需求分析"和"系统设计"环节的."面向对象"有几大学派,就象如来佛.上帝和真主用各自的方式定义了这个

  • 关于C++面向对象设计的访问性问题详解

    前言 最近在看Scott Meyers大神的<Effective C++>和<More Effective C++>,虽然这两本书都是古董级的教参了(当然针对C++11/C++14作者所更新的<Modern Effective C++>英文已经发售了,不过还没中文翻译版本),但是现在看来仍然收益匪浅,而且随着对这个复杂语言了解的深入和实践项目经验的增加,很多东西和作者产生了一种共鸣,以前种种疑惑突然有种拨云雾而见天日.豁然开朗的感觉,也难怪被列为合格C++程序员之必读书

  • 剖析C++的面向对象编程思想

    面向对象的程序设计 面向对象编程(Object Oriented Programming,OOP,面向对象程序设计) 的主要思想是把构成问题的各个事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙一个事物在整个解决问题的步骤中的行为. 面向过程就是分析出解决问题所需要的步骤,然后用函数逐步实现,再依次调用就可以了. 面向对象和面向过程是两种不同的编程思想,没有哪一种绝对完美,要根据具体需求拟定开发方案.例如,开发一个小型软件或应用程序,工程量小,短时间内即可完成,完全可以采用面

  • c++ 面向对象的类设计

    类的设计在于用恰到好处的信息来完整表达一个职责清晰的概念,恰到好处的意思是不多也不少,少了,就概念就不完整:多了,就显得冗余,累赘,当然特例下,允许少许的重复,但是,这里必须要有很好的理由.冗余往往就意味着包含了过多的信息,概念的表达不够精准,好比goto,指针,多继承这些货色,就是因为其过多的内涵,才要严格限制其使用.好像,more effective c++上说的,class的成员函数,应该是在完整的情况下保持最小化.但是,这里我们的出发点,是成员数据的完整最小化. 最小化的好处是可以保持概

  • Django中的模型类设计及展示示例详解

    django中设计数据模型类是基于ORM的对象关系映射更方便的进行数据库中的数据操作. 对象关系映射 把面向对象中的类和数据库表--对应,通过操作类和对象,对数表实现数据操作,不需要写sql,由ORM框架生成 django实现了ORM框架,在项目中与数据库之间产生桥梁作用 django数据库定义模型的步骤如下: python manage.py makemigrations python mange.py migrate 在应用models.py中编写模型类,继承models.Model类 在模

  • java面向对象编程类的内聚性分析

    目录 类划分时关于内聚性的问题 静态类的设计 高内聚类的设计 附:面向过程编程中模块的内聚性 偶然内聚或巧合内聚(Coincidental) 逻辑内聚(Logical): 时间内聚(Temporal ): 过程内聚: 通信内聚(Communicational): 顺序内聚(Sequential): 功能内聚(Functional): 类划分时关于内聚性的问题 静态类的设计 在软件设计中,我们经常会将一些通用的方法封装到一个类中,这种类只包含方法,没有属性,类中的方法之间没有关联,内聚性最低,属于

  • 浅谈python中的面向对象和类的基本语法

    当我发现要写python的面向对象的时候,我是踌躇满面,坐立不安呀.我一直在想:这个坑应该怎么爬?因为python中关于面向对象的内容很多,如果要讲透,最好是用面向对象的思想重新学一遍前面的内容.这个坑是如此之大,犹豫再三,还是只捡一下重要的内容来讲吧,不足的内容只能靠大家自己去补充了. 惯例声明一下,我使用的版本是 python2.7,版本之间可能存在差异. 好,在开讲之前,我们先思考一个问题,看代码: 为什么我只创建是为 a 赋值,就可以使用一些我没写过的方法? 可能会有小伙伴说:因为 a

  • PHP面向对象程序设计类的定义与用法简单示例

    本文实例讲述了PHP面向对象程序设计类的定义与用法.分享给大家供大家参考,具体如下: <?php class Person { private $name; private $sex; private $age; function __construct($name = "", $sex = "男", $age = 22) { $this->name = $name; $this->sex = $sex; $this->age = $age;

  • 浅谈javaSE 面向对象(Object类toString)

    每一个对象,都有一个在内存中的地址哈希值,这个哈希值是十六进制的 调用Object对象的hashCode()方法,返回这个对象的哈希值 调用Integer.toHexString()方法,转换十六进制 调用Object对象的toString()方法,得到:类名@哈希值 通常我们会复写toString()方法,因为默认的没有太大意义 实现原理是类的反射 当我们创建一个对象,会在硬盘上生成xxx.class的文件,jdk定义了Class类来描述这些class文件 调用Object对象的getClas

  • Python面向对象class类属性及子类用法分析

    本文实例讲述了Python面向对象class类属性及子类用法.分享给大家供大家参考,具体如下: class类属性 class Foo(object): x=1.5 foo=Foo() print foo.x#通过实例访问类属性 >>>1.5 print Foo.x #通过类访问类属性 >>>1.5 foo.x=1.7 #只改新实例属性,不会改变类属性 print foo.x >>>1.7 print Foo.x >>>1.5 foo.

  • Python面向对象程序设计类的多态用法详解

    本文实例讲述了Python面向对象程序设计类的多态用法.分享给大家供大家参考,具体如下: 多态 1.多态使用 一种事物的多种体现形式,举例:动物有很多种 注意: 继承是多态的前提 函数重写就是多态的体现形式 演示:重写Animal类 第一步:先定义猫类和老鼠类,继承自object,在其中书写构造方法和eat方法 第二步: 抽取Animal父类,定义属性和eat方法,猫类与老鼠类继承即可 第三步: 定义人类,在其中分别定义喂猫和喂老鼠的方法 第四步:使用多态,将多个喂的方法提取一个. # 测试类

  • Python面向对象程序设计类的封装与继承用法示例

    本文实例讲述了Python面向对象程序设计类的封装与继承用法.分享给大家供大家参考,具体如下: 访问限制(封装) 1.概念 面向对象语言的三大特征:封装, 继承, 多态. 广义的封装: 类和函数的定义本身就是封装的体现. 狭义的封装:一个类的某些属性,不希望外界直接访问,而是把这个属性私有化[只有当前类持有],然后暴露给外界一个访问的方法. 封装的本质:就是属性私有化的过程. 封装的好处:提供了数据的复用性,保证了数据的安全性. 举例:插排 2.使用 class Person(object):

  • python3 面向对象__类的内置属性与方法的实例代码

    0.object类源码 class object: """ The most base type """ def __delattr__(self, *args, **kwargs): # real signature unknown """ Implement delattr(self, name). """ pass def __dir__(self): # real signatu

随机推荐