Clever Castle
1614 words
8 minutes
面向对象六大原则(上)

单一职责原则#

单一职责原则(Single Responsibility Principle,SRP)简而言之就是对于一个类或者接口, 引起其改变的应该只能有一个原因. 比如要将负责属性和行为的类分开.

里氏替换原则#

定义:所有引用基类的地方必须能透明地使用其子类的对象. 只要父类出现的地方, 子类就可以出现, 而且替换为子类不会产生任何错误或者一场. 但是反过来不一定可行.

  1. 子类中可以增加自己特有的方法。
  2. 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  3. 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

前置条件#

当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。

class Father {

    public Collection doSomething(HashMap map){
        StdOut.println("父类被执行...");
        return map.values();
    }
}

class Son extends Father{

    public Collection doSomething(Map map) {
        StdOut.println("子类被执行");
        return map.values();
    }
}

子类方法和父类方法, 方法名相同, 返回类型相同, 但是参数不同, 所以不是Override, 而是Overload. 在这种情况下, 如果传入HashMap, 子类的doSomething()不会被执行. 这是正确的, 因为子类并没有重写父类方法, 而是重载父类方法, 所以如果父类的前置条件(形参) 范围宽于子类则不正确.

public static void main(String[] args){
    Father f=new Father();
    HashMap map=new HashMap();
    f.doSomething(map);
}

父类被执行
public static void main(String[] args){
    //父类出现的地方都可以被子类代替, 且不会改变逻辑
    Son f=new Son();
    HashMap map=new HashMap();
    f.doSomething(map);
}

父类被执行
public static void main(String[] args){
   //子类出现的地方, 父类不一定可以代替
    Son f=new Son();
    Map map=new HashMap();
    f.doSomething(map);
}

子类被执行
class Father {

    public Collection doSomething(Map map){
        StdOut.println("父类被执行...");
        return map.values();
    }
}

class Son extends Father{

    public Collection doSomething(HashMap map) {
        StdOut.println("子类被执行");
        return map.values();
    }
}
public static void main(String[] args){
    Father f=new Father();
    HashMap map=new HashMap();
    f.doSomething(map);
}

父类被执行
public static void main(String[] args){
    //父类出现的地方都可以用子类代替
    Son f=new Son();
    HashMap map=new HashMap();
    f.doSomething(map);
}

子类被执行

可以注意到, 此时子类方法被执行了, 而子类并没有重写父类的相应的方法, 而是重载了父类的方法.

public static void main(String[] args){
    Son f=new Son();
    Map map=new HashMap();
    f.doSomething(map);
}

父类被执行

后置条件#

当子类的方法实现或覆写父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格或相同。

注意点#

  1. 在类中调用其他类时务必使用其父类或者接口, 如果不能使用父类或者接口, 则说明类的设计已经违背了LSP原则.
  2. 如果子类不能完整地实现父类的方法, 或者父类的一些方法在子类中完全和父类业务逻辑不同, 则建议不使用继承关系, 而是用依赖, 聚集, 组合等关系代替.

依赖倒置原则#

Dependence Inversion Principle

  1. 高层模块不应该依赖低层模块
  2. 抽象不应该依赖细节
  3. 细节应依赖抽象

Untitled

对于Java来说.

  1. 模块间的依赖通过抽象产生,实现类之间不发生直接依赖关系, 其依赖关系是通过接口或者抽象类产生的
  2. 接口或这抽象类不依赖于实现类
  3. 实现类依赖接口或抽象类

这样也有利于并行开发, 即使只完成了一部分工作, 仍可以进行单元测试.

这也符合现代开发的流程, 先写好单元测试类, 再写实现类.

规则#

  1. 每个类尽量都有接口或抽象类, 或者抽象类和接口都具备
  2. 变量的表面类型尽量是接口或者是抽象类(如果使用类的clone方法, 就必须使用实现类)
  3. 任何类都不应该从具体类中派生(如果实在需要继承自具体类, 尽量不要超过两层的继承)
  4. 尽量不要覆写基类的方法

总体而言, 依赖倒置原则是六大原则中最难实现的, 也是实现开闭原则的重要途径. 总体而言, 把握住面向接口编程即可.

接口隔离原则#

Interface Segregation Principles(ISP)

  1. 客户端不应该依赖它不需要的接口
  2. 类似的依赖关系应该建立在最小的接口上

接口应该细化, 不要使用过于臃肿的接口. 客户端需要什么接口就提供什么接口, 将不需要的接口剔除掉. 不要将太多的方法放在同一个接口之中.

但是接口设计也要有度, 不可过度设计, 这个度往往根据经验和常识判断.

迪米特法则#

Law of Demeter(LOD) , 最少知识原则(Least Knowledge Principle))

一个对象应该对其它对象有最少的了解, 另一个解释是只与直接的朋友通信.

朋友类: 出现在成员变量, 方法的输入输出参数中的类称为成员朋友类, 而出现在方法体内部的类不属于朋友类

开闭原则#

Open Close Principle(OCP)

一个软件实体如类, 模块和函数等应该对扩展开放, 对修改关闭.

实例#

interface IBook{

    public String getName();

    public int getPrice();

    public String getAuthor();
}

class NovelBook implements IBook{
    private String name;
    private int price;
    private String author;

    public NovelBook(String name, int price, String author){
        this.name=name;
        this.price=price;
        this.author=author;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getPrice() {
        return price;
    }

    @Override
    public String getAuthor() {
        return author;
    }
}

如果将来要搞打折, 一般可能会用以下两个方法来解决:

  1. 在接口中添加getOffPrice()方法. 但是这需要对每一个实现IBook接口的实现类都添加该方法, 工作繁琐. 且接口应该是稳定且可靠的, 不应该经常发生变化.
  2. 修改实现类, 直接在getPrice()中实现打折处理, 但是如果仍需要知道原价是多少, 就会出问题.
  3. 通过扩展实现

添加一个子类

class OffNovelBook extends NovelBook{

    public OffNovelBook(String name,int price, String author){
        super(name,price,author);
    }

    @Override
    public int getPrice(){
        int selfPrice=super.getPrice();
        int offPrice=selfPrice*90/100;
        return offPrice;
    }
}
面向对象六大原则(上)
https://blog.ivyxjc.com/posts/pattern-oo-principle/
Author
ivyxjc
Published at
2016-10-24