系统化掌握Dart编程之面向对象设计

529 阅读7分钟

image.png

前言

面向对象设计Object-Oriented Design, OOD)是软件工程中的一种设计方法,它通过将现实世界中的实体抽象为对象来简化复杂系统的建模。

OOD阶段在分析模型基础上进行应用软件的系统设计对象设计,从而得到设计模型,该模型包含了解决问题的方案策略。是确定问题具体解决方案的过程。

OOD不仅依赖于基本的概念如对象封装继承多态,还依赖于一系列指导原则来确保代码的质量。以下是五个重要的OOD原则,并通过具体例子详细解释每个原则的应用。

千曲而后晓声,观千剑而后识器。虐它千百遍方能通晓其真意

一、单一职责原则(Single Responsibility PrincipleSRP

1.1、核心思想

1、定义: 一个应该只有一个引起它变化的原因

2、通俗理解:如果一个类做了太多的事情,当其中一部分需求发生变化时,可能会影响到其他部分的功能。因此,应该让每个类专注于完成一个特定的任务,减少耦合依赖。即低耦合和高内聚

1.2、案例说明

在图书馆管理系统中,Book 类只负责管理书籍的信息(如书名作者等),而不处理借阅逻辑会员信息。这样,如果需要修改书籍信息的存储方式,不会影响到借阅记录或会员管理。

代码示例

class Book {
  String title;
  String author;
  ///ISBN编号是指国际标准书号,它是识别图书的唯一标识符。
  ///ISBN由一串数字组成,有时也包含破折号,用于分隔不同部分,
  ///例如出版国家、出版社和书名等信息。ISBN有助于图书管理、销售及库存控制。
  String isbn;

  Book(this.title, this.author, this.isbn);
}

二、开放封闭原则(Open/Closed Principle, OCP

2.1、核心思想

1、定义软件实体模块函数等)应该对扩展开放对修改封闭

2、通俗理解: 这意味着可以在不修改现有代码的情况下添加新功能。这有助于保持系统的稳定性,同时允许新的特性被轻松集成

2.2、案例说明

在图书馆管理系统中,如果我们想支持不同类型书籍(如 Textbook, Novel),可以通过继承 Book 类来实现,而不需要修改 Book 类本身。

代码示例

// 定义抽象类 Book
abstract class Book {
  String title;
  String author;
  String isbn;

  // 构造函数
  Book(this.title, this.author, this.isbn);

  // 抽象方法,用于显示书籍的详细信息
  void displayDetails();
}

// 定义 Textbook 类,继承自 Book 类
class Textbook extends Book {
  String subject;

  // Textbook 类的构造函数
  Textbook(String title, String author, String isbn, this.subject) : super(title, author, isbn);

  // 实现抽象方法 displayDetails
  @override
  void displayDetails() {
    print('Textbook: $title, Author: $author, Subject: $subject');
  }
}

// 定义 Novel 类,继承自 Book 类
class Novel extends Book {
  String genre;

  // Novel 类的构造函数
  Novel(String title, String author, String isbn, this.genre) : super(title, author, isbn);

  // 实现抽象方法 displayDetails
  @override
  void displayDetails() {
    print('Novel: $title, Author: $author, Genre: $genre');
  }
}

void main() {
  Textbook textbook = Textbook('Dart Programming', 'John Doe', '123456789', 'Computer Science');
  textbook.displayDetails();

  Novel novel = Novel('费曼学习法', "尹红心", '987654321', '学习方法');
  novel.displayDetails();
}

三、接口隔离原则(Interface Segregation Principle, ISP

3.1、核心思想

1、定义客户端不应该依赖于它们不使用的接口

2、通俗理解:避免创建大而全的接口,而是创建多个小而具体的接口,使每个接口只包含一组紧密相关的操作

3.2、案例说明

在图书馆管理系统中,我们可以为不同的角色(如 Member, Librarian)定义不同的接口,而不是让所有角色都实现一个庞大的通用接口

代码示例

// 定义一个表示书籍的类
class Book {
  String title;
  String author;

  Book(this.title, this.author);
}

// 定义可借阅的接口
abstract class Borrowable {
  void borrow(Book book);
}

// 定义可归还的接口
abstract class Returnable {
  void returnBook(Book book);
}

// 定义可管理的接口
abstract class Manageable {
  void addBook(Book book);
  void removeBook(Book book);
}

// 会员类,实现 Borrowable 和 Returnable 接口
class Member implements Borrowable, Returnable {
  @override
  void borrow(Book book) {
    print('会员正在借阅书籍:${book.title} 作者:${book.author}');
  }

  @override
  void returnBook(Book book) {
    print('会员正在归还书籍:${book.title} 作者:${book.author}');
  }
}

// 图书管理员类,实现 Borrowable、Returnable 和 Manageable 接口
class Librarian implements Borrowable, Returnable, Manageable {
  @override
  void borrow(Book book) {
    print('图书管理员正在借阅书籍:${book.title} 作者:${book.author}');
  }

  @override
  void returnBook(Book book) {
    print('图书管理员正在归还书籍:${book.title} 作者:${book.author}');
  }

  @override
  void addBook(Book book) {
    print('图书管理员正在添加书籍:${book.title} 作者:${book.author}');
  }

  @override
  void removeBook(Book book) {
    print('图书管理员正在移除书籍:${book.title} 作者:${book.author}');
  }
}


void main() {
  Book book1 = Book('Dart 入门', '张三');
  Member member = Member();
  Librarian librarian = Librarian();

  member.borrow(book1);
  member.returnBook(book1);

  librarian.borrow(book1);
  librarian.returnBook(book1);
  librarian.addBook(book1);
  librarian.removeBook(book1);
}

四、依赖倒置原则(Dependency Inversion Principle, DIP

4.1、核心思想

1、定义高层模块不应该依赖于低层模块,二者都应该依赖于抽象抽象不应该依赖于细节细节应该依赖于抽象

2、通俗理解:尽量让代码依赖于接口抽象类而不是具体的实现类。这提高了代码的灵活性可测试性

  • 1、高层模块不应该依赖于低层模块高层模块是指那些负责业务逻辑决策关键功能的部分;低层模块是指那些执行具体操作的部分。
  • 2、二者都应该依赖于抽象:无论是高层还是低层模块,都应依赖于接口抽象类而不是具体的实现类
  • 3、抽象不应该依赖于细节:接口或抽象类不应依赖于具体的实现细节
  • 4、细节应该依赖于抽象具体的实现类应该依赖于接口或抽象类。

4.2、案例说明

在图书馆管理系统中,Library 类不应该直接依赖于具体的 Book 类型,而是依赖于 Book 接口。这样,即使将来引入新的书籍类型,也不会影响 Library 类。

传统方法(有问题的做法)

class Library {
  List<Book> books = [];

  Library() {
    // 构造函数,初始化书籍列表
  }

  void addBook(Book book) {
    books.add(book);
  }

  void listBooks() {
    for (Book book in books) {
      book.displayDetails();
    }
  }
}

class Book {
  String title;
  String author;

  Book(this.title, this.author);

  void displayDetails() {
    print('Title: $title, Author: $author');
  }
}

在上述例子中,Library 类直接依赖于 Book 类的具体实现。如果要引入新的书籍类型(如 Textbook, Novel),需要修改 Library 类的代码,这违背了开放封闭原则OCP)。

应用依赖倒置原则后的改进

  • 1、定义抽象接口: 首先定义一个 Book 接口,所有具体的书籍类型都将实现这个接口
abstract class Book {
  void displayDetails();
}
  • 2、创建具体的实现类: 为不同类型的书籍创建具体的实现类
// 教科书类,实现 Book 接口
class Textbook implements Book {
  String title;
  String author;
  String isbn;
  String subject;

  Textbook(this.title, this.author, this.isbn, this.subject);

  @override
  void displayDetails() {
    print('Textbook: $title by $author, ISBN: $isbn, Subject: $subject');
  }
}

// 小说类,实现 Book 接口
class Novel implements Book {
  String title;
  String author;
  String isbn;
  String genre;

  Novel(this.title, this.author, this.isbn, this.genre);

  @override
  void displayDetails() {
    print('Novel: $title by $author, ISBN: $isbn, Genre: $genre');
  }
}

正确实现代码示例

// 图书馆类
class Library {
  List<Book> books = [];

  // 构造函数
  Library() {
    // 初始化书籍列表
  }

  // 添加书籍的方法
  void addBook(Book book) {
    books.add(book);
  }

  // 列出所有书籍的方法
  void listBooks() {
    for (Book book in books) {
      book.displayDetails();
    }
  }
}

void main() {
  Library library = Library();
  Textbook textbook = Textbook("Java Programming", "John Doe", "1234567890", "Computer Science");
  Novel novel = Novel('费曼学习法', "尹红心", '987654321', '学习方法');
  library.addBook(textbook);
  library.addBook(novel);
  library.listBooks();
}

五、里式替换原则(Liskov Substitution PrincipleLSP

5.1、核心思想

1、定义子类应当能够替换它们的基类而不影响程序的正确性

2、通俗理解:子类应该可以无缝地替代父类使用,而不改变程序的行为。这确保了继承关系的合理性

5.2、案例说明

在图书馆管理系统中,TextbookNovel 都是 Book 的子类,任何接受 Book 对象的地方都可以无差别地接受 TextbookNovel 对象。

代码示例

// 打印书籍详细信息的函数
void printBookDetails(Book book) {
  book.displayDetails();
}

void main() {
  Textbook textbook = Textbook("Java Programming", "John Doe", "1234567890", "Computer Science");
  Novel novel = Novel('费曼学习法', "尹红心", '987654321', '学习方法');
  // 使用示例
  printBookDetails(textbook);
  printBookDetails(novel);
}

六、总结

通过上述五个面向对象设计原则(SRP, OCP, ISP, DIP,LSP),我们可以构建出更加健壮灵活且易于维护的软件系统。这些原则不仅仅是理论上的概念,而是实际可行且非常有效的设计方法。希望通过上述深入的讲解详细案例能帮助你更好地掌握面向对象设计的原则

码字不易,记得 关注 + 点赞 + 收藏 + 评论