Deep Dive into Flutter Design Patterns [part 2] [Behavioural design Pattern]

Deep Dive into Flutter Design Patterns [part 2] [Behavioural design Pattern]

In our previous discussion, we delved into the basics of Design Patterns, beginning with the Factory Design Pattern - a cornerstone of the Creational Design Pattern Family. Today, we will be shifting out focus to the Behavioural Design Pattern and explore its applications.

Understanding Behavioural Design pattern

According to Gang of Four (GoF) design pattern, Behavioural design patterns are design patterns which provide solutions for better interaction between objects.

Strategy Design Pattern

Strategy Design Pattern is a behavioural design pattern which defines a family of algorithm and englobes them into a class and makes them interchangeable. The algorithm will as well dynamically change depending on the client.

Problem Statement

Imagine you have a button that, for now, only displays in red. In the future, you plan to introduce a blue button with a distinct behaviour compared to the red one. Additionally, there's potential for further button variations down the line.

class BeforeStrategy extends StatefulWidget {
  const BeforeStrategy({super.key});

  @override
  State<BeforeStrategy> createState() => _BeforeStrategyState();
}

class _BeforeStrategyState extends State<BeforeStrategy> {
  bool isRedButton = false;
  @override
  Widget build(BuildContext context) {
    return Center(
      child: ElevatedButton(
        onPressed: () {
          if (isRedButton) {
            setState(() {
              print("I am a red button");
            });
          }
        },
        child: Text("Red Strategy"),
      ),
    );
  }
}

Modifying the above code to implement a blue button would look as such:

class _BeforeStrategyState extends State<BeforeStrategy> {
  bool isRedButton = false;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ElevatedButton(
        onPressed: () {
          if (isRedButton) {
            setState(() {
              print("I am a red button");
            });
          } else {
            setState(() {
              print("I am a blue button");
            });
          }
        },
        child: Text("Red Strategy"),
      ),
    );
  }
}

Solution

The strategy pattern recommends encapsulating algorithms into distinct classes, allowing the behaviour to be selected and swapped at runtime without altering the classes that use them.

The primary benefit to this approach includes encapsulation, separation of concerns, and the ability to change behaviour dynamically.

Moreover it adheres to the Open/Closed Principle; that software should be open for extension but closed for modification, by allowing new strategies to be added without modifying the existing code.

Refining the code provided above as follows:

abstract class ButtonStrategy {
  void execute();
}
import 'package:design_patterns/strategies/abstractButtonStrategy.dart';

class BlueButtonStrategy implements ButtonStrategy {
  @override
  void execute() {
    print("i am a blue button");
  }
}
import 'package:design_patterns/strategies/abstractButtonStrategy.dart';

class RedButtonStrategy implements ButtonStrategy {
  @override
  void execute() {
    print("i am a red button");
  }
}
class ViewButtonStrategy extends StatefulWidget {
  const ViewButtonStrategy({super.key});

  @override
  State<ViewButtonStrategy> createState() => _ViewButtonStrategyState();
}

class _ViewButtonStrategyState extends State<ViewButtonStrategy> {
  bool isRedButton = false;
  ButtonStrategy? buttonStrategy;

  @override
  void initState() {
    super.initState();
    updateStategy();
  }

  updateStategy() {
    if (isRedButton) {
      buttonStrategy = RedButtonStrategy();
    } else {
      buttonStrategy = BlueButtonStrategy();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            buttonStrategy?.execute();
            setState(() {
              isRedButton = !isRedButton;
              updateStategy();
            });
          },
          child: Text("Test Stategy"),
        ),
      ),
    );
  }
}

Explanation of the above code:

The context, which in our case is the ViewButtonStrategy will be in charge of communicating with the strategies which are RedButtonStrategy() and BlueButtonStrategy().

The strategy interface serves as a common blueprint for all the concrete strategies.

Concrete strategies are tasked with implementing the specific algorithms.

At run time, the client decides which algorithm to use; in this use case, strategy to use will depend on "isRedButton". Therefore once the button is pressed, it leads to a change in the strategy.


Wrapping Up

And that’s it guys! I hope you have a better understanding of Strategy Pattern!

References:

Strategy Design Pattern
Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.One of the dominant strategies of object-oriented design is the “open-closed principle”.
Strategy
Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.

About Me

I am Zaahra, a Google Women Techmakers Ambassador who enjoy mentoring people and writing about technical contents that might help people in their developer journey. I also enjoy building stuffs to solve real life problems.

To reach me:

LinkedIn: https://www.linkedin.com/in/faatimah-iz-zaahra-m-0670881a1/

X (previously Twitter): _fz3hra

GitHub: https://github.com/fz3hra

Cheers,

Umme Faatimah-Iz-Zaahra Mujore | Google Women TechMakers Ambassador | Software Engineer