Appendix B. Defining classes and libraries

This appendix discusses building classes and using libraries and privacy.

B.1. Classes and interfaces

Dart is a class-based, object-orientated language with single inheritance and multiple interfaces. Dart has explicit classes and implicit interfaces: that is, a class definition implicitly defines an interface on its public properties and methods that other classes can implement.

 

Note

In the initial release of Dart, there were explicit interfaces defined using the interface keyword. After feedback from Dart’s early adopters, it was discovered that because an abstract class definition also defines an interface, the interface keyword was redundant.

 

B.1.1. Defining classes

The class keyword is used to define a class. Classes must be defined in top-level scope (that is, you can’t define a class in a function, method, or other class):

You create an instance of a class by using the new keyword:

You use the class name as type information throughout your application when assigning an instance into a variable or defining a function parameter. Just as with the built-in types (String, int), using that type information is optional, but the Dart tools will validate your code if you annotate your variables and parameters with type information:

Properties

Classes can have properties, which are attributes that describe the class. The following class describes an animal that has a number of legs and a color. The number of legs is strongly typed to be an integer, and the color is dynamically typed and can contain any type:

 

Note

Early in your application’s development, it’s acceptable to use dynamic typing for properties in classes, especially when you haven’t decided what type a property will become. As development progresses, though, you should aim to change dynamic types, such as the color property, into strong types that can be validated by the tools. The following example shows how dynamic typing is useful in early stages of development.

 

You access the properties on an instance of a class using dot notation syntax, similar to that used in Java and JavaScript. The syntax for reading and writing properties is identical:

You can initialize properties with an initial value and make properties constant by using the final keyword. Again, you can mix strong typing and optional typing. Every instance of the class will have the same starting values. If you don’t initialize a property, its starting value is null, just like a variable:

 

Note

See the section on constructors for more on providing initial and final values at runtime rather than in the class definition.

 

Getters and setters

A class’s properties can be represented by getters and setters, which proxy for the underlying property. This allows you to write code that’s invoked when a user tries to access a property: for example, code to validate the value that’s being written or generate a value in the getter. The following example stores legCount in the property called _legCount; the getter and setter code is invoked when legCount is accessed. The setter validates that the number of legs is zero or greater:

You can also use strong typing on getters and setters:

Accessing a getter and setter in calling code is identical to accessing a property directly. This means when you create your class, you can start with properties and then change to getters and setters without affecting other code that uses your class:

By providing only a getter or a setter, it’s possible to create read-only or write-only properties:

 

Note

The underscore in _legCount represents a private property. Privacy exists in Dart at a library, rather than a class, level. See the section on libraries for more information.

 

Methods

Classes can have functions called methods associated with them. Methods work like normal functions, except that they can access properties and methods on the specific instance of the class. Method definition is the same as function definition, except that methods are defined in the top-level scope of the class declaration. The this keyword is implied when it isn’t explicitly used and refers to the specific instance of the object:

Methods, like functions, can also use the shorthand function syntax:

You can use the this keyword to help when there would be name clashes, such as when a method parameter name is the same as a property name:

You use dot notation to call a method of a class instance, just as with properties, but you must also provide the parentheses ()—again, just as with function calls:

Just as with functions, if you leave off the parentheses on the function call, you access the function object itself, which you can store in a variable:

Methods have full access to other methods and properties in the instance of the class:

Methods have the same parameter rules as functions and can take mandatory and optional parameters with default values. Parameters and return types are optional but provide documentation to tools and fellow developers:

Constructors

When you use the new keyword to create an instance of a class, this calls the class’s constructor method. The constructor method uses the same name as the class. If no constructor is defined, this is equivalent to an empty constructor.

The following

is equivalent to

You can use the constructor to perform an initialization step in the class:

Constructor parameters

Like other methods, the constructor can take parameters with optional and default values. You can use this to define property values as the class is initialized:

Dart also provides a useful shorthand for populating properties in the constructor. When you use the this keyword in the constructor’s parameter definition, Dart knows you want to set the property with the same name:

Constructor initialization

Dart also has another initialization block in the constructor, which you can use to populate final values that need some calculation. Final values must be populated before the constructor starts executing.

The initialization block appears after the argument list and before the constructor-opening curly bracket defining the code block. It’s a comma-separated list of statements beginning with a colon. Code in the initialization block is bold:

Named constructors

A class can have multiple named constructors in addition to the default constructor. For example, you might want to create an animal instance by reading values out of a map:

If you don’t provide a default constructor, then you can only use named constructors to create instances of the class. Named constructors share all the same rules relating to optional parameters and initialization blocks as the default constructor.

Factory constructors

A factory constructor allows your class to decide how the class is instantiated, such as returning an instance of a class from a cache rather than creating a new instance. It uses the factory keyword as a prefix and must return an instance of a class, but you can have named factory constructors in the same way as other constructors. Calling code uses them just like normal constructors, and the code has no knowledge that it’s calling a factory:

This is useful for maintaining a cache of objects and loading them from a cache:

B.1.2. Class inheritance

Classes can form an inheritance hierarchy. For example, all animals have legs, but a dog has different methods and properties than a bird (birds fly; dogs run). You can use class inheritance to define a base class, such as Animal, which has properties and methods common to all animals, and then define subclasses that inherit the base class and add their own methods and properties. This creates an is-an relationship between the subclass and the parent class: for example, a Dog is-an Animal. This doesn’t work the other way around—an Animal isn’t a Dog.

You use the extends keyword to extend an existing class:

With the is-an relationship, you can get strong typing at multiple levels:

Type inheritance is also useful when you’re strongly typing lists, maps, and functions or methods:

There’s no limit to the depth of inheritance. For example, you might define specific classes of Dog, such as Poodle and Husky, which extend from Dog.

Overriding methods

Subclasses can also provide their own implementation of methods and properties defined in the parent class. For example, a bird might “peck” when eating:

B.1.3. Abstract classes

Sometimes you want the strong typing provided by a base class but with completely different implementations in each of the subclasses. For example, dogs and birds don’t eat food the same way, so it might make more sense to force Dog and Bird to provide their own implementations. You can do this with an abstract class, which doesn’t define a specific implementation but instead forces subclasses to provide an implementation. The optional abstract keyword is used to define an abstract class or method:

The abstract keyword is optional, so the following class has the same effect:

This means subclasses must provide an implementation. If they don’t, then that class is also implicitly abstract and can’t be instantiated:

Abstract classes are useful for providing some functionality but mostly for forcing subclasses to provide their own specific implementations for other functionality. For example, the Animal class might provide a default sleep() implementation but force subclasses to provide their own eat() implementation:

B.1.4. Implicit interfaces

Every class has an implicit interface. An interface is the list of the properties and methods that a class promises it will have. Other code can rely on this promise when executing.

This is especially useful when you don’t want to subclass a specific implementation; instead you want to provide a different alternative implementation, such as a mock. Imagine a third-party library that contains an enterprise dog that connects to a server somewhere for food. It looks like this:

If you write a method in your code to print the result of the eat() method, and you want to test it, you can pass in an EnterpriseDog instance, but that involves setting up a database and all the external dependencies:

Using interfaces, you can provide your own mock implementation of the EnterpriseDog from the other library by using the implements keyword:

Implementing abstract classes

All classes have an implicit interface, and this includes abstract classes. This is analogous to defining and implementing an interface in Java or C#:

Multiple interfaces

A class can implement multiple interfaces. For example, your Dog might implement two different interfaces:

B.1.5. Static methods and properties

Classes can have static methods and properties. A static method or property belongs to a class but acts independently from any instances of that class. Use the static keyword to indicate that the property or method is static. You use the class name to access static properties and methods rather than the name of a specific instance:

B.2. Libraries and privacy

A library is one or more Dart files linked together. Libraries are the smallest unit of privacy in Dart. A library can contain multiple source files, multiple functions, and multiple classes. A library that contains a main() function can also be used as an entry-point Dart script.

B.2.1. Defining libraries

A library is defined with the library statement, which must appear at the top of the file and defines the library name. Your library can be composed of multiple source files, which contain classes and functions that form your library, but the library file itself can also contain classes and functions. Your library is the aggregate of the library file and any source files it contains.

You can also import other libraries into your library by using the import statement and providing a path to that library. Imported libraries provide external code that your library can use. Listings B.1, B.2, and B.3 show two library files and a source file. my_library is defined in my_library.dart (listing B.1), is made from one other source file called source.dart (listing B.2), and imports another library called other_library.dart (listing B.3).

Listing B.1. my_library.dart

Listing B.2. source.dart

Listing B.3. other_library.dart

B.2.2. Library privacy

Anything prefixed with an underscore is defined as private to that library. Listing B.4 shows other_library.dart, which contains a private class, a private function, and a public class with private methods and properties. None of the private elements are accessible to the importing my_library.

Listing B.4. other_library.dart with private elements

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.136.142