程序很大程度上是对现实世界的模拟,即通过代码来建立模型,然后根据现实世界的处理方式来对模型进行操作。这样在以前的面向过程语言中,数据的组织和处理方式的组织都是零散的,各顾各这样的,而且特别不适应变化,比如要给数据添加一个项或给算法添加一个环节,这样可能都会造成大范围的改动。而在面向对象语言中,因为数据和处理都是被一体化地封装到类中的,而且类支持继承,重写等一些特性,如果处理方式发生变化,就可以使用重载来完成,如果数据发生变化就可以使用继承来进行扩充,所以面向对象语言在适应变化的特性上也就比面向过程更有优势。而类作为一个处理和数据的综合,来代表对现实世界的模拟,比如Dog类就代表所有的狗,而Labrador、Husky则是从Dog类继承而来的分别代表拉布拉多和哈士奇的子类,这样通过类也就是一个用程序表示的事物的模板,而具体如你家的小狗这样的就是对象,也就是一个模板的具体化。所以总结类和对象的概念也就是模板和模板实例化的关系。解释了类和对象关系后,就来看看怎么设计类。
首先看看面向对象有什么特性,以及这些特性带来的好处,具体如下:
1. 抽象:添加类时肯定是对某一类事物的程序化,也就是提取出影响的属性和涉及到的操作特性,然后用程序来抽象表达;
2. 封装:因为类是既含有数据,又含有方法的,所以为了避免不必要的数据改动和方法调用,这样就要根据数据和方法的特性来选择不同的可见性,从而达到封装的目的;
3. 继承:类之间是可以有层次化的,可以把一些共有的东西放到基类中,而特异性的则放到派生类中,这样做首先可以提高代码的复用率,其次可以提高代码适应变化的能力;
4. 多态:如继承中所说,在派生类中是分别包括各自的特异性实现的,这样当使用基类引用变量调用派生类对象的方法时,如果方法时重写了基类的方法的话,实际上调用的就是派生类的方法,即同样的调用是根据当前对象不同,会触发与当前对象绑定的方法,
根据以上特性面向对象设计产生了一些基本原则的,具体如下:
1. Open-close principle(OCP),开发封闭原则,即设计应该是对扩展开放,而对修改关闭。一般软件代码都会涉及到很多代码,这样如果在一个继承体系中,修改了基类的接口或者给基类增加了成员,这样派生类可能都要发生改动;而如果同样的要求,是重新添加一个派生类,然后在派生类中实现需要的改动,这样就不会影响现有代码。按照这样设计软件即提高了系统的稳定性,有提高了系统的扩展性;
2. Liskov Substituition Principle(LSP),里氏替换原则,在继承层次中,派生类对象应该和基类对象保持共性,即派生类对象可以被完全当做基类对象来使用,在设计调用该类的代码的时候,就可以使用基类的引用来完成算法,而实际上基类引用的可能是派生类对象,这样算法实现也就和派生类解耦了,如果派生类有增加或修改都不会影响算法的运行流程;
3. Dependence Inversion Principle(DIP),依赖倒转原则,即要依赖于抽象,不要依赖于具体。很多时候代码是固定而,而现实是不断改变的,如果在实现代码的基础结构时,如果前期搭建的结构时,系统调用的对象都是一些数据和方法都比较具体的类,这样后面发生变化时,可能就会有比较多改动,而如果调用的对象都是只管对象的相关接口,而不涉及对象的数据和方法的实现,这样代码的结构上就相对更稳定;
4. Interface Seregation Principle(ISP),接口隔离原则,一个类对另外一个类的依赖性应当是建立在最小的接口上的。通常一个类都是含有很多方法的,但调用的时候可能只会调用这个类的某些方法,而在其他地方又值调用这个类的另一些方法,所以这样就要根据这个类的使用场景来将这个类拆分成一个个的接口,这样对该类的改动就会缩减对其他地方的影响,然后只用检查改动涉及的接口是否会影响其他地方即可;
5. Composite/Aggregate Reuse Principle(CARP),合成/聚合复用原则,要尽量使用合成/聚合,尽量不要使用继承。在继承层次中,比如如果基类要增加一个抽象方法,或者接口要增加一个方法,这样派生类或实现类都要实现该方法,这样就会涉及很多改动,而使用合成/聚合,这样将原本的派生类设计成一个含有原本基类的引用变量的类,然后在其类内部,如果要涉及到对原本基类方法或属性的使用就直接调用就行,这样基类即便有变化,只要注入的基类对象正确,就能进行正确的处理;
6. Law of Demeter(LOD),迪米特法则,对象间应该尽可能少的互相了解,即对象间应互为黑盒,而只是利用提供的接口进行调用通信,这样不仅使得类之间不会发生实现上的耦合,也减少了接口上的耦合;
这些设计原则不是互相独立的,设计类的时候要整合以上原则来设计,而这些也是前人总结的经验,正所谓,他山之石,可以攻玉,借鉴前人的经验总是没错的。