敏捷软件开发宣言
个体和交互 胜过 过程和工具
可以工作的软件 胜过 面面俱到的文档
客户合作 胜过 合同谈判
响应变化 胜过 遵循计划
敏捷设计原则:
单一职责原则(The Single Responsibility Principle,简称SRP);
开发-封装原则(The Open-Close Principle,简称OCP);
Liskov替换原则(The Liskov Substitution Principle,简称LSP);
依赖倒置原则(the Dependency Inversion Principle,简称DIP);
接口隔离原则(The Interface Segregation Interface,简称ISP);
单一职责原则(SRP):
就一个类而言,应该仅有一个引起它变化的原因。
开放-封装原则(OCP):
软件实体(类,模块,函数等)应该是可以扩展的,但是不可修改的。
1.对于扩展是开发的
这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。换句话说,我们可以改变模块的功能。
2.对于更改是封闭的
对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进可执行版本,无论是可链接的库、DLL或者Java的.jar文件,都无需改动。
怎么可能在不改动模块源代码的情况下去更改它的行为呢?关键是抽象!
public class Square(){ public String type="square"; } public class Circle(){ public String type="circle"; } public class DrawShape{ //list为所有要画的shape的集合 public void DrawAllShapes(ArrayList<Object> list){ for(int i=0;i<list.size();i++){ switch(s.type){ case "square": DrawSquare(list.get(i));break; case "circle": DrawCircle(list.get(i));break; } } } private void DrawSquare(Object o){} private void DrawCircle(Object o){} }
DrawAllShapes函数是要画出所有图形,先检查图形的类型,在判断是调用DrawSquare还是DrawCircle。该函数显然是不符合OCP,因为它对于新的形状类型的添加不是封装的。如果希望这个函数能够绘制包含有三角形的列表,就必须得更改整个函数。而要解决这个问题,就可以使用抽象
public interface Shape{ public void Draw(); } public class Square implements shape{ public void Draw(){System.out.println("Draw square");} } public class Circle implements Shape{ public void Draw(){System.out.println("Draw circle");} } public class DrawShape{ public void DrawAllShapes(ArrayList<Shape> list){ for(int i=0;i<list.size();i++){ i.draw(); } } }
这样想添加新图形时,只要创建这个图形的类,并implements Shape接口,并实现Draw()方法即可,无需再改动DrawAllShapes方法。它也就符合OCP了。
Liskov 替换原则(LSP)
子类型必须能够替换掉它的基类型。
正方形并不是个矩形!!!
class Rectangle{ private double width; private double height; void setWidth(double w){width = w;} void setHeight(double h){height = h;} double getWidth(){reutrn width;} double getHeight(){return height;} double Area(){ return width * height; } }
正方形应该是一个矩形,所以Square应该派生自Rectangel类。不过,Square并不需要成员变量width和height。但仍会继承它们,显然这是种浪费,虽然在许多情况下这种浪费是可以忽略的,但setWidth()和setheight()这种个函数对于Square来说是不适合的,但Square仍会继承它们。当然,为了避免这个问题,我们也可以写:
class Square extends Rectangel{ void setWidth(double w){super.setWidth(w);super.setHeight(w);} void setHeight(double h){super.setHeight(h);super.setWidth(h);} }
这样就可以保证这个正方形的长和宽是一样的了。但对于下面这个问题
void g(Rectangle r){ r.setWidth(5); r.setHeight(4); assert(r.Area()==20); }
这本来是很正常的,但当r是个正方形是,这个assert就会是错误的,因为当最后调用r.setHeight(4)时,长宽都为4,Area()得到的结果就是16!所以Square并不能完全替换Rectangel,也就是说,它不符合LSP原则。
IS-A是关于行为的
对于那些不是g测试方法的编写者而言,正方形当然是个矩形,但从g的角度来看,Square对象绝对不是Rectangel对象。因为Square对象的行为方式和函数g所期望的Rectangle对象的行为方式不相容。不行为方式的角度来看,Square不是Rectangle。
对象的行为方式才是软件真正所关注的问题。
依赖倒置原则(DIP)
a.高层模块不应该依赖于低层模块。二者都应该依赖于抽象。
b.抽象不应该依赖于细节,细节应该依赖于抽象。
这是一个违反DIP原则的例子,高层的PolicyLayer 使用了低层的MechanismLayer,而MechanismLayer又使用了更低层的UtilityLayer。那么UtilityLayer一些很小的改动都将影响高层的PolicyLayer。而低层的类又是容易改变的,为了解决这个问题,我们可以将这些类低速于抽象。每个较高层次都为它所需要的服务声明一个抽象接口,较低的层次实习了这些抽象接口,每个高层类都通过该抽象接口使用下一层,这样高层就不依赖于低层。低层反而依赖于在高层中声明的抽象服务接口。
1.任何变量都不应该持有一个指向具体类的指针或者引用。
2.任何类都不应该从具体类派生
3.任何方法都不应该复写它的任何基类中的已经实现了的方法。
一个简单的例子
public class Button{ private Lamp lamp; public void poll(){ if(/*some condition*/) lamp.turnOn(); } }
这个方案违反了DIP,Button类直接依赖于Lamp类,当Lamp改变时,高层的Button类也会受到影响,此外,想重用Button类来挖掘一个Motor对象是不可能的。在这个设计中,Button控制着Lamp对象,并且也只能控制Lamp对象。
找出潜在的抽象
这样,Lamp依赖于ButtonServer,但ButtonServer没有依赖于Button,任何知道如果去操作ButtonServer接口的对象都能够控制Lamp。
接口隔离原则(ISP)
接口应该是高内聚的,一个接口应该尽量只为一种客户服务,而不是为了几个客户,否则,就会出现接口污染。
不应该强迫客户依赖于它们不用的方法。
如果强迫客户程序依赖于那些它们不使用的方法,那么这些客户程序就面临着由于这些未使用方法的改变所带来的变更。这无意中导致了所有客户程序之间的耦合。换句话说,如果一个客户程序依赖于一个含有它不使用的方法的类,但是其他客户程序却要使用该方法,那么当其他客户要求这个类改变时,就会影响到这个客户程序。我们希望尽可能地避免这种耦合,因此我们希望分享接口。