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

687 阅读7分钟

image.png

前言

面向对象编程 —— 构建灵活、可维护软件的基石

理想中的OOP

面向对象图.jpg

实际中的OOP:

image.png 面向对象编程Object-Oriented Programming, OOP)是一种以“对象”为核心概念的编程范式。它鼓励我们将现实世界中的实体抽象为计算机世界中的对象,每个对象都是数据作用于数据操作封装体。通过这种方式,OOP 使我们可以用更直观更贴近自然的方式描述问题域,并且能够轻松地模拟复杂的交互关系

OOP核心思想在于封装继承多态抽象 四大支柱

除了上述特性外,OOP 还提倡遵循一些设计原则,如 SOLID 原则,它们指导我们编写易于维护和扩展的代码。此外,各种设计模式也为解决常见的设计挑战提供了经过验证的解决方案

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

一、类

1.1、概述

image.png

在学习OOP时,先是引入对象的概念,再对对象进行抽象的角度引入的概念。但当设计和实现时,首先接触的不是对象,而是类和类层次结构

具有实例化功能,包括实例生成(由类的构造函数Constructor)完成)。类的实例化功能决定了类及其实例具有如下特征:

  1. 同一个类的不同实例具有相同的数据结构,承受的是同一方法集合所定义的操作,因而具有规律相同的行为
  2. 同一个类的不同实例可以持有不同的值,因而可以具有不同的状态
  3. 实例的初始状态初值)可以在实例化时确定

1.2、类的定义

  • 1、类定义:类是使用 class 关键字来定义的。
  • 2、类名:类的唯一标识,遵循标识符的命名规则
  • 3、类体:使用大括号 {}表示,其内部包含了类的定义内容,同时限定了类的作用范围作用域)。
  • 4、类体内容:内容可以包含属性成员变量)和方法成员函数),以及构造函数等。

代码示例

///定义Dart类,所有的类都继承自Object
class Person {
  // 属性 (成员变量)
  String name;
  int age;

  // 构造函数
  Person(this.name, this.age);

  // 方法 (成员函数)
  void introduce() {
    print('Hello, my name is $name and I am $age years old.');
  }
}

void main() {
  var person = Person('Alice', 30);
  person.introduce(); // 输出: Hello, my name is Alice and I am 30 years old.
}

二、继承和类层次结构

谁知道这是一棵什么树?

image.png

继承一般通过定义类之间的关系来体现。在面向对象系统中,子类和父类之间的继承关系构成了这个系统的类层次结构,可以用(对应于单继承)这样的图来描述。

  • 当执行子类的实例生成方法时,首先在类层次结构中从该子类沿继承路径上溯至它的一个基类,然后自顶向下执行该子类所有父类的实例生成方法。
  • 当子类的实例消除时,执行顺序恰好相反
class Student extends Person{
  Student(super.name, super.age);
}

三、对象、消息传递和方法

对象类的实例。其表现形式与一般数据类型相似,本质区别在于:

对象之间是通过消息传递方式进行通信的

3.1、消息传递模型

OOP中,消息传递对象间交互的主要方式。对象被视为通过消息互相通信的实体,可以接收拒绝外界的消息。

  • 1、消息内容:发送一条消息至少需要指定接收对象消息名称(通常是对象中的一个方法名)。
  • 2、参数:消息通常还包含一组参数,用于向对象传递额外信息

简而言之,对象通过消息传递进行通信,消息包括目标对象方法名和必要的参数。这使得对象能够以一种解耦的方式互动,增强了系统的灵活性可维护性

class Student extends Person{
  Student(super.name, super.age);

  void learn(String subject){
    print('$name is learning $subject');
  }
}

void main() {
  var person = Person('Alice', 30);
  person.introduce(); // 输出: Hello, my name is Alice and I am 30 years old.
  
  var student = Student("zhangsan", 18);
  student.learn("English");//输出:zhangsan is learning English
}

四、对象自身引用

对象自身引用self-Reference)是编程语言中的一种特有的结构。不同编程语言中的名称不同,如C++JavaDart中称为this,在Object-C中称为self

示例代码

class Person {
  // 属性 (成员变量)
  String name;
  int age;

  // 构造函数
  Person(this.name, this.age);
}

五、重置

重置覆盖Overriding)是在子类中重新定义父类中已经定义的方法,基本思想是通过一种动态绑定机制的支持,使得子类在继承父类接口定义的前提下用适合自己要求的实现去置换父类中的相应实现。

代码示例

///定义Dart类,所有的类都继承自Object
class Person {
  // 属性 (成员变量)
  String name;
  int age;

  // 构造函数
  Person(this.name, this.age);
  
  /// 重写父类的方法, 多态的最主要的表现形式
  @override
  String toString() {
    return 'Person{name: $name, age: $age}';
  }
}

void main() {
  var person = Person('Alice', 30);
  person.toString(); 
}

六、使用 gettersetter

Dart 支持显式定义 gettersetter 方法来访问修改私有属性

class Person {
  String _name; // 私有属性,以下划线开头

  // 构造函数
  Person(this._name);

  // Getter
  String get name => _name;

  // Setter
  set name(String value) {
    if (value.isNotEmpty) {
      _name = value;
    } else {
      throw ArgumentError('Name cannot be empty');
    }
  }

  void introduce() {
    print('Hello, my name is $_name.');
  }
}

void main() {
  var person = Person('Alice');
  person.introduce(); // 输出: Hello, my name is Alice.

  person.name = 'Bob';
  person.introduce(); // 输出: Hello, my name is Bob.
}

七、工厂构造函数

工厂构造函数用于返回现有的实例不同类型的对象,而不必每次都创建新的实例。

///实现方式1
class Logger {
  static final Map<String, Logger> _cache = <String, Logger>{};

  final String name;

  Logger._internal(this.name);

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name]!;
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }
}

///实现方式2
// 定义抽象基类
abstract class Shape {
  void draw();
}

// 定义具体的子类
class Circle implements Shape {
  @override
  void draw() {
    print('Drawing a circle.');
  }
}

class Rectangle implements Shape {
  @override
  void draw() {
    print('Drawing a rectangle.');
  }
}

class Square implements Shape {
  @override
  void draw() {
    print('Drawing a square.');
  }
}

// 使用工厂模式的类
class ShapeFactory {
  // 静态工厂方法
  static Shape createShape(String type) {
    switch (type.toLowerCase()) {
      case 'circle':
        return Circle();
      case 'rectangle':
        return Rectangle();
      case 'square':
        return Square();
      default:
        throw ArgumentError('Unknown shape type: $type');
    }
  }
}

void main() {
  //方式1
  var logger1 = Logger('main');
  var logger2 = Logger('main');

  print(logger1 == logger2); // 输出: true
  
  //方式2
  // 创建不同类型的对象
  try {
    Shape shape1 = ShapeFactory.createShape('circle');
    shape1.draw(); // 输出: Drawing a circle.

    Shape shape2 = ShapeFactory.createShape('rectangle');
    shape2.draw(); // 输出: Drawing a rectangle.

    Shape shape3 = ShapeFactory.createShape('square');
    shape3.draw(); // 输出: Drawing a square.
  } catch (e) {
    print(e);
  }
}

八、命名构造函数

命名构造函数允许为类定义多个构造函数,每个都有不同的名字

class Rectangle {
  double width;
  double height;

  // 默认构造函数
  Rectangle(this.width, this.height);

  // 命名构造函数
  Rectangle.square(double side) : width = side, height = side;
}

void main() {
  var rect = Rectangle(20, 15);
  var square = Rectangle.square(10);

  print('Rectangle: ${rect.width}x${rect.height}');
  print('Square: ${square.width}x${square.height}');
}

Dart 中,命名构造函数后面的冒号(:)用于初始化列表initializer list)。初始化列表允许你在构造函数体执行之前初始化类的成员变量。这是确保对象在创建时就处于有效状态的一种方式。

初始化列表的作用:

  • 1、初始化成员变量:可以在构造函数中直接为成员变量赋值
  • 2、调用父类构造函数:如果当前类继承自另一个类,可以使用初始化列表来调用父类的构造函数
  • 3、执行表达式:可以在初始化列表中执行一些简单的表达式方法调用

8.1、简单初始化

class Person {
  String name;
  int age;
  // 命名构造函数,使用初始化列表初始化成员变量
  Person.guest(this.name, this.age);
}

在上述例子中,this.namethis.age初始化列表的一部分,它们直接将构造函数参数赋值给类的成员变量。

8.2、使用表达式进行初始化

class Rectangle {
  double width;
  double height;
  double area;

  // 命名构造函数,使用初始化列表计算面积
  Rectangle.square(double side) : width = side, height = side, area = side * side;

  void display() {
    print('Rectangle: ${width}x${height}, Area: $area');
  }
}

void main() {
  var square = Rectangle.square(5);
  square.display(); // 输出: Rectangle: 5.0x5.0, Area: 25.0
}

在上述例子中,area = side * side 是一个表达式,在初始化列表中计算并赋值给 area 成员变量。

8.3. 调用父类构造函数

class Animal {
  String name;

  Animal(this.name);

  void sound() {
    print('$name makes a sound.');
  }
}

class Dog extends Animal {
  bool isTrained;

  // 命名构造函数,使用初始化列表调用父类构造函数
  Dog(super.name, this.isTrained);

  @override
  void sound() {
    print('$name barks.');
  }
}

void main() {
  var dog = Dog('Buddy', true);
  dog.sound(); // 输出: Buddy barks.
}

在上述例子中,super(name) 在初始化列表中调用了父类 Animal 的构造函数,以确保父类的成员变量也被正确初始化。

九、总结

OOP不仅仅是一组技术或规则,更是一种思维方式。它教会我们如何将复杂的现实世界映射到软件中,同时保持系统的清晰结构和良好的可维护性。对于现代软件开发而言,掌握 OOP 是一项至关重要的技能,它为我们打开了通往高效优雅编程的大门。

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