Dart best practices - enforce noninstantiability with a private constructor Page

Item 4: Dart Best Practices - Enforce noninstantiability with a private constructor




Introduction to Enforcing Noninstantiability in Dart



In Dart, a language commonly used for building web and mobile applications, you may sometimes need to create utility classes or other types that should not be instantiated. Enforcing noninstantiability ensures that these classes are used only as intended, typically through static methods or properties. This can be accomplished by making the constructor private, which prevents the class from being instantiated directly by users.

Advantages of Enforcing Noninstantiability in Dart



Enforcing noninstantiability in Dart offers several key advantages:
1. **Prevents Misuse**: By preventing the instantiation of a class that is not intended to be instantiated, you avoid unintended behaviors or logical errors in your code.
2. **Encapsulates Implementation Details**: A private constructor allows you to encapsulate the class's internal logic, ensuring that only the intended API is exposed to users.
3. **Encourages Proper Design**: This approach encourages the use of static methods and properties, leading to more maintainable and clear code.

Example 1: Enforcing Noninstantiability with a Private Constructor



In Dart, you can enforce noninstantiability by defining a class with a private constructor, ensuring that the class cannot be instantiated directly:

```dart
class UtilityClass {
// Private constructor to prevent instantiation
UtilityClass._() {
throw UnimplementedError("This class cannot be instantiated.");
}

// Static method to provide utility functionality
static void utilityMethod() {
print("This is a utility method.");
}
}

void main() {
UtilityClass.utilityMethod(); // Works fine

try {
var obj = UtilityClass._(); // Throws an error
} catch (e) {
print(e); // Outputs: UnimplementedError: This class cannot be instantiated.
}
}
```

In this example, the `UtilityClass` has a private constructor named `_` that throws an exception if an attempt is made to instantiate the class. The `utilityMethod` is a static method that provides the intended functionality without requiring instantiation.

Example 2: Enforcing Noninstantiability Using Static Classes



You can also define a class with only static members and a private constructor to enforce noninstantiability:

```dart
class MathUtils {
// Private constructor to prevent instantiation
MathUtils._();

// Static utility methods
static int add(int a, int b) {
return a + b;
}

static int subtract(int a, int b) {
return a - b;
}
}

void main() {
print(MathUtils.add(10, 5)); // Outputs: 15
print(MathUtils.subtract(10, 5)); // Outputs: 5

// Uncommenting the following line will result in an error
// var mathUtils = MathUtils._(); // Throws an error
}
```

In this example, the `MathUtils` class is designed to be used statically, and its private constructor ensures that the class cannot be instantiated.

Example 3: Enforcing Noninstantiability in a Singleton-Like Class



In cases where you need a single instance of a class, you can use a singleton pattern with a private constructor:

```dart
class Singleton {
static final Singleton _instance = Singleton._internal();

// Private constructor to prevent instantiation
Singleton._internal();

factory Singleton() {
return _instance;
}

void doSomething() {
print("Singleton instance is doing something!");
}
}

void main() {
var singleton = Singleton();
singleton.doSomething(); // Outputs: Singleton instance is doing something!

try {
var anotherSingleton = Singleton._internal(); // Throws an error
} catch (e) {
print(e); // Outputs: Unhandled exception: Singleton._internal is not defined
}
}
```

In this example, the `Singleton` class has a private constructor and a static instance that is accessed through the factory constructor. This ensures that only one instance of the class can exist.

Example 4: Using Factory Constructors to Control Instantiation



Another approach to enforce noninstantiability is to use a factory constructor that returns a predefined instance, rather than allowing new instances to be created:

```dart
class Logger {
static final Logger _instance = Logger._internal();

// Private constructor to prevent instantiation
Logger._internal();

factory Logger() {
return _instance;
}

void log(String message) {
print("Log: $message");
}
}

void main() {
var logger1 = Logger();
var logger2 = Logger();

logger1.log("This is a log message."); // Outputs: Log: This is a log message.

// Both instances are the same
print(logger1 == logger2); // Outputs: true
}
```

In this example, the `Logger` class uses a factory constructor to control the instantiation process, ensuring that only one instance is ever created.

When to Enforce Noninstantiability in Dart



Enforcing noninstantiability in Dart is useful in the following scenarios:
- **Utility Classes**: When creating a class that contains only static methods or properties and is not meant to be instantiated.
- **Singleton-Like Classes**: When you want to ensure that a class is never instantiated directly but can still be accessed in a controlled manner through a factory constructor.
- **Module Design**: When designing modules that contain utility functions, enforcing noninstantiability can prevent misuse and clarify the intended usage.
- **API Design**: When developing a Dart package or library where certain classes should not be instantiated by users, enforcing noninstantiability can prevent misuse and maintain the integrity of the API.

Conclusion



In Dart, enforcing noninstantiability with a private constructor is a best practice when you want to prevent a class from being instantiated. This technique is particularly useful for utility classes, singleton patterns, and scenarios where class instances would lead to logical errors or misuse. By enforcing noninstantiability, you can write more maintainable, clear, and reliable code, especially in situations where class instances should be tightly controlled.

Further Reading and References



For more information on enforcing noninstantiability in Dart, consider exploring the following resources:

* https://dart.dev/guides/language/language-tour#classes
* https://dart.dev/guides/language/effective-dart
* https://www.dartlang.org/articles/style-guide

These resources provide additional insights and best practices for using noninstantiability effectively in Dart.