Irbisa · Cheatsheet

Design Patterns

Singleton, Factory, Observer, Builder, Strategy, Command in Flutter

Senior Developer · 22 questions

  1. 01

    This Session is meant to be a singleton, yet the token saved at login is null on the next screen. Why?

    class Session {  Session._();  static Session get instance => Session._();   String? token;} void onLoggedIn(String token) {  Session.instance.token = token;} void onNextScreen() {  print(Session.instance.token); // null}

    instance is a getter with an arrow body, so every read runs Session._() and returns a new object: the token went into an object nobody kept.

  2. 02

    Which GoF design patterns do you see in Flutter itself and in a typical Flutter app?

    Flutter is built from them: the widget tree is Composite, ChangeNotifier and Stream are Observer, ScrollPhysics is Strategy, and Actions/Intent is Command.

  3. 03

    How do you implement a Singleton in Dart, and what are its drawbacks?

    A private named constructor plus a static final field holding the instance; Dart creates it lazily on first access, and a factory constructor can return it if callers should write Analytics().

    class Analytics {  Analytics._(); // nobody else can construct it  static final Analytics instance = Analytics._(); // created on first read  factory Analytics() => instance; // optional: Analytics() is the same object   final _events = <String>[];…
  4. 04

    Why use the Repository pattern, and how does it let you switch between a local database and a remote API?

    A repository hides where data comes from behind an interface: the app asks for a User and never learns whether it came from the network, a database or a cache.

    abstract interface class WeatherRepository {  Future<Weather> getWeather(String city);} class WeatherRepositoryImpl implements WeatherRepository {  WeatherRepositoryImpl(this._api, this._db);…
  5. 05

    Find the bug: switching on the cart's "On sale only" filter also lowers the checkout total.

    class CartRepository {  final _items = <CartItem>[];   List<CartItem> get items => _items;  int get totalCents =>      _items.fold(0, (sum, i) => sum + i.priceCents * i.qty);   void add(CartItem item) => _items.add(item);} // Cart screen, "On sale only" chipList<CartItem> visibleItems(CartRepository repo, {required bool onSaleOnly}) {  final visible = repo.items;  if (onSaleOnly) visible.removeWhere((i) => !i.onSale);  return visible;}

    The items getter returns the repository's own list, not a copy, so removeWhere on the screen deletes items from the cart itself: the filter lowers totalCents, and the hidden items are gone for good.

  6. 06

    How do you do dependency injection in Flutter (get_it, injectable, Riverpod or plain constructor injection), and what are the trade-offs?

    Dependency injection means a class receives its dependencies from outside instead of creating them, so a test can hand it a fake.

    // The class itself: constructor injection, no container in sightclass LoginViewModel {  LoginViewModel(this._auth);  final AuthRepository _auth;   Future<void> login(String email, String pass) => _auth.signIn(email, pass);…
  7. 07

    Find the bug: the login screen works the first time, but on the next visit tapping Sign in throws a StateError.

    void setupLocator() {  sl.registerLazySingleton<AuthRepository>(() => AuthRepositoryImpl(sl()));  sl.registerLazySingleton<LoginBloc>(() => LoginBloc(sl()));} class LoginPage extends StatelessWidget {  const LoginPage({super.key});   @override  Widget build(BuildContext context) => BlocProvider(        create: (_) => sl<LoginBloc>(),        child: const LoginForm(),      );}

    LoginBloc is a lazy singleton, yet BlocProvider(create:) owns what it creates and closes it when the page is popped.

  8. 08

    What is the Facade pattern, and how does it differ from a Repository?

    A facade is one entry point shaped like a task, sitting over several subsystems: it holds no data and adds no rules, it only knows how the pieces fit.

  9. 09

    How do you apply the Adapter pattern to wrap an inconsistent third-party API?

    An adapter wraps a class that speaks one interface and re-exposes it through the interface your app wants.

    // Your contractabstract interface class Analytics {  Future<void> logEvent(String name, Map<String, Object> params);} // Vendor SDK with its own shape (simplified)…
  10. 10

    How do you implement the Decorator pattern in Dart, and when do mixins or extensions cover the same need?

    A decorator wraps an object to add behaviour without changing its class: the wrapper implements the same interface, forwards every call to the inner object, and puts extra logic around it.

    abstract interface class UserRepository {  Future<User> byId(String id);} class LoggingUserRepository implements UserRepository {  LoggingUserRepository(this._inner);…
  11. 11

    Two widgets call byId('42') at the same moment through this caching decorator. How many network requests go out, and how do you fix it?

    class CachingUserRepository implements UserRepository {  CachingUserRepository(this._inner);  final UserRepository _inner;  final _cache = <String, User>{};   @override  Future<User> byId(String id) async {    final hit = _cache[id];    if (hit != null) return hit;    final user = await _inner.byId(id);    _cache[id] = user;    return user;  }}

    Two. Both calls check the cache before either response has arrived, so both miss; the map fills only after the first await completes.

  12. 12

    Functions are first-class in Dart, so when do you still write a Strategy interface instead of passing a callback?

    A callback already is a Strategy when the behaviour is one operation with no state and no identity.

    // A plain function is enough: one operation, no statetypedef PriceFormatter = String Function(int cents); // An interface earns its place: several operations, config and a nameabstract interface class RetryPolicy {  String get name; // identity for logs and tests…
  13. 13

    How do you implement the Command pattern with undo/redo support?

    A command turns an operation into an object with an execute() method and, when you need history, an undo().

    abstract interface class Command {  void execute();  void undo();}class History {  final _undo = <Command>[];…
  14. 14

    Named arguments, cascades and copyWith already cover most of the GoF Builder, so when is a real builder class still worth writing in Dart?

    Almost never for plain construction.

  15. 15

    What is the Template Method pattern, and how does Flutter's State lifecycle use it?

    A base class fixes the order of an algorithm and leaves named steps for subclasses to fill in.

  16. 16

    How does the Observer pattern relate to ChangeNotifier, Stream and BLoC?

    ChangeNotifier, ValueNotifier and Stream are ready-made Observers: a subject keeps a list of listeners and calls them on change.

  17. 17

    Memory grows each time users open and close a screen, and every retaining path ends at an app-wide ChangeNotifier. What went wrong?

    Each screen subscribed with addListener and never called removeListener in dispose, so the long-lived ChangeNotifier keeps every old State alive through its listener list; a closure that captured this drags in the whole subtree.

    class _ProfileState extends State<Profile> {  late final AuthNotifier _auth;   @override  void initState() {    super.initState();…
  18. 18

    How does the State pattern compare with Dart 3 sealed classes?

    Both model an object that behaves differently in each of several modes.

    // State pattern: behaviour and transitions live in the state objectsabstract interface class PlayerState {  PlayerState onTap();}class Playing implements PlayerState {  @override…
  19. 19

    Your feed API tags each item with a type and new kinds ship every sprint. How do you parse it, and what does an old app do with an unknown type?

    Dispatch on the type field in exactly one factory, and make the unknown branch a real model instead of a throw: an app that crashes on a type it has never seen can only be fixed by shipping a release.

    sealed class FeedItem {  const FeedItem();   // One factory owns the dispatch; map patterns also check the field types  factory FeedItem.fromJson(Map<String, dynamic> json) => switch (json) {        {'type': 'post', 'text': String text} => PostItem(text),…
  20. 20

    How would you build a composable middleware chain in Dart, and when is that better than a list of if-statements?

    This is Chain of Responsibility: model each stage as a function that takes the request and the next stage, fold the list into one pipeline, and prefer it to ifs when order, per-environment sets or testing each stage matter.

    typedef Handler = Future<Response> Function(Request req);typedef Middleware = Handler Function(Handler next); // The first stage in the list becomes the outermost oneHandler pipeline(List<Middleware> stages, Handler endpoint) =>    stages.reversed.fold<Handler>(endpoint, (next, stage) => stage(next));…
  21. 21

    How do you ship one API with different implementations for mobile and web when dart:io throws in the browser and package:web does not compile on mobile?

    Put the implementations behind one abstract interface and choose between them with a conditional export.

    // secure_store.dart: the only file the app importsexport 'src/secure_store.dart';export 'src/create_store_stub.dart'    if (dart.library.io) 'src/create_store_io.dart'    if (dart.library.js_interop) 'src/create_store_web.dart';…
  22. 22

    You render a document AST to widgets and also need plain text out of the same tree for search: Visitor, or a sealed class with two switches?

    In Dart 3 the sealed switch is the default answer: it gives the same compiler-checked completeness as a Visitor, without accept/visit boilerplate or double dispatch.

    sealed class Node {} final class Heading extends Node {  Heading(this.level, this.text);  final int level;  final String text;…