面向对象设计原则-单一职责原则
1)概述
一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因。
2)优化前
CRM 客户信息图形统计模块 初始设计方案:
CustomerDataChart 类方法说明:
getConnection() 连接数据库
findCustomers() 查询客户信息
createChart() 创建图表
displayChart() 显示图表。
现使用对其进行重构。
3)重构后
DBUtil:负责连接数据库,getConnection();
CustomerDAO:负责操作 Customer 表,findCustomers();
CustomerDataChart:负责图表的生成和显示,createChart() 和 displayChart()。
2、面向对象设计原则-开闭原则
1)概述
应尽量在不修改原有代码的情况下进行扩展。
2)优化前
CRM 可以显示各种类型的图表,如饼状图和柱状图,为支持多种图表显示方式,初始设计方案:
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()方法,违反了开闭原则。
3)重构后
引入抽象图表类 AbstractChart,且 ChartDisplay 针对抽象图表类编程,通过 setChart() 方法由客户端来设置实例化的具体图表对象,在ChartDisplay 的 display() 中调用 chart 对象的 display() 显示图表。
如需增加新的图表,如折线图LineChart,只需将 LineChart 作为AbstractChart 的子类,在客户端向 ChartDisplay 注入 LineChart 对象即可,无须修改现有类库的源代码。
注意:
一般不把对配置文件的修改认为是对系统源代码的修改。
3、面向对象设计原则-里氏代换原则
1)概述
所有引用基类(父类)的地方必须能透明地使用其子类的对象。
尽量使用基类来对对象定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
注意:
- 子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法;
- 将父类设计为抽象类或接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时子类实例替换父类实例;
2)优化前
CRM中,客户(Customer)分VIP客户(VIPCustomer)和普通客户(CommonCustomer),提供发 Email 的功能,初始设计方案:
问题:两个 send() 中代码重复,而且在本系统中还将增加新类型的客户。
3)重构后
增加抽象客户类 Customer,将 CommonCustomer 和 VIPCustomer 类作为子类,邮件发送类 EmailSender 针对抽象客户类 Customer 编程。
在本实例中,传递参数时使用基类对象,此外在定义成员变量、定义局部变量、确定方法返回类型时都可使用里氏代换原则。
4、面向对象设计原则-依赖倒转原则
1)概述
抽象不应该依赖于细节,细节应当依赖于抽象,要针对接口编程,而不是针对实现编程。
在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类。
依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。
**构造注入:**通过构造函数来传入具体类的对象;
**设值注入:**通过Setter方法来传入具体类的对象;
**接口注入:**通过在接口中声明的业务方法来传入具体类的对象;
在定义时使用抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。
2)优化前
将存储在TXT或Excel文件中的客户信息转存到数据库中,需要进行数据格式转换,在客户数据操作类中将调用数据格式转换类的方法来实现格式转换和数据库插入操作。
问题:转换数据时数据来源不一定相同,需要更换数据转换类,需要修改CustomerDAO的源代码,而且在引入并使用新的数据转换类时也需要修改CustomerDAO的源代码。
3)重构后
更换具体数据转换类时无须修改源代码,只需修改配置文件,如果需要增加新的具体数据转换类,只要将新增数据转换类作为DataConvertor的子类并修改配置文件即可,无须修改源代码。
5、面向对象设计原则-接口隔离原则
1)概述
使用多个专门的接口,而不使用单一的总接口,为客户端提供尽可能小的单独的接口,而不要提供大的总接口。
2)优化前
CRM 客户数据显示模块,方法dataRead() 从文件中读取数据,transformToXML() 将数据转换成XML格式,createChart() 创建图表,displayChart() 显示图表,createReport() 创建文字报表,displayReport() 显示文字报表。
问题:
如果数据显示类无须进行数据转换,但实现了该接口,将不得不实现其中声明的transformToXML()方法(至少提供一个空实现);
如果需要创建和显示图表,除了需实现与图表相关的方法外,还需要实现创建和显示文字报表的方法,否则程序编译时将报错。
3)重构后
注意:控制接口的粒度
接口不能太小,会导致系统中接口泛滥,不利于维护;
接口不能太大,将违背接口隔离原则,灵活性较差,使用不方便。
6、面向对象设计原则-合成复用原则
1)概述
尽量使用对象组合,而不是继承来达到复用的目的。
组合/聚合可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少,其次才考虑继承。
合成复用可以在运行时动态进行。
2)优化前
数据库操作相关类,如 CustomerDAO 需要连接数据库,连接数据库的 getConnection() 封装在 DBUtil 类中,需要重用 DBUtil 类的getConnection(),初始方案将 CustomerDAO 作为 DBUtil 类的子类。
问题:
增加 OracleDBUtil 类连接 Oracle,由于 CustomerDAO 和 DBUtil 是继承关系,更换数据库连接时需要修改CustomerDAO类的源代码,将 CustomerDAO 作为 OracleDBUtil 的子类,这将违反开闭原则。
3)重构后
CustomerDAO 和 DBUtil 关系由继承变为关联,采用依赖注入的方式将 DBUtil 对象注入到 CustomerDAO 中,可以使用构造注入,也可以使用Setter注入。
如需要对 DBUtil 的功能扩展,可以通过其子类来实现,如通过子类 OracleDBUtil 来连接 Oracle 数据库。
7、面向对象设计原则-迪米特原则
1)概述
一个软件实体应当尽可能少地与其他实体发生相互作用。
尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。
2)优化前
当一个按钮(Button)被单击时,对应的列表框(List)、组合框(ComboBox)、文本框(TextBox)、文本标签(Label)等都将发生改变。
问题:
在该窗口中增加新的界面控件时需要修改与之交互的其他控件的源代码,系统扩展性较差,不便于增加和删除新控件。
3)重构后
引入中间类,界面控件之间不再发生直接引用,而是将请求先转发给中间类,再由中间类完成对其他控件的调用。
当需要增加或删除新的控件时,只需修改中间类即可,无须修改新增控件或已有控件的源代码。