面向对象的7大设计原则 之举例

一、面向对象的7大设计原则

  • 开闭原则
    • 对扩展开放,对更改关闭
    • 类模块应该是可扩展的,但是不可修改。
  • 里氏代换原则
    • 子类必须能够替换它们的基类(IS-A)
    • 继承表达类型抽象
  • 迪米特原则
    • 要求一个对象应该对其他对象有最少的了解,所以迪米特法则又叫做最少知识原则(Least Knowledge Principle, LKP)
  • 单一职责原则
    • 一个类应该仅有一个引起它变化的原因
    • 变化的方向隐含着类的责任
  • 接口分离原则
    • 不应该强迫客户程序依赖它们不用的方法
    • 接口应该小而完备
  • 依赖倒置原则
    • 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)
    • 抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)
  • 组合/聚合复用原则
    • 尽量使用组合/聚合,不要使用类继承
    • 在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新对象通过向这些对象的委派达到复用已有功能的目的。就是说要尽量的使用合成和聚合,而不是继承关系达到复用的目的

二、一句话理解七大原则

  • 开闭原则
/**
* 定义课程接口
*/
public interface ICourse {
    String getName();  // 获取课程名称
    Double getPrice(); // 获取课程价格
    Integer getType(); // 获取课程类型
}

/**
 * 英语课程接口实现
 */
public class EnglishCourse implements ICourse {

    private String name;
    private Double price;
    private Integer type;

    public EnglishCourse(String name, Double price, Integer type) {
        this.name = name;
        this.price = price;
        this.type = type;
    }

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

    @Override
    public Double getPrice() {
        return null;
    }

    @Override
    public Integer getType() {
        return null;
    }
}

// 测试
public class Main {
    public static void main(String[] args) {
        ICourse course = new EnglishCourse("小学英语", 199D, "Mr.Zhang");
        System.out.println(
                "课程名字:"+course.getName() + " " +
                "课程价格:"+course.getPrice() + " " +
                "课程作者:"+course.getAuthor()
        );
    }
}

如果课程要打折,有一种方法是往接口中添加方法;

public interface ICourse {

    // 获取课程名称
    String getName();

    // 获取课程价格
    Double getPrice();

    // 获取课程类型
    String getAuthor();

    // 新增:打折接口
    Double getSalePrice();
}

这就违反了开闭原则,会导致所有的其他课程都需要实现打折接口,更好的方法是用扩展代替修改;

public class SaleEnglishCourse extends EnglishCourse {

    public SaleEnglishCourse(String name, Double price, String author) {
        super(name, price, author);
    }

    @Override
    public Double getPrice() {
        return super.getPrice() * 0.85;
    }
}
  • 里氏代换原则
    里氏替换原则强调的是设计和实现要依赖于抽象而非具体;子类只能去扩展基类,而不是隐藏或者覆盖基类,它包含以下4层含义
    • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
    • 子类可以有自己的个性
    • 覆盖或实现父类的方法时输入参数可以被放大
    • 覆写或实现父类的方法时输出结果可以被缩小
  • 迪米特原则
    通过老师要求班长告知班级人数为例,讲解迪米特原则。先来看一下违反迪米特法则的设计,代码如下
public class Student {
    private Integer id;
    private String name;

    public Student(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class Teacher {
    public void call(Monitor monitor) {
        List<Student> sts = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            sts.add(new Student(i + 1, "name" + i));
        }
        monitor.getSize(sts);
    }
}

public class Monitor {
    public void getSize(List list) {
        System.out.println("班级人数:" + list.size());
    }
}

从逻辑上讲 Teacher 只与 Monitor 耦合就行了,与 Student 并没有任何联系,这样设计显然是增加了不必要的耦合。按照迪米特原则,应该避免类中出现这样非直接朋友关系的耦合。

public class Student {
    private Integer id;
    private String name;

    public Student(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class Teacher {
    public void call(Monitor monitor) {
        monitor.getSize();
    }
}

public class Monitor {
    public void getSize() {
        List<Student> sts = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            sts.add(new Student(i + 1, "name" + i));
        }
        System.out.println("班级人数" + sts.size());
    }
}
  • 单一职责原则
    用一个类描述动物呼吸这个场景:
class Animal {
    func breathe(animal: String) {
        print("\(animal)呼吸空气")
    }
}

let animal = Animal()
animal.breathe(animal: "牛")
animal.breathe(animal: "羊")
animal.breathe(animal: "猪")

程序上线后,发现问题了,并不是所有的动物都呼吸空气的,比如鱼就是呼吸水的。修改时如果遵循单一职责原则,需要将 Animal 类细分为陆生动物类 Terrestrial,水生动物 Aquatic,代码如下:

class Terrestrial {
    func breathe(animal: String) {
        print("\(animal)呼吸空气")
    }
}

class Aquatic {
    func breathe(animal: String) {
        print("\(animal)呼吸水")
    }
}

let terrestrial = Terrestrial()
terrestrial.breathe(animal: "牛")
terrestrial.breathe(animal: "羊")
terrestrial.breathe(animal: "猪")

let aquatic = Aquatic()
aquatic.breathe(animal: "鱼")

违反单一职责的修改:

class Animal {
    func breathe(animal: String) {
        if (animal == "鱼") {
            print("\(animal)呼吸水")
        } else {
            print("\(animal)呼吸空气")
        }
    }
}

let animal = Animal()
animal.breathe(animal: "牛")
animal.breathe(animal: "羊")
animal.breathe(animal: "猪")
animal.breathe(animal: "鱼")

// 这种修改方式要简单的多。但是却存在着隐患:有一天需要将鱼分为呼吸淡水的鱼和呼吸海水的鱼,则又需要修改Animal类的breathe方法,而对原有代码的修改会对调用 “猪” “牛” “羊” 等相关功能带来风险,也许某一天你会发现程序运行的结果变为“牛呼吸水”了。
  • 接口分离原则
    • 接口中的方法应该尽量少,不要使接口过于臃肿,不要有很多不相关的逻辑方法。
  • 依赖倒置原则
    场景是这样的,母亲给孩子讲故事,只要给她一本书,她就可以照着书给孩子讲故事了。代码如下:
class Book {
    func getContent() -> String{
        return "很久很久以前有一个阿拉伯的故事……"
    }
}

class Mother {
    func narrate(book: Book) {
        print("妈妈开始讲故事");
        print("\(book.getContent())")
    }
}

let mother = Mother()
mother.narrate(book: Book())

运行良好,假如有一天,需求变成这样:不是给书而是给一份报纸,让这位母亲讲一下报纸上的故事,报纸的代码如下:

class NewsPaper {
    func getContent() -> String {
        return "70 周年庆..."
    }
}

只是将书换成报纸,居然必须要修改Mother才能读。假如以后需求换成杂志呢?换成网页呢?还要不断地修改Mother,这显然不是好的设计。原因就是Mother与Book之间的耦合性太高了,必须降低他们之间的耦合度才行。我们引入一个抽象的接口IReader。读物,只要是带字的都属于读物:

protocol IReader {
    func getContent() -> String
}

class Book: IReader {

    func getContent() -> String{
        return "很久很久以前有一个阿拉伯的故事……"
    }
}

class NewsPaper: IReader{
    func getContent() -> String {
        return "70 周年庆..."
    }
}

class Mother{
    func narrate(reader: IReader) {
        print("妈妈开始讲故事");
        print("\(reader.getContent())")
    }
}

let mother = Mother()
mother.narrate(reader: Book())
mother.narrate(reader: NewsPaper())

这样修改后,无论以后怎样扩展,都不需要再修改 Mother 类了。

  • 组合/聚合复用原则
    • 类之间有三种基本关系,分别是:关联(聚合和组合)、泛化(与继承同一概念)、依赖。
      • 聚合,用来表示整体与部分的关系或者 “拥有” 关系。其中,代表部分的对象可能会被代表多个整体的对象所拥有,但是并不一定会随着整体对象的销毁而销毁,部分的生命周期可能会超越整体。好比班级和学生,班级销毁或解散后学生还是存在的,学生可以继续存在某个培训机构或步入社会,生命周期不同于班级甚至大于班级。
      • 合成,用来表示一种强得多的 “拥有” 关系。其中,部分和整体的生命周期是一致的,一个合成的新的对象完全拥有对其组成部分的支配权,包括创建和泯灭。好比人的各个器官组成人一样,一旦某个器官衰竭,人也不复存在,这是一种 “强” 关联。

三、温故而知新

posted @ 2023-02-02 16:06  又是火星人  阅读(82)  评论(0编辑  收藏  举报