C#面向对象设计模式纵横谈——1.面向对象设计模式与原则


一:设计模式简介

每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。 ---- Christopher Alexander

软件设计领域设计模式: 设计模式描述了软件设计过程中某一类常见问题的解决方案。

面向对象的设计模式: 面向对象的设计模式描述了面向对象设计过程中,特定场景下,类与相互通信的对象之间常见的组织关系。


二: GOF 23种设计模式

历史性著作 《设计模式 : 可复用面向对象软件的基础》一书中描述了23种经典面向对象设计模式,创立了模式在软件设计中的地位。该书四位作者被人们并称Gang of four (GOF),“四人组”,该书描述的23种经典的设计模式被人们称为GOF 23种设计模式。

由于《设计模式 : 可复用面向对象软件的基础》一书确定了设计模式的地位,人们通常所说的设计模式隐含地表示“面向对象设计模式”。但并不意味“设计模式”就等于“面向对象设计模式”,也不意味着GOF 23种模式就表示所有“面向对象设计模式”。除了“面向对象设计模式”外还有其他的设计模式,除了GOF 23 种设计模式外还有更多的设计模式。

三:设计模式与面向对象

面向对象设计模式解决的是类与相互通信的对象之间的组织关系,包括他们的角色,职责,协作方式几个方面。

面向对象设计模式是“好的面向对象设计”,所谓“好的面向对象设计”是那些满足“应对变化,提高复用”的设计。

面向对象的设计模式描述的是软件设计,因此它是独立于编程语言的,但面向对象的设计模式最终实现仍然要用面向对象的编程语言来表达。

面向对象设计模式不像算法技巧,可以照搬照用,它是建立在对“面向对象”纯熟,深入的理解的基础上的经验性认识。掌握面向对象设计模式的前提是首先掌握“面向对象”。


四:从编程语言直观的了解面向对象

各种面向对象编程语言互相有别,但都能看到他们面向对象的三大机制,“封装”,“继承”,“多态”。
封装:隐藏内部实现
继承:复用现有代码
多态:改写对象行为


使用面向对象的编程语言,可以推动程序员以面向对象的思维来思考软件设计结构,从而强化面向对象的编程范式。

C#是一门支持面向对象编程的优秀语言,包括:各种级别的封装支持:单实现继承+多接口实现;抽象方法与虚方法重写。


五:OOPL并非面向对象的全部

通常面向对象的编程语言(OOPL)认识到的面向对象,并不是面向对象的全部,甚至只是浅陋的面向对象。

OOPL的三大机制“封装”,“继承”,“多态”可以表达面向对象的所有概念,但这三大机制本身并没有刻画出面向对象的核心精神。换言之,既可以用这三大机制做出“好的面向对象设计”,也可以用着三大机制做出“差的面向对象设计”。不是使用了面向对象语言就实现了面向对象的设计与开发。因此我们不能依赖编程语言的面向对象机制,来掌握面向对象。

OOPL没有回答面向对象的根本性问题——我们为什么要使用面向对象?我们应该怎样使用三大机制来实现“好的面向对象”?我们应该遵循什么样的面向对象原则 ?
任何一个严肃的面向对象的程序员,都需要系统地学习面向对象的知识,单纯从编程语言上获得的面向对象知识,不能够胜任面向对象设计与开发。

六:示例

示例场景:
我们需要设计一个人事系统,其中一个功能是对各种不同类型的员工,计算其当月工资——不同类型的员工,拥有不同的薪资计算制度。

结构化做法:

1.获得人事系统中所有可能的员工类型。

2.根据不同员工类型所对应的不同薪资制度,计算工资。


enum EmployeeType {
Engineer;
Sales;
Manager;
..........
}

//薪资计算

if(type==EmployeeType.Engineer)
{
.............
}
else if(type==EmployeeType.Sales)

{
.............
}
.............


面向对象设计做法:


1.根据不同员工类型设计不同的类,并使这些类继承自一个Employee抽象类,其中有一个抽象方法GetSalary。

2.在各自不同的员工类中,根据自己的薪资制度,重写(override)GetSalary方法。

abstract class Employee{
...
public abstract float GetSalary();

}

class Engineer:Employee{
...
public override float GetSalary(){

}
}

class Sales:Employee{
...
public override float GetSalary(){

}
}

//显示薪资程序

Employee e =EmployeeFactory.GetEmployee(id);

MessageBox.show(e.GetSalary());

七:面向结构做法与面向对象做法对比

示例场景:

随着公司业务规模拓展,出现了更多类型的员工,比如钟点工,计件工。。。。。等等,这对人事管理系统提出了挑战--原有的程序必须改变。

结构化做法:

几乎所有涉及到员工类型的地方,都需要修改,代码需要重新编译,重新部署。

面向对象做法:

只需要添加新的类文件,定义新的员工类,继承Employee类重写GetSalary()方法,然后EmployeeFactory.GetEmployee方法中根据条件,产生新的员工类型就可以了。其他地方(显示程序,原有员工类)不需要做任何改变。


总结: 面向对象的做法在应对需求变化的时候更为灵活。


八:重新认识面向对象

从宏观层面来看,面向对象的构建方式更能适应软件变化,能将变化所带来的影响减为最小。

从微观上层来看,面向对象的方式更强调各个类的“责任”,新增员工类型不会影响原来员工类型的实现代码——这更符合真实的世界,也更能控制变化所影响的范围,毕竟Enginner类不应该为新增的“钟点工”来买单。

对象是什么?
从概念层面讲,对象是某种拥有责任的抽象。
从规格层面讲,对象是一系列可以被其他对象使用的公共接口。
从语言层面来看,对象封装了代码和数据。

九:从设计原则到设计模式

针对接口编程,而不是针对实现编程:客户无需知道所使用对象的特定类型,只需知道对象拥有客户所期望的接口。

优先使用对象组合,而不是类继承:类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。继承在某种程度上破坏了封装性,子类父类耦合度高,而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。

封装变化点:使用封装来创建对象之间的分界层,让设计者可以在分界成的一侧进行修改,而不会对另一侧产生不良影响,从而实现层次间的松耦合。

使用重构得到模式:设计模式的应用不宜先入为主,一上来就使用设计模式是对设计模式最大的误用,没有一步到位的设计模式。敏捷软件开发实践提倡的”Refactoring to patterns“ 是目前公认的最好的使用设计模式的方法。

十:设计原则

单一职责原则(SRP):一个类应该仅有一个引起它变化的原因。

开放封闭原则(OCP):类模块应该是可扩展的,但是不可修改(对扩展开放,对更改封闭。)

Liskov 替换原则(LSP):子类必须能够替换它们的基类。

依赖倒置原则(DIP):高层模块不应该依赖低层模块,二者都应该依赖于抽象。抽象不应该依赖于实现细节,实现细节应该依赖于抽象。

接口隔离原则(ISP):不应该强迫客户程序依赖于它们不用的方法。

 

posted @ 2015-09-29 23:29  畅想  阅读(445)  评论(0编辑  收藏  举报