面向对象设计必须遵循的几条原则

494 阅读4分钟

我们知道软件开发的需求总是不完整的,错误的,容易让人产生误解的,而且需求一直在变化,它主要表现在以下几个方面:用户对需求的看法,可能在于开发人员讨论以及看到软件新的可能性后发生变化;随着对问题的熟悉,开发人员对问题领域的看法也会变化;不是需求在变,而是人们对需求的理解再变化。

我们应该采用何种方法去应对需求变化呢?首先,在方法论层面我们应该采用敏捷开发;其次,在代码层面,使用OOD(Object-Oriented Design,面向对象设计),它的根本原则:面向接口编程;多用组合,而不是继承;发现变化,封装变化。但如何让设计满足这个原则呢?我们的先辈们总结出了5条设计原则,俗称SOLID原则,这就是本期我们要介绍的详细内容。

单一职责原则(SRP,The Single Responsibility Principle)

Robert C. Martin 大师对于单一职责原则有这样一个定义:一个类应该仅有一个引起它变化的原因,而这个引起变化的原因就是职责。那么职责是什么东西呢?先来说说我们人类的职责。作为项目经理,我的职责是:项目计划、需求管理、项目成本控制、项目时间控制等,我需要处理很多事情,同时,这些事紧密相关的。对应到面向对象设计领域,一个类的职责应该包含多个相关的功能。比如说用户控制类中一般包含了新增用户、删除用户、修改用户等相关功能。

开闭原则(OCP,The Open close Principle)

开闭原则就是说对扩展开发,对修改封闭。看到这里,我们不禁会问:新增功能还可以不用修改代码呀? 这是如何做到的呀?实际上开闭原则指的是提供者增加新的功能,而使用者不需要修改代码,并且增加的新功能不能是一个全新的功能,而是原有功能的替代实现。为了帮助大家理解,我举一个具体的例子。目前购物车有添加商品、计算价格两个功能,而商品有香蕉、苹果两种。Java代码如下:

public interface Item{
  public String getName();
  public float getPrice();
}
public class Apple implements Item {
    public String getName() {
        return "Apple";
    }

    public float getPrice() {
        return 5.0f;
    }
}

class Orange implements Item{
    public String getName() {
        return "Orange";
    }

    public float getunitPrice() {
        return 6.0f;
    }
}

public class ShopCar {
    List<Item> items = new ArrayList<Item>();
    public void addItem(Item item) {
        items.add(item);
    }

    public float calculateTotalPrice() {
        float total = 0.0f;
        for (Item i : items) {
           total += i.getPrice();
        }
        return total;
    }
}

我们注意到当新增一个香蕉类,ShopCar类不用修改,这是因为香蕉类是原有功能的替代实现,而不是一个全新的功能,如果购物车中新增商品价格变化这一全新功能,则需要修改ShopCar类。

Liskov替换原则(LSP,The Liskov Substitutuin Principle)

LSP替换原则指的是:子类型能够完全替换父类。它最经典的例子就是长方形和正方形。我们知道正方形是一种特殊的长方形,但是在面向对象设计领域,正方形并不能作为长方形的子类。原因在于设置正方形的长或宽时,同时设置了它的宽或长,那么,正方形的面积等于最后一次设定的长或宽的平方,而不是长乘以宽。具体Java代码如下:

// 长方形
public class Rectangle {
  protected int width;
  protected int height;
  public void setWidth(int width){
    this.width = width;
  }
  public void setHeight(int height){
    this.height = height;
  }
  public int area(){
    return width*height;
  }
}
// 正方形
public class Squrare extends Rectangle{

  public void setWidth(int width){
    this.width = width;
    this.height = height;
  }
  public void setHeight(int height){
     this.width = width;
     this.height = height;
  }
}
// 测试类
public class Test{
  public static void main(String[] args){  
        Rectangle rectangle = new Rectangle();  
        rectangle.setWidth(4);  
        rectangle.setHeight(5);  
        // 正确
        assert( rectangle.getArea() == 20);  
          
        rectangle = new Square();  
        rectangle.setWidth(4);  
        rectangle.setHeight(5);  
        // 错误,此时正方形面积是25
        assert( rectangle.getArea() == 20);  
    }  
}

判断一个子类是否满足LSP替换原则,我们可以像上面这个例子那样,在测试类中用子类来替换父类。如果测试能够通过,则说明符合LSP原则,否则就说明不符合LSP原则。

接口隔离原则(ISP,The Interface  Segregation Interface)

虽然我们要求软件开发人员都要遵循SRP,但是很多情况下类是没有满足SRP原则的,这个时候就需要用到接口隔离原则。ISP原则指的是客户端不应该被强迫去依赖它们并不需要的接口。也就是说,调用方调用的不需要是一个大而全的接口,而是一个小而精的接口。

依赖倒置原则(DIP,The Dependency Inversion Principle)

DIP包含两层意思:1. 高层模块不应该直接依赖低层模块,两者都应该依赖抽象层;2. 抽象不能依赖细节,细节必须依赖抽象。这里模块是个广义概念,可以是系统、子系统、子模块,甚至是类。而依赖包含多层意思:高层模块调用底层模块的方法,底层模块继承或实现抽象层。


image

欢迎关注微信公众号:木可大大,所有文章都将同步在公众号上。