2、如何实现代码的高质量?
遵循SOLID设计原则:(接口设计原则)参考依据高内聚、低耦合
单一职责原则:一个类值负责一个功能的职责
开闭原则:扩展开放,修改关闭。
里氏代换原则:使用父类的地方都能使用子类对象
依赖倒转原则:针对接口编程,
接口隔离原则:针对不同部分用专门接口,不用总接口,需要哪些接口就用哪些接口
你认为下图的设计违反了哪一种设计原则?
(1)违反了单一职责原则(SRP)
画图和计算面积并不是单一职责,计算几何学应用程序只计算面积不画图,但是还要引入GUI。
应该有且仅有一个原因引起类的变更。简单点说,一个类,最好只负责一件事,只有一个引起它变化的原因。也就是说引起类变化的原因只有一个。高内聚、低耦合是软件设计追求的目标,而单一职责原则可以看做是高内聚、低耦合的引申,将职责定义为引起变化的原因,以提高内聚性,以此来减少引起变化的原因。职责过多,可能引起变化的原因就越多,这将是导致职责依赖,相互之间就产生影响,从而极大的损伤其内聚性和耦合度。单一职责通常意味着单一的功能,因此不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。
(2)开闭原则
对扩展开放。模块对扩展开放,就意味着需求变化时,可以对模块扩展,使其具有满足那些改变的新行为。换句话说,模块通过扩展的方式去应对需求的变化。
对修改关闭。模块对修改关闭,表示当需求变化时,关闭对模块源代码的修改,当然这里的“关闭”应该是尽可能不修改的意思,也就是说,应该尽量在不修改源代码的基础上面扩展组件。
一个开闭原则的简单实例(懂则不用看)
对拓展开放,对修改关闭:比如当某个业务增加,不是在原类增加方法,而是增加原类的实现类。
下面的例子是一个非常典型的开闭原则及其实现。非常简单,但却能够很好的说明开闭原则。
假设有一个应用程序,能够计算任意形状面积。这是几年前我在明尼苏达州农作物保险公司遇到的一个非常简单问题。app程序必须能够计算出指定区域的农作物总的保险报价。正如你所知道的,农作物有各种形状和大小,有可能是圆的,有可能是三角形的也可能是其他各种多边形。
OK,让我们回到我们之前的例子中....
作为一名优秀的程序员,我们将这个面积计算类命名为 AreaManager。这个 AreaManager是单一职责的类:计算形状的总面积 。
假设我们现在有一块矩形的农作物,我omen用一个Rectangle类来表示。相关类代码如下:
public class Rectangle { private double length; private double height; // getters/setters ... } public class AreaManager { public double calculateArea(ArrayList<Rectangle>... shapes) { double area = 0; for (Rectangle rect : shapes) { area += (rect.getLength() * rect.getHeight()); } return area; } } AreaManager类现在运行良好,直到几周之后,我们又有一种新的形状——圆形: public class Circle { private double radius; // getters/setters ... } 由于有新的形状需要考虑,我们必须修改我们的AreaManager类: public class AreaManager { public double calculateArea(ArrayList<Object>... shapes) { double area = 0; for (Object shape : shapes) { if (shape instanceof Rectangle) { Rectangle rect = (Rectangle)shape; area += (rect.getLength() * rect.getHeight()); } else if (shape instanceof Circle) { Circle circle = (Circle)shape; area += (circle.getRadius() * cirlce.getRadius() * Math.PI; } else { throw new RuntimeException("Shape not supported"); } } return area; } } 从这段代码开始,我们察觉到了问题。 如果我们遇到一个三角形,或者其他形状呢,这时候我们就必须一次又一次的修改AreaManager类。 这个类的设计就违背了开闭原则,没有做到对修改的封闭性以及对扩展的开放性。我们必须避免这种事情的发生~ 基于继承的开闭原则的实现 AreaManager类的职责是计算各种形状的面积,而每一种形状都有其独特的计算面积的方法,因此将面积的计算放入到各个形状类中是特别合理的。 AreaManager类仍然需要知道所有的形状,否则它就无法判断所有的形状类是否都包含了计算面积的方法。当然了,我们可以通过反射来实现。其实有一种更简单的方式也可以实现——让所有的形状类都继承一个接口:Shape(也可以是抽象类) public interface Shape { double getArea(); } 每一个形状类都实现这个接口(如果接口无法满足你的需求,也可以通过继承某个抽象类): public class Rectangle implements Shape { private double length; private double height; // getters/setters ... @Override public double getArea() { return (length * height); } } public class Circle implements Shape { private double radius; // getters/setters ... @Override public double getArea() { return (radius * radius * Math.PI); } } 现在,我们可以通过这个抽象方法将AreaManager构造成一个符合开闭原则的类。 public class AreaManager { public double calculateArea(ArrayList<Shape> shapes) { double area = 0; for (Shape shape : shapes) { area += shape.getArea(); } return area; } }
通过扩展去应对需求变化,就要求我们必须要面向接口编程,或者说面向抽象编程。所有参数类型、引用传递的对象必须使用抽象(接口或者抽象类)的方式定义,不能使用实现类的方式定义;通过抽象去界定扩展,比如我们定义了一个接口A的参数,那么我们的扩展只能是接口A的实现类。总的来说,开闭原则提高系统的可维护性和代码的重用性。