Agile Principles, Ptterns, and Practices in C#
Requirements
总结可以遵循的物理设计规则
《敏捷软件开发:原则、模式与实践》
敏捷开发
敏捷开发是一个过程,而不是一个事件。它是一个持续的应用原则、模式以及实践来改进软件的结构和可读性的过程
敏捷开发致力于保持系统设计在任何时间都尽可能的简单、干净以及富有表达力。
例:Clipping Region
S.O.L.I.D原则
Flexiblility
单一职责原则(SRP)
一个类应该只有一个发生变化的原因。
如果一个类承担的职责过多,就等于把这些职责耦合在了一起。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。
例:
// 调制解调器
public interface Modem
{
public void Dial(string pno);
public void Hangup();
public void Send(char c);
public char Recv();
}
Dail和Hangup函数负责连接管理;
Send和Recv函数负责数据通信
如果应用程序的变化方式总是导致这两个职责同时变化,那就不必分离它们。
开放-封闭原则(OCP)
软件实体(类、模块、函数等)应该是可以扩展的,但是是不可修改的。
对扩展开放:需求变化时,可以对模块扩展,使其满足那些改变的新行为;
对修改封闭:需求变化时,不应通过修改已有代码来实现变化。
抽象
例:
抽象类和他们的客户的关系要比和实现他们的类的关系更密切一些。
Liskov替换原则(LSP)
子类型必须能够替换它们的基类型。
例:
public class Rectangle
{
private Point topLeft;
private double width;
private double height;
public virtual double Width
{
get { return width; }
set { width = value; }
}
public virtual double Height
{
get { return height; }
set { height = value; }
}
}
public class Square : Rectangle
{
public override double Width
{
set
{
base.Width = value;
base.Height = value;
}
}
public override double Height
{
set
{
base.Height = value;
base.Width = value;
}
}
}
void g(Rectangle r)
{
r.Width = 5;
r.Height = 4;
if(r.Area() != 20)
throw new Exception("Wrong Area!");
}
继承是关于行为的
对于那些不是g的编写者而言,Square可以继承Rectangle;
但从g的角度来看,Square对象绝对不能继承Rectangle。因为Square的行文方式和函数g所期望的Rectangle对象的行文方式不相容。
对象的行为方式才是软件真正关注的。
鉴别是否违反LSP:使用运行时类型检查。
对于LSP的违反通常也潜在的违反了OCP
避免违反LSP的方法:
- 基于契约设计(Design By Contracts)
契约是通过为每个方法生命前置条件和后置条件来指定的。
要使一个方法得以执行,前置条件必须为真。执行完毕后要保证后置条件为真。 - 测试驱动开发
单元测试通过彻底的测试一个类的行为来使该类的行为更加清晰。 - 避免继承
提取公共部分的方法,使用对象组合而不是类继承。
例:
接口隔离原则(ISP)
不应该强迫客户程序依赖并未使用的方法
ISP原则用来处理胖接口所存在的缺点。
胖接口:不内聚的接口。这个接口可以分解成多组方法,每一组方法都服务于一组不同的客户程序。
分离接口
在维护面向对象的应用程序时,常常会改变现有的类和组件的接口。通常这些改变都会造成巨大影响,并且迫使系统的绝大部分需要重新编译和部署。
这种影响可以通过为现有对象增加新接口的方法来缓解,而不是去改变现有接口。
依赖倒置原则(DIP)
- 高层模块不应该依赖于底层模块。而这都应该依赖于抽象;
- 抽象不应该依赖于细节,细节应该依赖于抽象。
例:
接口所有权的倒置
倒置不仅仅是依赖关系的倒置,也是接口所有权的倒置
我们通常会认为工具库应该拥有他们自己的接口。但当应用了DIP时,我们发现往往是客户拥有抽象接口,而他们的服务者则从这些抽象接口中派生。