Description:

Starting with this article, we continue lecture 5 of the Dart Syntax article, object-oriented basics in Dart. We know that everything is an object in Dart, so object orientation is very important in Dart development. It is also slightly different from other things, such as multiple inheritance mixins, constructors that cannot be overridden, accessor functions for setters and getters, etc.

Setters and getters for accessors

The Dart class has a special function in its properties to facilitate access to its values: the setter and getter property accessor functions. In fact, dart always has a setter and getter for each instance property (only getters for final modified read-only properties, and both setters and getters for mutable properties). Assigning a value to an instance property or getting a value is actually all internal calls to setter and getter functions.

Property accessor function setters

Setter function names are prefixed with set and take only one argument. Setter call syntax is the same as traditional variable assignment. If an instance property is mutable, then a setter property accessor function is automatically defined for it, and all instance property assignments are actually calls to setter functions. This is very similar to the setter and getter in Kotlin.

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);
  set right(num value) => left = value - width;// Use set as a prefix and take only one argument value
  set bottom(num value) => top = value - height;// Use set as a prefix and take only one argument value
}

main() {
  var rect = Rectangle(3.4.20.15);
  rect.right = 15;// When calling setter functions, you can directly call the right function using a property-assignment style.
  rect.bottom = 12;// When calling setter functions, the bottom function can be called directly using a property-assignment style.
}
Copy the code

Compare the implementation in Kotlin

class Rectangle(var left: Int.var top: Int.var width: Int.var height: Int) {
    var right: Int = 0// In kotlin, var is variable and val is read-only
        set(value) {Setters are defined in kotlin
            field = value
            left = value - width
        }
    var bottom: Int = 0
        set(value) {Setters are defined in kotlin
            field = value
            top = value - height
        }
}

fun main(args: Array<String>) {
    val rect = Rectangle(3.4.20.15);
    rect.right = 15// When calling setter functions, you can directly call the right function using a property-assignment style.
    rect.bottom = 12// When calling setter functions, the bottom function can be called directly using a property-assignment style.
}
Copy the code

Property accessor function getter

All instance properties in Dart are accessed by calling the getter function. Each instance amount line always has a getter associated with it, provided by the Dart compiler.

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);
  get square => width * height; ;// Use get as prefix and getter to calculate the area.
  set right(num value) => left = value - width;// Use set as a prefix and take only one argument value
  set bottom(num value) => top = value - height;// Use set as a prefix and take only one argument value
}

main() {
  var rect = Rectangle(3.4.20.15);
  rect.right = 15;// When calling setter functions, you can directly call the right function using a property-assignment style.
  rect.bottom = 12;// When calling setter functions, the bottom function can be called directly using a property-assignment style.
  print('the rect square is ${rect.square}');When calling the getter function, you can directly call the square function in the same way as reading the property value.
}
Copy the code

Compare the Kotlin implementation

class Rectangle(var left: Int.var top: Int.var width: Int.var height: Int) {
    var right: Int = 0
        set(value) {
            field = value
            left = value - width
        }
    var bottom: Int = 0
        set(value) {
            field = value
            top = value - height
        }

    val square: Int// Since only read-only is involved, val is used
        get() = width * heightGetters are defined in kotlin
}

fun main(args: Array<String>) {
    val rect = Rectangle(3.4.20.15);
    rect.right = 15
    rect.bottom = 12
    println(rect.square)When calling the getter function, you can directly call the square function in the same way as reading the property value.
}
Copy the code

3. Application scenarios of attribute accessor functions

In fact, the above setter and getter functions do exactly what normal functions do. But if you use the setter,getter form, it’s more of a code specification. So since ordinary functions can do that, when do you use setters, getters, when do you use ordinary functions. This has to be translated into the question of which scenario to define a property or a function (a question that was explained in detail in Kotlin’s syntax discussion a long time ago). We all know that functions describe actions, while properties describe state data structures (states may be computed from multiple property values). If you have a class that needs to expose some state in the class, you’re better off using setters, getters; If you are triggering a behavior operation in a class, ordinary functions are more appropriate.

For example, in this example, the draw rectangle action is better implemented using a normal function, and the square rectangle area is better implemented using a getter function.

class Rectangle {
  num left, top, width, height;

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

  set right(num value) => left = value - width; // Use set as a prefix and take only one argument value
  set bottom(num value) => top = value - height; // Use set as a prefix and take only one argument value
  get square => width * height; The getter function calculates the area and describes the Rectangle state properties
  bool draw() {
    print('draw rect'); //draw draws function, trigger is action behavior
    return true;
  }
}

main() {
  var rect = Rectangle(3.4.20.15);
  rect.right = 15; // When calling setter functions, you can directly call the right function using a property-assignment style.
  rect.bottom = 12; // When calling setter functions, the bottom function can be called directly using a property-assignment style.
  print('the rect square is ${rect.square}');
  rect.draw();
}
Copy the code

Variables in object orientation

1. Instance variables

Instance variables are actually member variables of a class, or member properties. When you declare an instance variable, it ensures that each object instance has a copy of its unique properties. To indicate instance private attributes, underline the attribute name, such as _width and _height

class Rectangle {
  num left, top, _width, _height;The left,top,_width, and _height attributes are declared. When uninitialized, their default values are null
}  
Copy the code

Left, top, width, and height in the above example all automatically introduce a getter and setter. In fact, no property is accessed directly in DART. All references to a field property are calls to the property accessor function, and only the accessor function has direct access to its state.

Class variables (static variables) and top-level variables

Class variables are static variables that belong to the scope of the class. A top-level variable is a variable that is defined not in a specific class, but in the entire code file, which is the top level of the file, which is similar to a top-level function. Static variables are more commonly referred to as static variables, but static variables in Dart include not only static variables but also top-level variables.

Actually for class variables and top access or by calling the accessor functions to achieve, but the class variables and the top is a little special, they are lazy initialization, the getter function is called for the first time the top class variables or variable to perform initialization, namely reference classes variables or the top for the first time. The default value is null if the class variable or top-level variable is not initialized.

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {
    Cat() {
        print("I'm a Cat!"); }}// Note that the variable is not defined in any concrete class, so this animal is a top-level variable.
// Although it looks like a Cat object was created, there is no Cat object created at all due to the lazy initialization of the top-level variable
Animal animal = Cat();
main() {
    animal = Dog();// Then the animal reference points to a new Dog object,
}
Copy the code

The top-level variable has a lazy initialization process, so the Cat object is not created, because the whole code does not access Animal, so it cannot trigger the first getter function, so the Cat object is not created, which means that it does not say I’m a Cat! This sentence. This is why top-level variables are lazily initialized, as are static variables.

3. Final variables

Dart modifies a variable with the final keyword to indicate that the variable cannot be modified after initialization. Final variables have only getter accessor functions, not setter functions. A variable similar to the val modifier in Kotlin. Variables declared as final must be initialized before the instance method runs, so there are many ways to initialize a final variable. Note: It is recommended that variables be declared with final whenever possible

class Person {
    final String gender = 'male';// This method is limited. It is ok for basic data types, but not suitable for object types.
    final String name;
    final int age;
    Person(this.name, this.age);// Initialize final variables with constructors.
    // The above code is equivalent to the following implementation
    Person(String name, int age) {
        this.name = name;
        this.age = age; }}Copy the code

The difference between final and const is similar to the difference between Kotlin val and const val, where const is initialized at compile time and final is initialized at run time.

4. Constant objects

Dart has some objects that are constants that can be evaluated at compile time, so dart supports the definition of constant objects. Constant objects are created using the const keyword. The creation of a constant object also calls the class’s constructor, but it must be a constant constructor, which is decorated with the const keyword. A constant constructor must be a number, a Boolean, or a string, and a constant constructor cannot have a function body, but it can have an initializer list.

class Point {
    final double x, y;
    const Point(this.x, this.y);// Constant constructor, uses const keyword and has no function body
}

main() {
    const defaultPoint = const Point(0.0);// Create a constant object
}
Copy the code

Constructors

1. Primary constructor

The primary constructor is the most common type of constructor for creating objects in Dart, and there can only be one primary constructor. If no primary constructor is specified, a default primary constructor with no parameters is automatically assigned by default. In addition, constructors in DART do not support overloading

class Person {
    var name;
    // Hide the default no-argument constructor Person();
}
// This is equivalent to:
class Person {
    var name;
    Person();// A function with the same class name is usually called the primary constructor
}
/ / equivalent to the
class Person {
    var name;
    Person(){}
}

class Person {
    final String name;
    final int age;
    Person(this.name, this.age);// Explicitly declare the main constructor with parameters
    Person();Dart does not support overloading multiple constructors at once.
}
Copy the code

Constructor initializer list:

class Point3D extends Point {
    double z;
    Point3D(a, b, c): z = c / 2.super(a, b);// Initializer list, multiple initializer steps separated by commas; Z is initialized, then super(a, b) calls the constructor of the parent class
}
/ / equivalent to the
class Point3D extends Point {
    double z;
    Point3D(a, b, c): z = c / 2;// If the initializer does not call the parent constructor,
    // Then there is an implicit superclass constructor super call that is added by default to the end of the initializer list
}
Copy the code

The initialization sequence is shown below:

Several ways to initialize instance variables

// Method 1: initialize by assigning the default value directly to the instance variable declaration
class Point {
    double x = 0, y = 0;
}

// Method two: use the constructor initialization method
class Point {
    double x, y;
    Point(this.x, this.y);
}

// Method 3: Initialize using the initialization list
class Point {
    double x, y;
    Point(double a, double b): x = a, y = b;//: initializes the list
}

// Method 4: initialize in the constructor. Note that this is a little different from method 2.
class Point {
    double x, y;
    Point(double a, doubleb) { x = a; y = b; }}Copy the code

2. Name the constructor

Dart constructors do not support overloading. In fact, Dart does not support function overloading even for basic normal functions. The problem is that we often encounter scenarios where constructors are overloaded, and sometimes we need to specify different constructor parameters to create different objects. In order to solve the problem of creating objects with different parameters, the concept of named constructors is introduced instead of function overloading. It can specify any argument list to build the object, but you need to give the constructor a specific name.

class Person {
  final String name;
  int age;

  Person(this.name, this.age);

  Person.withName(this.name);// Define the named constructor withName with the class name. function name. You only need the name parameter to create an object,
  If there is no named constructor, in other languages we usually use function overloading.
}

main () {
  var person = Person('mikyou'.18);// Create an object through the main constructor
  var personWithName = Person.withName('mikyou');// Create an object by naming the constructor
}
Copy the code

Redirect constructor

Sometimes you need to redirect a constructor to another constructor in the same class. The body of the redirect constructor is empty, and the constructor call appears after the colon (:).

class Point {
    double x, y;
    Point(this.x, this.y);
    Point.withX(double x): this(x, 0);// Note that this is used to redirect to the Point(double x, double y) main constructor.
}
/ / or
import 'dart:math';

class Point {
  double distance;

  Point.withDistance(this.distance);

  Point(double x, double y) : this.withDistance(sqrt(x * x + y * y));// Note that the main constructor is redirected to the named constructor withDistance.
}
Copy the code

4, factory factory constructor

In general, constructors always create a new instance object. But there are times when you don’t need to create a new instance every time, and you may need to use caching, which is difficult to do with just the normal constructor above. The Factory factory constructor is needed. It uses the factory keyword to decorate the constructor and can either return a created instance from the cache or return a new one. Any constructor can be replaced with a factory method in DART. It looks like a normal constructor. It may not have an initializer list or initialization parameters, but it must have a function body that returns an object.

class Logger {
  // Instance properties
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};/ / class attribute

  factory Logger(String name) {// Use the factory keyword to declare the factory constructor,
    if (_cache.containsKey(name)) {
      return _cache[name]// Return that the cache has created an instance
    } else {
      final logger = Logger._internal(name);// If the name logger is not found in the cache, call the _internal named constructor to create a new logger instance
      _cache[name] = logger;// Add the instance to the cache
      return logger;// Note that the newly created instance is returned at the end
    }
  }

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

  void log(String msg) {// Instance method
    if(! mute)print(msg); }}Copy the code

Abstract methods, classes, and interfaces

Abstract methods declare a method without providing its implementation. Methods of any instance can be abstract, including getters, setters, operators, or ordinary methods. A class that contains an abstract method is itself an abstract class, which is declared using the keyword abstract.

abstract class Person {//abstract declares an abstract class
  String name();// Abstract ordinary methods
  get age;/ / abstract getter
}

class Student extends Person {// Uses extends inheritance
  @override
  String name() {
    // TODO: implement name
    return null;
  }

  @override
  // TODO: implement age
  get age => null;
}
Copy the code

Dart does not have an interface keyword as in other languages. Because each class in Dart implicitly defines an interface by default.

abstract class Speaking {// Although the abstract class is defined, the interface Speaking is implicitly defined
  String speak();
}

abstract class Writing {// Although the abstract class is defined, the interface Writing is implicitly defined
  String write();
}

class Student implements Speaking.Writing {// implements the interface using the implements keyword
  @override
  String speak() {// Override the speak method
    // TODO: implement speak
    return null;
  }

  @override
  String write() {// Override the write method
    // TODO: implement write
    return null; }}Copy the code

Class functions

A class function is a function of a class. It does not belong to any instance, so it cannot be inherited. Class functions are decorated with the static keyword and can be called using the class name directly. Function name.

class Point {
    double x,y;
    Point(this.x, this.y);
    static double distance(Point p1, Point p2) {// Use the static keyword to define class functions.
        var dx = p1.x - p2.x;
        var dy = p1.y - p2.y;
        return sqrt(dx * dx + dy * dy);
    }
}
main() {
    var point1 = Point(2.3);
    var point2 = Point(3.4);
    print('the distance is ${Point.distance(point1, point2)}');// Use point.distance => class name. Call by function name
}
Copy the code

conclusion

This article focuses on the constructors commonly used in DART and some object-oriented basics. In the next article, we will continue with some advanced aspects of OBJECT-ORIENTED in DART, such as object-oriented inheritance and mixins. Welcome to follow ~~~

My official account

Welcome to: Mr. Xiong Meow