Classes in Dart, Part One

(This is part 12 in an ongoing series on Dart. See part 11, Booleans in Dart.)

Intro

Dart is a familiar object oriented programming language, with classes, single inheritance, and interfaces. You can build classes and instantiate objects pretty much like how you would expect, with a few caveats and pleasant surprises. This post is the first in a series of posts that will explore classes in Dart.

If you think you need to use classes when programming in Dart, think again. Dart is a scripting language, which implies you can write "short and sweet" scripts and programs with ease. Dart functions very well with only, well, er,  functions, alleviating the requirement to build classes. In fact, I've made it through eleven posts here in this series without talking about classes. You can get far with just functions in Dart.

Dart is built to be scalable, helping you as your program grows in scope. You may start with a collection of functions, but as complexity increases, eventually you'll want to organize your code into classes.

Class

Straight from the language spec:
A class has constructors,  instance members and static members. The instance members of a class are its instance methods, getters, setters and instance variables. The static members of a class are its static methods, getters, setters and static variables. Every class has a single superclass except class Object which has no superclass.
 Translated, the above says:
  • Dart's classes have single inheritance
  • Dart's classes have constructors
  • Dart's classes have instance members like methods, getters, setters, and variables
  • Dart's classes can also have static methods, getters, setters, and variables
Single inheritance means each class has a single superclass. A class may only inherit from one other class.

Constructors are special methods on a class that initialize a new instance of a class. Constructors don't return anything. Dart supports traditional constructors (methods named the same as the class) and "named constructors". We will see examples of both shortly.

Instance members like instance methods and instance variables can manipulate the state of the object, and define the state of an object, respectively. An "instance" or "object instance" or "object" is a real, true instantiation of the class. Your normal interaction with classes is to instantiate, or create, a new instance of the class. This new instantiation is an object.

Static members, on the other hand, are methods and state of the class itself. Sometimes, it's helpful to define special behavior that doesn't require an object instance.

Hopefully this all sounds familiar. If you are new to class based object oriented languages, try to think about classes as blueprints for the things in your program. Each thing, or object, has state (its properties) and behavior or abilities (its methods).

Fields

A simple Dart class is defined like this:


class InputField {
  int maxLength;
  String name;
}

This class, named InputField, has two fields: maxLength and name. These fields define the state of the object, or what data it can hold.

The default value of fields and variables is null. You may only assign a const (aka compile-time constant) value to a field initialized in the body of a class. For example:


class InputField {
  int maxSize = 20; // OK! a literal num is a constant
  String name;
}

You may not use a non-const value like this:

class InputField {
  int maxSize = 20;
  String name;

  // ERROR: expecting const expression
  Date initializeAt = new Date(); 
}

Dart is designed to support consistent initialization of objects. One cannot observe an object before its initialization is complete. To achieve that, the preferred way to initialize instance variables in Dart is via the constructor's initializer, where the parameters to the constructor are available and access to this is restricted. Initializing variables at the point of declaration was added as a convenience for simple cases.

NOTE: there is a discussion to simplify the syntax to allow you to set a non-const value to a field in the body of a class (like Date initializeAt = new Date()), by transparently moving the initialization down into the constructor. Stand by for more news on this.

An implicit getter is generated for each field. If the field is non-final, an implicit setter is also generated for each field.

For example, if you add a field like int maxSize, the following will be automatically added to the class:
  • int get maxSize() => this.maxSize;
  • void set maxSize(int x) => this.maxSize = x;
Warning: it is a compile time error to manually declare a getter or setter with the same name as a field or method!

Create an object, accessing fields

Once you have a class defined, you can create new instances:

InputField field = new InputField();

Setting and getting fields, via the automatically provided setters and getters, is easy:

field.maxSize = 30;
field.name = 'first-name';
print(field.name); // 'first-name'




This, of course, means that you can't create truly private fields for a class, because any field you define will receive a corresponding getting and (unless the field is final) a setter.

We have not yet discussed the visibility rules for Dart yet, because we have not introduced libraries (which create the boundaries for any private fields or methods.)

Constructors

As you saw in the code above, if you do not specify a constructor, a default constructor will be provided.

You can also specify your own constructors. For example:

class InputField {
  int maxSize;
  String name;

  InputField(this.maxLength, this.name);
}

This constructor that takes two parameters, and uses a cool Dart syntax shortcut to set the field values by matching the names of the parameters to the fields.

The above example constructor is equivalent to:

  InputField(maxSize, name) {
    this.maxSize = maxSize;
    this.name = name;
  }

But of course, that's the long way to do it. You can also mix the shorthand with a full constructor like this:

  InputField(this.maxSize, this.name) {
    renderComponent();
  }

If you define a constructor, no default zero-parameter constructor will be provided. This means, given this class definition...

class InputField {
  int maxSize;
  String name;

  InputField(this.maxLength, this.name);
}


... you will be unable to create a new object like this:

var field = new InputField(); // COMPILE TIME ERROR: no matching constructor

Dart does not allow for constructor overloading, which means you cannot have two constructors of the same name defined for the same class. Luckily, thanks to Dart's optional method arguments, this is less of a problem than you might think.

For example, using both optional arguments and default argument values:

class InputField {
  int maxSize;
  String name;
  
  InputField([this.type, this.maxSize = 20]);
}

The following instantiations will all work:

var field = new InputField();                 // works!
field.name;    // null

var field = new InputField(maxSize: 40);      // works!
field.maxSize; // 40

var field = new InputField('first-name', 20); // works!
field.name;    // first-name
field.maxSize; // 20

Named constructors

Another way to work around the lack of constructor overloading, as well as provide more clarity to your users of your class, is to use Dart's cool named constructors.

class InputField {
  int maxSize;
  String name;
  
  InputField([this.name, this.maxSize = 20]);
  
  InputField.Small(this.name) {
    this.maxSize = 10;
  }
}

You can instantiate an object using a named constructor like this:

InputField field = new InputField.Small('middle-initial');

Summary

Dart is a class based object oriented programming language. Classes have fields defined in the body of the class, and the fields can be initialized with compile-time constants or by values passed in from the constructor. Dart will generate a getter for each field, and if the field is non-null, a setter will also be generated.

If you do not specify a constructor, a default constructor will be provided for you. You cannot overload the constructor, but you can use either named constructors or optional arguments to provide options for initialization.

There is a lot more to cover with classes, this just skims the surface! We'll continue with classes in our next post.

Next Steps

Read more of my Dart posts, try Dart in your browser, browse the API docs, or file an issue request. Dart is getting ready, please send us your feedback!

Popular posts from this blog

Lists and arrays in Dart

Converting Array to List in Scala

Null-aware operators in Dart