Building with Blocks: Understanding Structural Patterns


All the images are from https://refactoring.guru/design-patterns. Please visit the site for a more detailed explanation.

Facade

Facade design pattern

As human beings, we all love the abstraction of things. We want to use the internet without knowing how it really works, or watch and operate a TV without understanding exactly how it functions. By hiding the details of your code (classes) from the end user, you create a facade.

In the code below, the doLaundry() method is a facade that hides all the inner details and provides a simpler method, so the user doesn't have to know how the code works internally.

Most of the code you see in Flutter uses the facade approach. For example, we can create a widget by just passing some parameters without really knowing how it works under the hood.

class LaundryService {
  void washClothes() {
    print("Clothes are being washed.");
  }

  void dryClothes() {
    print("Clothes are being dried.");
  }

  void foldClothes() {
    print("Clothes are being folded.");
  }
}

class LaundryWorkerFacade {
  final LaundryService _laundryService = LaundryService();

  // Facade method to simplify the process
  void doLaundry() {
    print("Starting the laundry service...");
    _laundryService.washClothes();
    _laundryService.dryClothes();
    _laundryService.foldClothes();
    print("Laundry service completed!");
  }
}
void main() {
  // Client code using the simplified interface
  final laundryWorker = LaundryWorkerFacade();
  laundryWorker.doLaundry();
}

Proxy

Proxy design pattern

So, we create a proxy, which means taking requests on behalf of someone. For example, your VPN acts as a proxy for you. Your friend marking your attendance on your behalf is also a proxy. We usually use the proxy design pattern to avoid repeatedly creating expensive objects. For example, in Android MAD architecture, we should have a service that acts as a mediator or proxy to handle the creation of database objects, caching, etc. This ensures that users or other objects don't have to manage this themselves, reducing redundancy and improving the maintainability and testability of the code.

class DatabaseProxy {
  final Database _database = Database();  // Real object
  String? _cachedData;  // Cached data

  String fetchData() {
    // If data is cached, return the cached data
    if (_cachedData != null) {
      print("Returning cached data...");
      return _cachedData!;
    }

    // If data isn't cached, fetch from the database and cache it
    _cachedData = _database.fetchData();
    return _cachedData!;
  }
}
void main() {
  // Using the Proxy
  final proxy = DatabaseProxy();

  // First time, it fetches data from the database
  print(proxy.fetchData());

  // Subsequent time, it returns the cached data
  print(proxy.fetchData());
}

Adapter

Adapter design pattern

an adapter is exactly what you know a phone adapter its role is to change the data to one form to another which in the phones case is to AC high Volt to DC low voltage . The adapters job is to make the sure that the both parties can pass and revieeve data in the same format as they expect it to be .

class ACCurrent {
  void provideAC() => print("Providing AC current");
}

class ACtoDCAdapter {
  final ACCurrent ac;

  ACtoDCAdapter(this.ac);

  void provideDC() {
    ac.provideAC();
    print("Converted to DC current");
  }
}

void main() {
  ACCurrent ac = ACCurrent();
  ACtoDCAdapter adapter = ACtoDCAdapter(ac);
  adapter.provideDC();
}

Composition

Composite design pattern

The term composition means combining two or more things together. Let's say you want to make a cup of tea. You do this by using milk, water, and tea powder. To make the tea, you first take water, then add tea powder, and finally add milk. That's it! By using this approach, you can change the type of milk or tea without changing the actual method of making the tea. This makes our code more modular.

class Milk {
  void add() {
    print("Adding milk.");
  }
}

class Water {
  void add() {
    print("Adding water.");
  }
}

class TeaPowder {
  void add() {
    print("Adding tea powder.");
  }
}

class Tea {
  final Milk milk;
  final Water water;
  final TeaPowder teaPowder;

  Tea(this.milk, this.water, this.teaPowder);

  void make() {
    milk.add();
    water.add();
    teaPowder.add();
    print("Your cup of tea is ready!");
  }
}

void main() {
  Tea tea = Tea(Milk(), Water(), TeaPowder());
  tea.make();  // Making tea by combining milk, water, and tea powder
}

Decorator

Decorator design pattern

The Decorator Pattern builds on the Composition Pattern, promoting the use of composition over inheritance. This helps avoid deep inheritance hierarchies, which can lead to unmanageable and inflexible code. Instead, the Decorator Pattern allows you to dynamically add features or responsibilities to an object without altering its core functionality.

For instance, instead of extending a base class like Notifier, we use composition to inject additional behaviors (like WhatsApp or Facebook) into the object. This approach leverages dependency injection, making the system highly flexible, easily swappable, and maintainable. By using this pattern, you can easily enhance your object’s capabilities without modifying the original structure, resulting in cleaner and more scalable code.

// Base Notifier
class Notifier {
  void send(String message) {
    print("Sending message: $message");
  }
}

// Decorators
class NotifierDecorator extends Notifier {
  final Notifier notifier;
  NotifierDecorator(this.notifier);

  @override
  void send(String message) {
    notifier.send(message);
  }
}

class WhatsAppNotifier extends NotifierDecorator {
  WhatsAppNotifier(Notifier notifier) : super(notifier);
  @override
  void send(String message) {
    super.send(message);
    print("Sent via WhatsApp: $message");
  }
}

class FacebookNotifier extends NotifierDecorator {
  FacebookNotifier(Notifier notifier) : super(notifier);
  @override
  void send(String message) {
    super.send(message);
    print("Sent via Facebook: $message");
  }
}

void main() {
  Notifier notifier = Notifier();
  notifier = WhatsAppNotifier(FacebookNotifier(notifier));
  notifier.send("Hello, World!");
}
0
Subscribe to my newsletter

Read articles from Affan Shaikhsurab directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Affan Shaikhsurab
Affan Shaikhsurab