Dart is an object-oriented language that supports mixin-based inheritance mechanisms

  • All objects are instances of a class, and all classes inherit from the Object class.
  • Mixin-based inheritance means that every class other than Object has only one superclass, and the code of one class can be reused in multiple other class inheritance
  • The Extension method is a way to add functionality to a class without changing it or creating a subclass

Use the members of the class

The members of an object consist of functions and data (that is, methods and instance variables). Method calls are made through the object, which has access to its functions and data. Use. To access an instance variable or method of an object:

import 'dart:math';

main(List<String> args) {
  var p = Point(2.2);
  // Assign to the instance variable y.
  p.y = 3;
// Get y.
  assert(p.y == 2);
// Call the distanceTo() method on p.
  num distance = p.distanceTo(Point(4.4));
  print(distance);
}
Copy the code

Use? . Instead of. Avoids the problem caused by null on the left side of the expression:

If p is non-null, set its y value to 4. If p is non-null, set its y value to 4.p? .y =4;
Copy the code

Using constructors

Constructors can be used to create an object. Constructors can be named by ClassName or ClassName. For example, the following code creates a Point object using the Point() and point.fromjson () constructors, respectively:

var p1 = Point(2.2);
var p2 = Point.fromJson({'x': 1.'y': 2});
Copy the code

The following code has the same effect, but the new keyword before the constructor name is optional:

var p1 = new Point(2.2);
var p2 = new Point.fromJson({'x': 1.'y': 2});
Copy the code

⚠️** Version tip: ** Starting with Dart 2, the new keyword is optional

Some classes provide constant constructors. Use the constant constructor to create compile-time constants preceded by the const keyword:

var p = const ImmutablePoint(2.2);
Copy the code

Two compile-time constants constructed with the same constructor and the same parameter values are the same object:

var a = const ImmutablePoint(1.1);
var b = const ImmutablePoint(1.1);
assert(identical(a, b)); // They are the same instance!
Copy the code

You can omit the const keyword before constructors or literals, depending on the situation in which constant context is used. For example, in the following example we create a constant Map:

// Lots of const keywords here.
// There are many const keywords here
const pointAndLine = const {
  'point': const [const ImmutablePoint(0.0)].'line': const [const ImmutablePoint(1.10), const ImmutablePoint(2 -.11)]};Copy the code

Depending on the context, you can keep only the first const keyword and omit all the rest:

// Only one const, which establishes the constant context.
// Only one const keyword is required; the others are implicitly related to context.
const pointAndLine = {
  'point': [ImmutablePoint(0.0)].'line': [ImmutablePoint(1.10), ImmutablePoint(2 -.11)]};Copy the code

However, if the context does not determine whether to omit cosnt, the const keyword should not be omitted. Otherwise, a nonconstant object will be created. For example:

var a = const ImmutablePoint(1.1); // Creates a constant
var b = ImmutablePoint(1.1); // Does NOT create a constant (Does NOT create a constant)
assert(! identical(a, b));// These two variables are NOT the same instance!
Copy the code

⚠️** Version tip: ** Only from Dart 2 can the const keyword be omitted based on context

Gets the type of the object

The Type of an Object, which is an instance of Type, can be obtained at runtime using the runtimeType property of an Object

print('The type of a is ${a.runtimeType}');
Copy the code

So far, we’ve solved how to use classes. The rest of this section will show you how to implement a class

The instance variables

Here is an example of declaring an instance variable:

class Point {
  num x; // Declare the instance variable x and initialize to NULL.
  num y; // Declare the instance variable y and initialize to null.
  num z = 0; // Declare the instance variable z and initialize it to 0.
}
Copy the code

All uninitialized instance variables have a value of NULL

All instance variables implicitly declare a Getter method, and non-final instance variables implicitly declare a Setter method. You can look at getters and setters for more information

class Point {
  num _x;
  num _y;
  set x(num value) =>_x = value;
  set y(num value){_y = value;print("Setting up y.setter"); }num get x => _x;
  num get y{print("Setting y.setter");return _y;}
}


void main() {
  var point = Point();
  point.y = 4; // Use Setter methods for x.
  print(point.y);
  point.x = 4; // Use Setter methods for x.
  assert(point.x == 3); // Use the Getter method for x.
}
Copy the code
class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two attributes generated by the calculation: right and bottom.
  set right(num value) => left = value - width;
  num get right => left + width;
  
  set bottom(num value) => top = value - height;
  num get bottom => top + height;
}

void main() {
  var rect = Rectangle(3.4.20.15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == - 8 -);
}
Copy the code

If you initialize an instance variable when you declare it (rather than in a constructor or other method), the value of the instance variable is set when the object instance is created, before the constructor and its initializer list are executed

The constructor

A constructor can be declared by declaring a function that has the same name as the class (you can also add additional identifiers to the named constructor). Most constructors take the form of generative constructors, which create an instance of a class:

class Point {
  num x, y;
  Point(num x, num y) {
    // There will be a better way to implement this logic, stay tuned.
    this.x = x;
    this.y = y; }}Copy the code

Use this keyword to reference the current instance note: Using this keyword makes sense if and only if there are naming conflicts, otherwise Dart ignores this keyword

Parameter properties

Assigning a value to an instance variable in a constructor is similar for most programming languages, but Dart provides a special syntactic sugar to simplify this step:

class Point {
  num x, y;
  // The syntax sugar used to set x and y before the constructor body is executed.
  Point(this.x, this.y);
}
Copy the code

Default constructor

If you don’t declare a constructor, Dart automatically generates a no-argument constructor that calls its parent’s no-argument constructor

Constructors are not inherited

A subclass does not inherit the constructor of its parent class, and if the subclass does not declare a constructor, there is only one constructor with no arguments by default

The named constructor

You can declare multiple named constructors for a class to express more explicit intent:

class Point {
  num x, y;
  Point(this.x, this.y);
  // the named constructor
  Point.origin() {
    x = 0;
    y = 0; }}Copy the code

Remember that constructors cannot be inherited, which means that the named constructor of the parent class cannot be inherited by subclasses. If you want to create a subclass using a named constructor defined in the parent class, you must implement that constructor in the subclass

Call the parent class non-default constructor

By default, the subclass constructor invokes the anonymous no-arg constructor of the parent, and the call will be in front of the subclass constructor function body code execution, if there is a list of initialization subclass constructor, then the initialization list before invoking the constructor of the parent is carried out, in general, the three call sequence is as follows:

  1. Initialization list
  2. A parameterless constructor for the parent class
  3. Constructor of the current class

If the parent class does not have an anonymous no-argument constructor, then the subclass must call one of the constructors of the parent class. Specifying a parent constructor for the subclass’s constructor is used only in front of the constructor body: specify. In the following example, the constructor of the Employee class calls the named constructor of the parent class Person:

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person'); }}class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}
Copy the code

Since the argument is passed to the parent constructor before the subclass constructor is executed, the argument can also be an expression, such as a function:

class Employee extends Person {
  Employee() : super.fromJson(defaultData);
  / /...
}
Copy the code

Note: Arguments passed to the parent constructor cannot use the this keyword, because at this point in the argument passing, the subclass constructor has not been executed and the instance object of the subclass has not been initialized, so all instance members cannot be accessed, but the class members can.

Initialization list

In addition to calling the superclass constructor, instance variables can be initialized before the constructor body is executed. Each instance variable is separated by a comma

// Initializer list sets instance variables before
// the constructor body runs.
// Use the initializer list to set the instance variable before the constructor body is executed.
Point.fromJson(Map<String.num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x.$y) ');
}
Copy the code

**⚠️ Note: the statement to the right of the initializer expression = cannot use the this keyword

Use assert statements during initialization to prevent arguments from being passed around
class Spacer extends StatelessWidget {
  const Spacer({Key key, this.flex = 1})
    : assert(flex ! =null),
      assert(flex > 0),
      super(key: key);

  final int flex;

  @override
  Widget build(BuildContext context) {
    return Expanded(
      flex: flex,
      child: constSizedBox.shrink(), ); }}Copy the code

In development mode, you can use assert in the initialization list to validate input data:

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x.$y) ');
}
Copy the code

Initializer lists are very useful for setting final fields, and the following example uses initializer lists to set the values of three final variables. Click the Run button to execute the sample code

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2.3);
  print(p.distanceFromOrigin);
}
Copy the code

Contrast conventional constructor parameters

import 'dart:math';

class Point {
  num x;
  num y;
  num distanceFromOrigin;

  Point(x, y) {
    x = x;
    y = y;
    distanceFromOrigin = sqrt(x * x + y * y);
  }
}

main() {
  var p = new Point(3.4);
  print(p.distanceFromOrigin);
}

Copy the code

Redirect constructor

Sometimes constructors in a class call other constructors in the class. The redirection constructor does not have a function body. After the function is signed, use (:) to specify the other constructors to be redirected to:

class Point {
  num x, y;
  // The primary constructor of the class.
  Point(this.x, this.y);
  // Delegate implementation to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}
Copy the code

Constant constructor

If the objects generated by the class are immutable, you can make them compile-time constants when they are generated. You can do this by prefixing the class constructor with the const keyword and making sure that all instance variables are final.

class ImmutablePoint {
  final num x, y;
  const ImmutablePoint(this.x, this.y);
  static final ImmutablePoint origin =const ImmutablePoint(0.0);
}
Copy the code

Instances created by constant constructors are not always constants, as described in the section Using constructors

Factory constructor

Identifying the constructor of a class with the factory keyword makes the constructor a factory constructor, which means that using the constructor to construct instances of the class does not always return a new instance object. For example, a factory constructor might return an instance from the cache, or an instance of a subtype. The following example demonstrates the factory constructor that returns an object from the cache:

class Logger {
  final String name;
  bool mute = false;


  // Build a private constructor
  Logger._internal(this.name);

  // Method 1: factory constructor, which creates an instance each time it is called
  factory Logger(String name) => createInstance(name);
  static Logger createInstance(String name) => new Logger._internal(name);

  // Method 2: factory constructor, which holds the point to a singleton instance
	// Cannot access this in the factory constructor
  //putIfAbsent is a Map method that obtains the value if the key is present, or adds it to the Map if it is absent and returns the value
  // the _cache variable is library private because it is preceded by an underscore.
  // static final Map<String, Logger> _cache = <String, Logger>{};
  // factory Logger(String name) {
  // return _cache.putIfAbsent(name, () => Logger._internal(name));
  // }

  // Method 3: This is a create singleton, return the first created instance
  // static var _singleton;
  // factory Logger(String name) {
  // if (_singleton == null) {
  // _singleton = Logger._internal(name);
  / /}
  // return _singleton;
  // }

  void log(String msg) {
    if(! mute)print(msg); }}// The factory constructor is called like any other constructor:
void main(List<String> args) {
  var logger1 = Logger('UI');
  var logger2 = Logger('UI');
  var logger3 = Logger('This is a different name, it will print a different hashCode.');
  print(logger1.hashCode);
  print(logger2.hashCode);
  print(logger3.hashCode);
  print(logger3.name);
  print(identical(logger1, logger2));
  logger1.log('Button clicked');
}
Copy the code
  • Note: This is not accessible in the factory constructor

Compare the factory function with the constructor

The constructor
class Symbol {
  static final Map<String.Symbol> cache = {};
  final String name;
  
  Symbol._internal(this.name);
  
  Symbol(this.name) {
    cache[name] = new Symbol._internal(this.name);
  }

 
}

main() {
  var a = new Symbol('something');
  var b = new Symbol('something');
  print(identical(a, b)); // false!
  print(Symbol.cache); //{something: Instance of 'Symbol'}
}
Copy the code
The factory function
class Symbol {
  final String name;
  static Map<String.Symbol> _cache = new Map<String.Symbol> ();Symbol._internal(this.name);
  
  factory Symbol(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final symbol = new Symbol._internal(name);
      _cache[name] = symbol;
      return symbol;
    }
  }

  
}


main() {
  var x = new Symbol('X');
  var alsoX = new Symbol('X');

  print(identical(x, alsoX));  // true
}
Copy the code

methods

Methods are functions that provide behavior for an object

Instance methods

Object instance methods have access to instance variables and this. The distanceTo() method below is an example of an instance method:

import 'dart:math';
class Point {
  num x, y;
  Point(this.x, this.y);
  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    returnsqrt(dx * dx + dy * dy); }}Copy the code

Getter and Setter

Getters and setters are a pair of special methods for reading and writing properties of objects. As mentioned above, there is an implicit Getter for each property of an instance object, and a Setter for non-final properties. You can add getters and setters for additional attributes using the get and set keywords:

class Rectangle {
  num left, top, width, height;
  Rectangle(this.left, this.top, this.width, this.height);
  // Define two attributes generated by the calculation: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}
void main() {
  var rect = Rectangle(3.4.20.15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == - 8 -);
}
Copy the code

The advantage of using getters and setters is that you can use your instance variables and wrap them in methods over time without changing any code, meaning that you define them and then change them without affecting the original logic

Note: Operators like increment (++) execute correctly regardless of whether a Getter is defined. To avoid unnecessary exceptions, the operator calls the Getter once and stores its value in a temporary variable

Abstract methods

Instance methods, getters, and setters can all be abstract. Define an interface method without implementing it and let the class that implements it implement it. Abstract methods can only exist in abstract classes using semicolons. An alternative method body can declare an abstract method:

abstract class Doer {
  // Define instance variables and methods...
  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so here the method is no longer abstract...}}Copy the code

An abstract class

Using the abstract keyword to identify a class makes it an abstract class that cannot be instantiated. Abstract classes are often used to declare interface methods and sometimes have concrete method implementations. If you want an abstract class to be instantiated at the same time, you can define a factory constructor for it. Abstract classes often contain abstract methods. Here is an example of an abstract class that declares an abstract method:

// This class is declared abstract and thus
// can't be instantiated.
// The class is declared abstract, so it cannot be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods, etc...
  void updateChildren(); // Abstract methods.
}
Copy the code

Implicit interface

Each class implicitly defines and implements an interface that contains all instance members of the class and other interfaces that the class implements. If you want to create A class A API that supports calling class B without inheriting from class B, you can implement A class B interface. A class implements one or more interfaces and implements the API defined by each interface using the keyword implements:

// A person. The implicit interface contains greet().
The Person class contains the greet() method in its implicit interface.
class Person {
  // the _name variable is also included in the interface, but it is only visible in the library.
  final _name;

  // The constructor is not in the interface.
  Person(this._name);

  // Greet () on the interface.
  String greet(String who) => 'hello,$who. I am a$_name. ';
}

// An implementation of the Person interface
class Impostor implements Person {
  get _name => ' ';

  String greet(String who) => 'how are you$who. Do you know who I am? ';
}

String greetBob(Person person) => person.greet('Stranger');

void main() {
  print(greetBob(Person('the little non-success')));
  print(greetBob(Impostor()));
}

// Hello, stranger. I'm Xiao Yun.
// Hello stranger. Do you know who I am?
Copy the code

If you need to implement multiple interface classes, you can separate each interface class with a comma:

class Point implements Comparable.Location {... }Copy the code

Extending a class

Create a subclass using the extends keyword and reference a parent class using the super keyword:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  / /...
}
class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  / /...
}
Copy the code

Overriding class member

Subclasses can override instance methods, getters, and Setter methods of their parent classes. You can use the @override annotation to indicate that you have overridden a member:

class SmartTelevision extends Television {
  @override
  voidturnOn() {... }/ /...
}
Copy the code

Limiting the types of method parameters and instance variables makes your code more type-safe, and you can use covariant keywords

Rewriting operator

You can override all of the operators listed in the following table in a single class. For example, if you have a Vector representing a class of vectors, consider overriding the + operator to handle adding two vectors.

< + | []
> / ^ [] =
< = ~ / & ~
> = * << = =
% >>  

Note: Be careful! The = operator is not an overridden operator. The expression E1! = e2 is just! (e1 == e2) a syntactic sugar. Here is an example of overriding the + and – operators:

class Vector {
  final int x, y;
  Vector(this.x, this.y);
  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
  The implementations of the == and hashCode operators are not shown here; see the notes below for details.
  / /...
}
void main() {
  final v = Vector(2.3);
  final w = Vector(2.2);
  assert(v + w == Vector(4.5));
  assert(v - w == Vector(0.1));
}
Copy the code

If you override the == operator, you must also override the Getter method of the object hashCode. You can check out implementing mapping keys for more examples of overwriting == and hashCode and you can check out extending a class for more information about overwriting

noSuchMethod()

If calling a method or instance variable that does not exist on the object will trigger the noSuchMethod method, you can override the noSuchMethod method to track and record this behavior:

class A {
  // Unless you override noSuchMethod, calling a nonexistent member will cause NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
  print('You tried using a non-existent member:' +
  '${invocation.memberName}'); }}Copy the code

You cannot call an unimplemented method unless one of the following conditions is true:

  • The receiver is static and of type Dynamic
  • The receiver has a static type that defines an unimplemented method (abstraction is also possible), and the receiver’s dynamic type implements the noSuchMethod method and implements it differently than Object

You can refer to the noSuchMethod forwarding specification for more information

The Extension method

The Extension method, introduced in Dart 2.7, is a way to add functionality to an existing library. You may not even know there is an Extension method. For example, when you use code completion in the IDE, it recommends using the Extension method in conjunction with regular methods. Here is an example of using the Extension method in String, which we call parseInt(), It is defined in string_apis. Dart:

import 'string_apis.dart'; .print(The '42'.padLeft(5)); // Use a String method.
print(The '42'.parseInt()); // Use an extension method.
Copy the code

For more information about using and implementing the Extension method, see the Extension Methods page

Enumerated type

Enumeration types are special types, also called Enumerations or enums, that define a fixed number of constant values.

Use enumerated

Use the keyword enum to define enumeration types:

enum Color { red, green, blue }
Copy the code

Each enumerated value has a Getter method called the index member variable, which returns the positional value of the base index at 0. For example, the index of the first enumeration value is 0 and the index of the second enumeration value is 1. And so on.

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
Copy the code

You can use the values method of an enumeration class to get a list of all enumerated values:

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
Copy the code

You can use enumerations in Switch statements, but note that each case of an enumerated value must be handled. That is, each enumerated value must be a case clause, otherwise a warning will appear:

var aColor = Color.blue;
switch (aColor) {
  case Color.red:
    print('Red as a rose! ');
    break;
  case Color.green:
    print('Green as the prairie! ');
    break;
  default: // A warning will appear without the statement.
    print(aColor); // 'Color.blue'
}
Copy the code

Enumerated types have two limitations:

  • Enumerations cannot be subclasses, they cannot be mixins, and you cannot implement an enumeration.
  • An enumerated class cannot be explicitly instantiated.

You can refer to the Dart Programming Language Specification [] for more information.

Use mixins to add functionality to classes

Mixin is a method pattern for reusing code from a class in multiple inheritance

Use the Mixin pattern using the with keyword followed by the name of the Mixin class:

class Musician extends Performer with Musical {
  / /...
}
class Maestro extends Person with Musical.Aggressive.Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true; }}Copy the code

Define a class that inherits from Object and does not define a constructor for the class. This class is a Mixin class. Unless you want the class to be used as a normal class, you can use the keyword Mixin instead of class to make it a pure Mixin class:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;
  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self'); }}}Copy the code

You can use the keyword on to specify which classes can use the Mixin class. For example, if there is A Mixin class A, but A can only be used by class B, you can define A like this:

mixin MusicalPerformer on Musician {
  / /...
}
Copy the code

Release note: The mixin keyword is only supported by reference in Dart 2.1. Code in earlier versions usually uses abstract class instead. You can consult the Dart SDK change log and the 2.1 Mixin specification for more information about mixins changing in 2.1.

Class variables and methods

The static keyword is used to declare class variables or class methods.

A static variable

Static variables (class variables) are often used to declare state variables and constants that belong within the scope of a class:

class Queue {
  static const initialCapacity = 16;
  / /...
}
void main() {
  assert(Queue.initialCapacity == 16);
}
Copy the code

Static variables are initialized the first time they are used. Note: The code follows the naming rules in the style recommendation guide and uses camel case to name constants.

A static method

Static methods (that is, class methods) cannot be accessed by an instance of a class. Likewise, this cannot be used within static methods:

import 'dart:math';
class Point {
  num x, y;
  Point(this.x, this.y);
  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    returnsqrt(dx * dx + dy * dy); }}void main() {
  var a = Point(2.2);
  var b = Point(4.4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}
Copy the code

Note: For some generic or common static methods, you should define them as top-level functions rather than static methods. Static methods can be used as compile-time constants. For example, you can pass a static method as an argument to a constant constructor.