设计模式
刘伟 http://blog.csdn.net/lovelion
在线书籍 https://gof.quanke.name/
对于面向对象软件系统的设计而言,在支持可维护性的同时,提高系统的可复用性是一个至关重要的问题,如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一。
面向对象设计原则为支持可维护性复用而诞生,这些原则蕴含在很多设计模式中,它们是从许多设计方案中总结出的指导性原则。
最常见的7种面向对象设计原则如下表所示: 表1 7种常用的面向对象设计原则
设计原则名称 | 定 义 | 使用频率 |
---|---|---|
单一职责原则 (Single Responsibility Principle, SRP) | 一个类只负责一个功能领域中的相应职责 | ★★★★☆ |
开闭原则 (Open-Closed Principle, OCP) | 软件实体应对扩展开放,而对修改关闭 | ★★★★★ |
里氏代换原则 (Liskov Substitution Principle, LSP) | 所有引用基类对象的地方能够透明地使用其子类的对象 | ★★★★★ |
依赖倒转原则 (Dependence Inversion Principle, DIP) | 抽象不应该依赖于细节,细节应该依赖于抽象 | ★★★★★ |
接口隔离原则 (Interface Segregation Principle, ISP) | 使用多个专门的接口,而不使用单一的总接口 | ★★☆☆☆ |
合成复用原则 (Composite Reuse Principle, CRP) | 尽量使用对象组合,而不是继承来达到复用的目的 | ★★★★☆ |
迪米特法则 (Law of Demeter, LoD) | 一个软件实体应当尽可能少地与其他实体发生相互作用 | ★★★☆☆ |
面向对象设计原则之单一职责原则
单一职责原则(Single Responsibility Principle, SRP)是最简单的面向对象设计原则,它用于控制类的粒度大小。
单一职责原则定义如下:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
例子:
CRM(Customer Relationship Management,客户关系管理)系统中客户信息图形统计模块
图1 初始设计方案结构图
在图1中,CustomerDataChart类中的方法说明如下:getConnection()方法用于连接数据库,findCustomers()用于查询所有的客户信息,createChart()用于创建图表,displayChart()用于显示图表
在图1中,CustomerDataChart类承担了太多的职责,既包含与数据库相关的方法,又包含与图表生成和显示相关的方法。如果在其他类中也需要连接数据库或者使用findCustomers()方法查询客户信息,则难以实现代码的重用。
使用单一职责原则重构后的结构如图2所示:
面向对象设计原则之开闭原则
开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展
如果一个软件设计符合开闭原则,那么可以非常方便地对系统进行扩展,而且在扩展时无须修改现有代码,使得软件系统在拥有适应性和灵活性的同时具备较好的稳定性和延续性。
为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。
在Java、C#等编程语言中,可以为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成。在很多面向对象编程语言中都提供了接口、抽象类等机制,可以通过它们定义系统的抽象层,再通过具体类来进行扩展。如果需要修改系统的行为,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。
所以在java web项目的时候定义 Service 接口类和 ServiceImpl 实现类就是为了满足开闭原则!!
例子
Sunny软件公司开发的CRM系统可以显示各种类型的图表,如饼状图和柱状图等,为了支持多种图表显示方式,原始设计方案如图1所示
在ChartDisplay类的display()方法中存在如下代码片段:
......
if (type.equals("pie")) {
PieChart chart = new PieChart();
chart.display();
}
else if (type.equals("bar")) {
BarChart chart = new BarChart();
chart.display();
}
......
在该代码中,如果需要增加一个新的图表类,如折线图LineChart,则需要修改ChartDisplay类的display()方法的源代码,增加新的判断逻辑,违反了开闭原则。
现对该系统进行重构,使之符合开闭原则。
具体做法如下:
(1) 增加一个抽象图表类AbstractChart,将各种具体图表类作为其子类;
(2) ChartDisplay类针对抽象图表类进行编程,由客户端来决定使用哪种具体图表。
重构后结构如图2所示:
博文:深入理解Java的接口和抽象类
http://www.cnblogs.com/dolphin0520/p/3811437.html
面向对象设计原则之里氏代换原则
通俗版定义: 里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。
其严格表述如下:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1代换o2时,程序P的行为没有变化,那么类型S是类型T的子类型。
里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
需要注意如下几个问题:
(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。
(2) 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法。
(3) Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。
例子
在Sunny软件公司开发的CRM系统中,客户(Customer)可以分为VIP客户(VIPCustomer)和普通客户(CommonCustomer)两类,系统需要提供一个发送Email的功能,原始设计方案如图1所示
图1原始结构图
图2 重构后的结构图
在本实例中,在传递参数时使用基类对象,除此以外,在定义成员变量、定义局部变量、确定方法返回类型时都可使用里氏代换原则。针对基类编程,在程序运行时再确定具体子类。
博文阅读:
理解java的三大特性之封装
http://www.cnblogs.com/chenssy/p/3351835.html
理解java的三大特性之继承
http://www.cnblogs.com/chenssy/p/3354884.html
理解java的三大特性之多态
http://www.cnblogs.com/chenssy/p/3372798.html
面向对象设计原则之依赖倒转原则
依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。
在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入,设值注入(Setter注入)和接口注入
例子
图1 初始设计方案结构图
图2重构后的结构图
面向对象设计原则之接口隔离原则
接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干
例子
图1 初始设计方案结构图
由于在接口CustomerDataDisplay中定义了太多方法,即该接口承担了太多职责,一方面导致该接口的实现类很庞大,在不同的实现类中都不得不实现接口中定义的所有方法,灵活性较差,另一方面由于客户端针对大接口编程,将在一定程序上破坏程序的封装性
图2 重构后的结构图