引言
遥想当年,刚学设计模式的时候,我们做的最多的就是抽象成一个对象, 设计出漂亮的面向对象的继承体系。
虚函数,重写,重载,各种手段。恨不得把里面的每个概念,都封装一下,抽象一下。恨不得每个概念都要高度的可重用,就会把一个概念抽象成几个层次。
1: 从代码维护的角度来说,过多的抽象增加代码维护难度。
比如我们游戏中的子弹,有各种类型的子弹,有各种威力的子弹, 很多我们往往设计的时候, 先抽象子弹基类,然后各种子弹再继承这个基类, 子弹---》炸弹---》超级炸弹, 你会发现体系越来越复杂。
过多的抽象,会导致每个概念分成了好几个层次,出来庞大的继承体系,我相信没有一个人喜欢庞大的继承体系,维护代码的时候,一个类,继承了几层,你维护的时候要跳转好多次,这个给维护带来很多难度,同时,如果以前的抽象不那么漂亮的时候,你所有的代码都围绕这样的数据对象,漂亮的展开,你要修改,也会带来很多的烦恼,要重写所有的相关代码才可以。
我们抽象一个体系出来的目的是为了什么?先要想清楚这个问题。可能不同的人会有不同的答案,比如我抽象出来,可以减少代码的重复编写,提升可重用性。比如,男人,女人,我先抽象一个人的对象,然后在继承男人,女人。这样,人的代码我抽象出来,男人可以共用人的,女人可以共用人的。
看上去很正确,对吧?
但是如果换一个角度,我就写一个人的代码,然后加一个数据成员sex, 来决定这个人是男人还是女人。
这样也可以哈,我就只有人这个概念,同时sex,来表示不同实例的数据,到底用哪个呢?
也就是说我们抽象的目的是什么?
我们抽象的目的,是为了完成既定的任务与功能,达到项目的需求, 让,让开发协作变简单,而不是构建一个漂亮的体系。完成对应的功能,概念造的越少越好, 越简单越好。
比如,我们刚才说的子弹,我们还可以这样设计,子弹动画,子弹数据属性,都可以通过数据配置。子弹的控制逻辑是一个类型Bullet,初始化的时候,吃入 动画数据+游戏数值数据(攻击力等), 即使你不抽象也能很真实的解决好对应的问题,而不用维护抽象出来的庞大的继承体系和关系。
当我们每次动笔,要开始抽象的时候,我们要问自己,这么做到底的原因是什么?有没有不抽象更简单的做法,更真实的做法。
真实性很重要,因为真实性,直接的解决问题,大家维护起来会更方便。
同时抽象层次不宜过多, 还是那句,linux内核驱动,那么多设备,一个文件机制就搞定了。真正的可扩展性其实就是源于简单,只有简单才可以推广,相反复杂的继承体系,可扩展性未必就好。
所以我们做设计,本质就从问题出发,从解决问题自然而衍生对应的设计,而不是从漂亮的抽象开始。
2: 从程序性能成面上来说,过多的抽象,其实是不好的。
举个例子, 你编写了人类(数据成员+方法), 男人(数据成员+方法), 你很难清晰的知道男人这个数据结构到底占了多少内存(虚函数表等看不见的内存开销),在内存数据里面如何布局的(内存是否对齐)。
这些是编写高性能代码的关键,所以你发现,ECS这种高性能出发的设计,就把数据都放一块。这样方便控制CPU的内存cache, 地址对齐, 分配释放等。大量的数据对象,如果你过多的抽象,越来越看不清楚数据的布局和结构,极不利于编写高性能的代码。
3: 过多的抽象层次,容易造成内存泄露。
过多的抽象,就会导致,抽象体系内各个类的构造函数被调用,释放对象的时候,会导致各个层次抽象类析构函数被调用,由于层次过多,每个层次维护自己的释放,给维护对象的数据内存带来很搞的复杂度,这个就是为什么很多程序员写代码容易内存泄露,也许在一开始开发机制就给你埋下了一颗雷。想反的,直接数据对象和结构,能跟清楚的维护内存的分配与释放。
比如ECS里面,释放掉Entity就可以了。因为数据都很清晰的排列在每个entity里面。
最后
总之, 尽量避免过度的抽象和减少抽象的层次,尽量做到数据+方法来处理对应的问题,真实的解决项目问题,将简单胜于花哨。