入职程序员做c++的,面向对象设计能力有些欠缺,最近想了一些记下来。这些想法未必是对的,但是也算是自己的一点思考,希望有经验的同事积极批评指正。
面向对象的设计分为两方面,基础类的设计和业务类的设计。
基础类分为和业务无关的基础类和和业务相关的基础类。举个例子,商场货物的管理系统,像文件读写、数据库增删改查、网络访问、日志系统等都是和业务无关的类,像商场下有哪些商店、商店下包含哪些仓库、哪些货架等、货架内有什么商品等都是和业务有关的类。
和业务无关的类一般可以在在各个项目中通用,类和类之间的关系并没有特别复杂,类的接口设计的越简单越好。和业务相关的类就相对复杂,只能用在当前的项目中。
类分为属性和方法。属性是对外提供的这个类描述的“性质”,方法是对外提供的功能。例如文件类
class File{
public:
string filename;
int read(char**,int&);
int write(char*,int);
}
例如文件名就是类的属性,读和写是方法。当然,有些和文件相关的属性并不是那么容易获取的,例如文件的创建日期,文件的扩展名等,这些本是属性,但需要方法获取,可以成为方法属性。在C#里面,这一类的属性可以使用get\set更好的表示,并且它告诉别人,这也是属性。
public class File{
//属性:文件名(可读、可写)
public String FileName{
get{ return m_filename; }
set{ m_filename = value }
}
//属性:创建日期(只读)
public String CreateTime{
get{ ... }
}
private String m_filename;
}
在c++和Java里面,并没有c#属性的概念,只能使用get和set函数进行封装。但是在设计这个类的时候,要有和c#一样的思维,这个get是要获取这个属性,get和set本身并不应当看成类的方法。
在设计一个类的时候,要注意类对外需要提供什么属性和什么方法,对内不管是怎么设计的,对外都是不会被关注到。
类之间是有包含关系的,这个关系并不一定对编程有什么帮助,但是它却可以客观的反映现实世界。还是上面商场的例子:
Shops(商场){
Shop(子分店){
StoreHouse(仓库){
Goods(商品){}
}
Shelves(货架){
Goods(商品){}
}
}
}
商场和子公司是属于包含和被包含的关系,子公司和仓库、货架是属于包含和被包含的关系。像这种关系是自然概念确定的包含关系,和具体的业务也是没有关系的。但是不管是什么业务,类的设计上也需要去体现出这种包含和被包含的关系。
下面改说说具体的业务类。假如需求上描述,供应商获取一定的的某种商品,我需要去自动管理将这种商品分配到哪个子分店里,是放置到具体的货架上还是在保存到仓库里,这需要一定的分配策略,像根据各个子分店的购买力、商品剩余等等。这相相当于一个需求。这个过程是最好封装成一个类,因为这个过程涉及一些公共变量可以为类存储。
在设计类的时候,需要考虑到类需要对外提供哪些属性,外部的输入可以作为设计属性的重要依据,各个过程可以作为方法的依据。
class GoodsAllocation{
public:
bool setGoods(const Good& good);
bool setShops(const Shops& shops);
void getInfo();
void calc();
void push();
}
商品分配类需要知道被分配的商品,需要知道商店的信息,步骤可能分成三步,先需要获取商店的信息到本地(可能涉及到数据库、网络等操作),计算分配,将分配好的结果保存到数据库或服务中。
这样的话,这个业务就被封装在一个类中了。当需求发生变化的时候,例如我需要修改分配的逻辑,就可以直接修改GoodsAllocation::calc方法。
我曾听过一个人说过这样一句话,当一个组件(类)提交给测试通过后,这个类便不再和程序员有任何的关联了,如果有变化,则需要重写开启一个研发任务。在需求如此变化的今天,一次性完成所有功能几乎是不太可能,但是往往改动的是业务,而非基础类,即基础类一旦提交测试,几乎就不太有改动的可能了,但业务类很有可能还是会变化的。