(1)理解面向对象设计原则的基本概念
(2)掌握主要设计原则的概念、实现方法和实例
里氏代换原则
修改之前:
修改之后:
interface Conic{
public long getMajor_axis();
public long getShort_axis();
}
class Ellipse implements Conic{
private long major_axis ;
private long short_axis ;
public void setMajor_axis(long major_axis){
this.major_axis = major_axis ;
}
public long getMajor_axis(){
return this.major_axis;
}
public void setShort_axis(long short_axis){
this.short_axis = short_axis ;
}
public long getShort_axis(){
return this.short_axis;
}
}
class Circle implements Conic{
private long radius;
public void setRadius(long radius){
this.radius = radius;
}
public long getRadius(){
return radius;
}
public long getMajor_axis(){
return getRadius ();
}
public long getShort_axis(){
return getRadius();
}
}
未修改之前,在“圆形类是否是椭圆形类的子类”问题上是违法“里氏代换原则”的,修改之后,里氏代换原则没有没破坏。
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
合成/聚合复用原则:
继承的优点:实现、修改、拓展比较容易。
继承的缺点:1、运行时不可改变。2、破坏父类的包装性。3、父类改变子类随着改变。4、当不适应问题的时候,有可能需要改变父类。
修改之前:
修改之后:
public class People {
Role role = new Role();
}
public class Emplyee extends Role{
}
public class Manager extends Role{
}
public class Role {
}
public class Student extends Role{
}
修改之前的设计Employee、Manager、Student分别描述一种角色,但是人可以有几种不同的角色,所以当一个类是另一个类的角色时,不应当使用继承描述这种关系。这一错误的设计源于把角色的等级结构与人的等级结构混淆起来,把Has-A误解为Is-A。这里采用合成/聚合关系的办法解决这个问题,增加一个Role角色,People和Role是合成关系,Role类和Employee类、Manager类、Student类之间是继承关系。
依赖倒转原则
class Account{
private AccountType accountType;
private AccountStatus accountStatus;
public Account(AccountType acctType){
//……
}
public void deposit(float amount){
//……
}
}
abstract class AccountType{
public abstract void deposit(float amount);
}
abstract class AccountStatus{
public abstract void sendMessage();
}
class Savings extends AccountType{
public void deposit(float amount){
//……
}
}
class Settlement extends AccountType{
public void deposit(float amount){
//……
}
}
class Open extends AccountStatus{
public void sendMessage(){
//……
}
}
class Frozen extends AccountStatus{
public void sendMessage(){
//……
}
}
实现类只实现接口和抽象类声明的方法,不能增加新的方法。
Account类依赖于AccountType类和AccountStatus两个抽象类,而不是它们的子类型。Account类不依赖于具体类,因此当有新的具体类型添加到系统中时,Account类不需要改变。减少了类之间的耦合性,提高系统的可维护性,减少并行开发引起的风险,提高代码的可读性。
迪米特法则
修改之前
修改之后
public class Agent {
private Outside_world ow = new Outside_world();
public void forward(){
ow.operation2();
}
}
public class Outside_world {
public void operation2()
{
}
}
public class Star {
public void operation1(Agent agent){
agent.forward();
}
}
修改之前,明星类和外界类发生了相互作用,不满足迪米特法则。
改造的方法是调用转发,Star无须知道Outside_word的存在就可以做同样的事情。
接口隔离法则
修改之前
修改之后
interface Searcher{
void search(String[] keyword);
void getResultset();
}
interface Indexer{
void reIndexAll();
void updateIndex();
}
interface ResultSet{
void first(); //将光标移到集合的第一个元素
void last(); //将光标移到集合的最后一个元素
void next(); //将光标移到集合的下一个元素
void previous(); //将光标移到集合的前一个元素
String getExcerpt(); //返回当前记录的摘要
String getFullRecord(); //将记录的全文返回
}
原来的代码违反了角色分割原则,原因是不同功能的接口放在一起,一个接口包含了搜索器角色、索引生成器角色以及搜索结果集角色多种角色。
接口隔离原则的第一种解释是客户端不应该依赖那些它不需要的接口。另一种解释是一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需要知道与之相关的方法即可。
通过仔细分析,设计人员发现该类图存在非常严重的问题,如果需要增加一种新的大小的笔或者增加一种新的颜色,都需要增加很多子类,如增加一种绿色,则对应每一种大小的笔都需要增加一支绿色笔,系统中类的个数急剧增加。
试根据依赖倒转原则和合成复用原则对该设计方案进行重构,使得增加新的大小的笔和增加新的颜色都较为方便。
依赖倒转原则(子类只实现声明过的方法,实现类依赖抽象类或接口,构建出一个抽象)
合成/聚合复用(将已有对象,纳入新对象之中,新对象可以调用已有对象的方法,构建出一个新的对象)
结合面向对象设计原则分析:正方形是否是长方形的子类?
不是,违反了里氏代换原则,例如有一个方法,如果长方形的短边小于等于长边,短边就加一,直到短边大于等于长边,这个方法对于长方形是适用的,但是对于正方形是不适用的,正方形类会一直执行这个方法直到溢出。所以它违反了理性代换原则中:引用父类的地方必须能透明地使用子类的对象。子类型必须能够替换掉它们的父类型;子类继承了父类,子类可以以父类的身份出现。
如果在上述系统中增加一个新的组件类,则必须修改与之交互的其他组件类的源代码,将导致多个类的源代码需要修改。
现根据迪米特法则对上述代码进行重构,以降低组件之间的耦合度。
原图:
根据迪米特法则修改后的图:
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
合成/聚合复用:将已有的对象纳入新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能。
迪米特法则:只与你直接的朋友通信;不要跟"陌生人"说话;每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
接口隔离原则:第一种解释是客户端不应该依赖那些它不需要的接口。另一种解释是一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需要知道与之相关的方法即可。
依赖倒转原则:抽象不应当依赖于细节;细节应当依赖于抽象。依赖倒转原则还可以表达为要针对接口编程,不要针对实现编程。