“This is the 27th day of my participation in the Gwen Challenge in November. See details of the event: The Last Gwen Challenge in 2021”
This article is adapted from the Members section of the Dart Efficient Code.
preface
Members include Properties and Methods of a class. Developing good class member definitions and usage habits not only makes the code clearer, but also improves the robustness of the program. This article explains how to use class members correctly.
Suggestion 1: Make it a priority to define fields or top-level variables as final
Defining fields or top-level variables as final indicates that they are immutable during runtime. Classes or libraries should minimize the scope of variability to improve the maintainability of code. Of course, this does not mean that mutable data is not needed, but that mutable fields or variables should not be defined unless necessary, and that it is better to use the final definition whenever possible, even if it needs to be changed later than to define mutable data in the first place. If a member attribute cannot be initialized initially, but cannot be changed after initialization, then the late final definition can be used:
class Student {
late final name;
int age;
Student({required this.name, required this.age});
}
Copy the code
Suggestion 2: Use getters for operations that conceptually access class properties
We can get a class member using either a getter or a method. There may seem to be little difference between the two approaches, but the choice of API design matters. Many other programming languages that consider encapsulation need to use the getXX() method to get object property values. Only those defined as public members can be accessed using.xx. For example, Java code:
public class Circle {
public Circle(double radius) {
this.radius = radius;
}
private double radius;
public double getRadius(a) {
returnradius; }}Copy the code
Dart is not all that is used. The XX approach can be viewed as member access, including counting members. The fields of the class are special because the Dart language itself implements getter access for them. Furthermore, replacing a method with a getter to access a property gives the caller a signal that this is an operation in the form of a field on the object. This operation tells the caller the following information:
- This operation takes no arguments and returns a result;
- The caller only needs to care about the result, not how it is implemented, or very little. The verb + noun combination is more about the caller doing a job. Of course, this doesn’t mean that getters run faster; in fact, iterableBase.length is O(n) computationally complex (IterableBase. For some important calculations, it’s okay to use getters. But if a getter does a lot of work, it’s more of a job, and the use of methods is more accurate in expressing the intent. For example, the following is a counter example to getter usage:
// Error example
connection.nextIncomingMessage; // Network I/O needs to be handled
expression.normalForm; // Exponential calculations may be required
Copy the code
- Getter operations have no side effects visible to the user. This may be a little hard to understand, but it actually means that when the caller accesses an object property using the getter, the object or program state does not change. You don’t get output in getters, you write to files, you change other properties of objects, and so on. Of course, if the actions in the getter don’t affect what the user sees, then it’s okay to do other things, such as delaying the calculation and storing the results, writing to the cache, and so on — as long as the caller doesn’t care about the effects.
// Error example
stdout.newline; // Output is generated
list.clear; // Modify the object
Copy the code
- Operations are idempotent. “Idempotent” means that in the current context, unless the object state has changed during the call, the result should be the same no matter how many calls are made. Let’s say we get
list.length
As long as the list has not been added, changed, or deleted, the length of the returned list should be the same each time. For example, the following method is not suitable for using getters because the timestamp is obtained differently each time.
// Error example
DateTime.now;
Copy the code
- An object returned by a getter should not expose the entire object. If you want to expose the entire object, it’s like a transformation of the object, and you should use the toX() or asX() method, for example
toString()
.toJson()
.asMap()
And so on.
If the result we want to return conforms to the above points, then we can use the getter. It may seem like it’s rarely enough, but there are a lot of situations where you can do it. Here are some typical examples:
rectangle.area;
collection.isEmpty;
button.canShow;
dataSet.minimumValue;
Copy the code
Recommendation 3: Use setters as operations that conceptually change class properties
Similar to getters, how to choose between a settter or a function depends on whether the operation is field-like. Setters should be used if the following three properties are true:
- The operation takes only one argument and returns no value.
- The operation changes the state data of the object;
- Operations are idempotent. Idempotent here means that if you call the setter twice with the same argument, the second time it should look like you did nothing. Inside the setter, of course, there could be cache invalidation or logging, but from the caller’s point of view, the second time, nothing happens.
Here are two typical examples:
rectangle.width = 3;
button.visible = false;
Copy the code
Tip 4: If you don’t have a getter, you shouldn’t define a setter
In general, getters and setters are visible properties that represent objects. Giving a property a setter without a getter would be strange and awkward to use. For example, for a property without a getter, you can use the assignment operator “=”, but not “+=”, which requires getter access. The headline suggestion is not that you should add getter access to accommodate the setter. In fact, if you have only the setter and no getter, it is more appropriate to write a method to change the object property.
Recommendation 5: Do not use runtime type detection for “pseudo-” overloading
Note that method overload is not supported in the Dart language. Many object-oriented languages support the same method name but different forms of argument lists to implement method overloading. Dart does not support overloading. For example, the following code will report an error.Of course, you can define a method with arguments of type dynamic and then use “is xx” inside the method to implement a “pseudo-overload”, as in the following:
// Error example
void fakeOverload([dynamic type]) {
if (type is String) {
// Process strings
} else if (type is num) {
// Process the value
} else {
/ /...}}Copy the code
If the code layer clearly knows what to do with arguments of that type, it should define separate methods so that business code is clearer and code errors can be avoided through static type checking. The core of this advice is not to use runtime types for method overloading. Suppose that some interfaces do not know what the incoming type is, and then expect the interface to do the corresponding operation through IS judgment. For example, Reducer, which dealt with Action responses in Redux, did this.
CounterState counterReducer(CounterState state, dynamic action) {
if (action is CounterAddAction) {
return CounterState(state.count + 1);
}
if (action is CounterSubAction) {
return CounterState(state.count - 1);
}
return state;
}
Copy the code
Suggestion 6: Avoid defining public Late final fields without initialization
This one is pretty obvious, if you define a public Late final field that is not initialized, because the setter is public, that means the initialization of that field might be done externally. There’s a lot of uncertainty about when you can use this field — bugs can crop up at any time. Therefore, if you want to define late final fields, you should follow the following rules:
- Do not apply
late
; - use
late
Initializes the field at the same time - use
late
, but set to private, providing public getter access externally.
Tip 7: Avoid returning Future, Stream, or collection that might be empty
When an interface returns a container object, there are two ways to indicate that there is no data: return an empty container or return NULL. Usually we prefer to return an empty container to indicate that there is no data. At this point, we can call a method or access an isEmpty-like property on the return value. Therefore, when there is absolutely no data to supply, it is recommended to return an empty collection, a non-empty Future (which can wrap an empty data type), or a stream that sends no value.
Tip 8: Do not return this for chaining operations
The Dart to use.. The chain operator does the chain operation, so there is no need to manually return this to support the chain operation. Here are two examples of comparison.
// Correct example
var buffer = StringBuffer()
..write('one')
..write('two')
..write('three');
// Error example
var buffer = StringBuffer()
.write('one')
.write('two')
.write('three');
Copy the code
conclusion
The Dart language has some subtle differences from other languages. Most of these differences may not be obvious, but when you do use them, you need to use Dart in its own right, not the conventions of other languages. For example, the following four points need special attention:
- Getters and setters need to support idempotence, which means that getter operations on an object’s property return the same value for multiple times without making any other changes to the object. In addition, when a setter operation is performed on the same parameter, any subsequent setter operation is considered invalid.
- Instead of providing a setter without a getter, you should write a separate method that modifies the property instead of using a setter.
- Dart does not support method overloading, so do not use dynamic typing to fake overloading. Instead, write handling methods for specific types.
- Instead of returning this, use.. Operators.
I am dao Code Farmer with the same name as my wechat official account. This is a column about the introduction and practice of Flutter, providing systematic learning articles about Flutter. See the corresponding source code here: The source code of Flutter Introduction and Practical column. If you have any questions, please add me to the wechat account: island-coder.
👍🏻 : feel the harvest please point a praise to encourage!
🌟 : Collect articles, easy to look back!
💬 : Comment exchange, mutual progress!