当前流行的软件架构,无论理论多么的高深,思想多么先进,最终都会落实到一个个的功能类上面去,当前我们对编程的思考也都是围绕类来进行的。可以不夸张的说一个开发人员对类的设计能力就决定了他总体的代码设计能力。在面试的时候我都会连续问三个问题,什么是类,你是如何设计编写一个类的以及如果让你review代码你是如何判定一个类的好坏的。我觉的能把这三个问题说明白也就把面向对象编程理解的差不多了。
要想搞明白类是什么,需要先明白一个概念:抽象数据类型(ADT,Abstract Data Types)它是指一组数据以及对该组数据操作的集合。数据对应到类里面就是一组变量,可以一个文件也可以是一个List也可以是一个简单的数字类型。操作就是指函数,对该组数据操作的函数,这是最关键的地方所有的函数都是要围绕一组数据进行的。一个ADT就对应一个类,对照这个概念我们就是可以去抠一下自己之前写的类的,符合这个概念的就是类,不符合的充其量就是个用class修饰起来的代码集而已。
为了更方便的理解ADT,这里先举一个例子。假设要用类去描述一个商品,先列一下商品所对应的数据以及可能的操作。
价格、库存、销售、进货.......
对应到类里面我们可能把价格和库存看成数据,销售和进货看成操作。类会提供set/get函数来供第三方修改/查看价格。库存只会提供一个get函数,它的修改只能在销售或进货函数的里进行处理。
如果按照这个模式先抽象再编程的话相对于直接随性发挥去拼凑一个类来说在代码变现的效率上确实要低很多。但是它带来的好处也是显而易见的。
首先它保证类职能的单一性,也就是高内聚。它能为类提供很高的可维护性。当因为需求变化或者技术进步而去修改某一个功能的时候,只需要少量的代码关联修改就可以了,进而也会省去很多的关联测试。例如上面的商品我们需要对最低价格进行限制的时候,如果提供了set函数,只需要在这一个地方进行判断就能实现需求,否则可能就需要找出所有赋值的地方进行修改判断了。
其次对数据进行抽取后,可以免去在类内函数与函数之间的参数传递。整个类都是围绕这组数据操作的,操作的结果直接体现到类变量上去就行了。
另外围绕数据的操作的特性也可以使对外提供的接口更加简单明了。第三方只要通过调取函数来发送对数据的读取和操作指令就可以了,所有的操作实现都是在类内部实现。大幅的降低了类于类之间的耦合性。
整个文章说到现在,其实都是在围绕ADT来说的。其实明白了ADT也就知道如何去封装一个类了,它的的步骤也和上一篇文章《浅谈架构——面向对象》里对象的抽取设计基本一致。
1、抽取数据,定义类内全局变量及构造函数(变量的初始化尽量在构造函数里面进行)
2、定义公共函数/对外接口(对于绝大部分的模块内功能类来说不建议使用接口)
3、实现功能抽取封装私有函数
4、按照一定的规则对代码优化,例如降低函数或数据的开放等级,一个函数不要超过一屏,类内变量不要超过7个,如果需要对类全局变量复制的话优先采用深拷贝,除非特殊需求才进行浅拷贝,增加注释等。
除了上面的步骤以外还想列一下自己在封装类的时候所采用的一些原则或方法。说这些方法之前先强调一个前提,我认为除了model类以外绝大部分的类可以划分为两种,一种是功能类另一种是操作类。举个例子说明一下什么是功能类,什么是操作类。例如应用系统里常用的Excel文档下载解析功能。这里的Excel下载就是功能类,Excel的解析就是操作类。
1、Excel下载的数据和操作
下载路径
存储路径
文件名称
文件下载
文件保存
2、Excel解析的数据和操作
文件对象
文件打开
文件头(一般是第一行)
单元格数据解析
下一行
对于操作类来说一定要将接口和实现类分开,最好是先定义接口,再写实现类。而且很有可能它的实现类不止一个,例如上面的Excel解析,不止Excel,CSV、XML、TXT甚至泛型列表的操作也是可以抽象成打开、文件头、单元格、下一行这些操作的。如果遇到这种情况对于他们的调用最好建立一个简单工厂,由工厂根据参数来决定实例化那个类。
对于功能类来说最好不要去定义接口,需要抽取的话建议使用抽象类。拿上面的Excel下载来说,虽说文件下载可能是不同的方式像http、ftp、共享目录。但对下载后文件的保存,文件名字的读取,文件路径的生成这些是可以抽取出来的。
对于功能类来说,最好采用命令模式,既一个类只包含构造、exe两个公开函数和一组数据的get函数。为什么要这样做那,其实功能类里面所有的子函数都是围绕一个核心功能展开的,第三方调用这个函数也是为了实现这个功能才调用的。构造函数负责数据的初始化,exe负责功能的实现。这样写的好处就是让这个类有一个中心控制点,从exe开始一抵溜一串,简单明了。从功能扩展修改、代码阅读跟踪、断言的增加以及异常处理各个方面来说都是很好的。在加注释的时候也可以在exe函数上详细描述调用的步骤。
还是对于功能类来说,所有的数据最好都定义成私有变量,并且只提供一个get函数。除非特殊情况才考虑提供一个set函数用于变量的赋值。
最后再强调一遍的类内全局变量的初始化尽量在构造函数内完成。这是一个很好的防御式编程的实践,另外一个好的防御式编程的实践就是刚才提到的断言。
PS:文章的大标题叫”浅谈架构“,这里却写了类的实现。在文章开头也说了再好的架构也是要落实到类这个层面上的。类写不好的话在好的架构也不会起到好的效果的。接下来的文章计划从设计模式开始、在延伸到应用层面的架构设计,最后再说一下我对产品线架构设计的理解。中间可能会穿插着我自己工作或者学习中所用到的一些设计实例。大家如果有什么好的实例的话也可以发到我的邮箱modify961@126.com一块讨论。谢谢了。
网友评论