22

Creational Design Patterns for Dart and Flutter: Singleton

 4 years ago
source link: https://dart.academy/creational-design-patterns-for-dart-and-flutter-singleton/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

When you need to ensure only a single instance of a class is created, and you want to provide a global point of access to it, the Singleton design pattern can be of great help. Examples of typical singletons include an app-wide debug logger or the data model for an app's configuration settings. If multiple parts of an app need access to such an object, each area could create its own instance or have one passed to it, but the Singleton pattern offers a cleaner, more memory efficient way. Often, the implementation includes lazy instantiation, which means the single instantiation occurs only upon first access.

About creational design patterns: These patterns, as the name implies, help us with the creation of objects and processes related to creating objects. With these techniques in your arsenal, you can code faster, create more flexible, reusable object templates, and sculpt a universally recognizable structure for your projects. The creational design patterns are blueprints you can follow to reliably tackle object creation issues that arise for many software projects.

In this article, we'll introduce the Singleton pattern using a typical implementation before looking at how Dart syntax can improve on the classic structure, then we'll examine a real-world example in the form of a debug message logger.

The code for this article was tested with Dart 2.8.4 and Flutter 1.17.5.

A typical singleton implementation

The following diagram illustrates the most basic structure of a singleton class:

nErmuiU.png!web

A typical singleton class has a private, static variable containing a reference to the class's one instance. Singleton constructors optimally don't take any parameters. Purists argue that if configuration parameters are allowed, it's possible to create a singleton object that differs from another based on those values, which may violate the basic premise of the Singleton pattern, as a user is not guaranteed a completely predictable instance. The getInstance() method is also static and serves as the gateway to the singleton's cached instance.

Let's look at how we might implement this generic approach in Dart:

class Singleton {
  static Singleton _instance;
  
  Singleton._internal();
  
  static Singleton getInstance() {
    if (_instance == null) {
      _instance = Singleton._internal();
    }
    
    return _instance;
  }
}

First, we define the private _instance variable as a static property on the Singleton class. This will be accessible only within the class's library, so outside code has to use getInstance() to access it. If getInstance() finds the one allowed instance doesn't yet exist, it creates the instance using the private, named constructor _internal() , then it returns the cached instance.

If a code segment needs access to the singleton's instance, this code will get it:

final singleton = Singleton.getInstance();

If code outside the singleton's own library attempts to directly instantiate a singleton, the Dart analyzer will flag an error, pointing out that Singleton has no public default constructor. It can only be instantiated via the private constructor, and only from within the library.

Next, we'll take a look at how the pattern can be simplified using more idiomatic Dart syntax.

Implementing Singleton the Dart way

Dart includes some features we can use to implement the Singleton pattern more elegantly. In this first example, we'll get rid of the awkward getInstance() static method and replace it with a getter:

class Singleton {
  static Singleton _instance;
  static get instance {
    if (_instance == null) {
      _instance = Singleton._internal();
    }
    
    return _instance;
  }
  
  Singleton._internal();
}

A Dart getter operates almost exactly like a method, but it doesn't require the caller to use parentheses.

The getter makes accessor code more readable, since it looks more like standard property access syntax, as in the following example:

final singleton = Singleton.instance;

A nice improvement, but can we do better? Using Dart's factory keyword, we can effectively hide the use of the Singleton pattern altogether:

class Singleton {
  static Singleton _instance;
  
  Singleton._internal();
  
  factory Singleton() {
    if (_instance == null) {
      _instance = Singleton._internal();
    }
    
    return _instance;
  }
}

In this version, we add a public default constructor that outside code can use, but we mark it as a factory . A factory constructor can be used in cases where the constructor doesn't always create a new instance of its class, as a standard constructor must. In our example, the factory constructor creates a new instance just once, then it returns that cached instance on every future invocation.

Now, a user of the class can use more familiar syntax to acquire a reference to a Singleton instance:

final singleton = Singleton();

Though it looks like a typical object instantiation, a new object will be created only the first time this constructor is used, but that is an implementation detail hidden from casual view.

How might you use the Singleton pattern in a real application?

Singleton example: Logger

Most apps need an easy way to log messages to the debug console during development. Creating a wrapper around Dart's most popular logger implementation, from the logging package, can make your app's logging solution more robust and customizable, and the Singleton pattern can be applied to prevent unneeded logger instances from cluttering up a device's memory space:

import 'package:logging/logging.dart';
import 'package:intl/intl.dart' show DateFormat;

class DebugLogger {
  static DebugLogger _instance;
  static Logger _logger;
  static final _dateFormatter = DateFormat('H:m:s.S');
  static const appName = 'my_app';

  DebugLogger._internal() {
    Logger.root.level = Level.ALL;
    Logger.root.onRecord.listen(_recordHandler);

    _logger = Logger(appName);
  };

  factory DebugLogger() {
    if (_instance == null) {
      _instance = DebugLogger._internal();
    }

    return _instance;
  }

  void _recordHandler(LogRecord rec) {
    print('${_dateFormatter.format(rec.time)}: ${rec.message}');
  }

  void log(message, [Object error, StackTrace stackTrace]) =>
      _logger.info(message, error, stackTrace);
}

After including the logging package in our app's dependencies, we can import that library to gain access to its Logger implementation. We also need the intl package, since it contains Dart's standard DateFormat service.

Dart code packages: You can learn more about logging , intl , and all of the other available packages on Dart's package repository site, Pub .

Our DebugLogger class starts off by defining a private, static instance variable, as discussed previously in our exploration of the Singleton design pattern. Another one is declared for an instance of the Logger class that will provide logging functionality. Then we set up a date formatter that can be used to create a readable string from a DateTime object. The logging package's Logger constructor requires the app name value, so we set that up here as well.

Next, the private, named constructor, which we've called _internal() per established convention, is defined. Its body does some basic setup for global logging, including setting the severity level of messages we want to deal with and registering a callback for handling generated log records. Finally, a logger is instantiated, and its reference is stored.

The default constructor for DebugLogger is a factory constructor. Its job is to lazily construct and return the managed instance. This is what makes the class a singleton.

The private method _recordHandler() serves as the callback for handling new log records as they're produced. The method prints the log message to the debug console, including the formatted time stamp. A developer could alter the body of this method to customize log output for any given app.

DebugLogger has one more public method, log() , that uses the logger to post a message at the Logger.INFO severity level. More public methods could be added to allow for logging messages with other severity levels.

Any code that wants to make use of the debug logger simply has to import the library containing DebugLogger, then run the factory constructor:

final logger = DebugLogger();

Every time a DebugLogger is constructed in this manner, the same instance will be returned, and only a single logger will ever be created.

Conclusion

This article introduced the Singleton pattern, which you can use to restrict instantiation of a class to one object and provide a global access point to it. To read more about creational design patterns in Dart, check out these related articles:


Recommend

  • 55

    Creational Design Patterns: Factory Pattern DZone's Guide to Creational Design Patterns: Factory Pattern As we continue to explore creational design patterns...

  • 30

    Creational Design Patterns: Prototype Pattern [Snippet] DZone's Guide to Creational Design Patterns: Prototype Pattern [Snippet] Want to learn more about usi...

  • 59

    The factory method design pattern is one of the well-known “Gang of Four” (GoF) design patterns. It is a creational design pattern that uses factory methods to deal with creating instances without specifying the exact cla...

  • 34
    • www.tuicool.com 6 years ago
    • Cache

    Singleton Design Pattern: What, Why, How

    Today, we are going to discuss how to create a singleton instance for a clustered (or) cloud environment. What The singleton instance is a class that has exactly one instance at any given time....

  • 41

    While having a vibe of a mad genius might be tempting, reinventing the wheel is usually not the best way to approach designing your software. The chances are that somebody already had the same problem as you and solved it...

  • 8

    Constructor PatternIn the constructor pattern, instead of returning the instance from the function, we use the new operator along with the function name.0 reactionsfunction createFruit(name) {...

  • 12
    • blog.knoldus.com 4 years ago
    • Cache

    Creational Design Pattern Part 1: Singleton

    Reading Time: 3 minutes Overview In this article, I will be sharing the working of a singleton pattern, in a nutshell, using a simple real-world example. 1. Singleton Design Pattern The aim of the singleto...

  • 6

    Flutter 设计模式:Singleton 单例 候选人来公司面试,我要求必须考察代码功力,一般我自己喜欢考个简单的算法题,有个同事,他喜欢考候选人写一个单例模式。单例模式可能是所有设计模式里,最最常用和常见的一个模式了。 单例模式的目的是...

  • 4

  • 9
    • blog.davidvassallo.me 2 years ago
    • Cache

    Singleton patterns in Python’s AIO-HTTP

    Singleton patterns in Python’s AIO-HTTP AIO-HTTP is a web server framework built on python 3’s async capabilities. The performance benefits of using async are widely d...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK