Strategy Pattern
Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 93 of 110.
The Strategy pattern lets you define a family of algorithms, encapsulate each one in its own class, and make them interchangeable at runtime. Instead of hardcoding behavior into a class, you inject the behavior through an interface, allowing you to swap strategies without modifying the class that uses them.
Consider a payment system where you want to support different payment methods. Rather than using conditionals to handle each method, you define a strategy interface and create concrete implementations:
abstract class PaymentStrategy {
void pay(double amount);
}
class CreditCardPayment implements PaymentStrategy {
@override
void pay(double amount) => print('Paid \$amount with Credit Card');
}
class PayPalPayment implements PaymentStrategy {
@override
void pay(double amount) => print('Paid \$amount with PayPal');
}
class ShoppingCart {
PaymentStrategy _paymentStrategy;
ShoppingCart(this._paymentStrategy);
void setPaymentStrategy(PaymentStrategy strategy) {
_paymentStrategy = strategy;
}
void checkout(double total) {
_paymentStrategy.pay(total);
}
}
void main() {
var cart = ShoppingCart(CreditCardPayment());
cart.checkout(99.99); // Paid $99.99 with Credit Card
cart.setPaymentStrategy(PayPalPayment());
cart.checkout(49.99); // Paid $49.99 with PayPal
}The ShoppingCart doesn't know or care how payment works - it just delegates to whatever strategy is currently set. You can add new payment methods by creating new strategy classes without touching the cart code. This pattern is ideal when you have multiple ways to perform an action and want to select or switch between them dynamically.
Challenge
EasyLet's build a text formatting system using the Strategy pattern! You'll create a system where different formatting strategies can be applied to text, allowing users to switch between formats dynamically without changing the core text processor.
You'll organize your code into two files:
formatting_strategies.dart: Create your strategy system here. Define an abstract classFormattingStrategywith a methodString format(String text). Then implement three concrete strategies:UppercaseStrategy- converts text to all uppercaseLowercaseStrategy- converts text to all lowercaseTitleCaseStrategy- capitalizes the first letter of each word (split by spaces, capitalize first character of each word, rest lowercase)
TextProcessorclass that holds aFormattingStrategy. It should have a constructor that accepts an initial strategy, asetStrategy()method to change the strategy at runtime, and aprocess(String text)method that applies the current strategy and returns the formatted result.main.dart: Import your formatting strategies and demonstrate how the Strategy pattern allows dynamic behavior switching. Create aTextProcessorstarting with theUppercaseStrategy. Process the texthello worldand print the result. Then switch toLowercaseStrategy, processHELLO WORLD, and print. Finally, switch toTitleCaseStrategy, processthe quick brown fox, and print the result.
This demonstrates the power of the Strategy pattern - your TextProcessor doesn't need to know the details of how formatting works. It simply delegates to whatever strategy is currently set, making it easy to add new formatting options without modifying the processor!
Expected output:
HELLO WORLD
hello world
The Quick Brown FoxCheat sheet
The Strategy pattern defines a family of algorithms, encapsulates each one in its own class, and makes them interchangeable at runtime. Instead of hardcoding behavior, you inject it through an interface.
Define a strategy interface:
abstract class PaymentStrategy {
void pay(double amount);
}Create concrete strategy implementations:
class CreditCardPayment implements PaymentStrategy {
@override
void pay(double amount) => print('Paid \$amount with Credit Card');
}
class PayPalPayment implements PaymentStrategy {
@override
void pay(double amount) => print('Paid \$amount with PayPal');
}Create a context class that uses the strategy:
class ShoppingCart {
PaymentStrategy _paymentStrategy;
ShoppingCart(this._paymentStrategy);
void setPaymentStrategy(PaymentStrategy strategy) {
_paymentStrategy = strategy;
}
void checkout(double total) {
_paymentStrategy.pay(total);
}
}Use the pattern by injecting and switching strategies:
var cart = ShoppingCart(CreditCardPayment());
cart.checkout(99.99); // Paid $99.99 with Credit Card
cart.setPaymentStrategy(PayPalPayment());
cart.checkout(49.99); // Paid $49.99 with PayPalThe context class delegates behavior to the strategy without knowing implementation details. Add new strategies by creating new classes without modifying existing code.
Try it yourself
import 'formatting_strategies.dart';
void main() {
// TODO: Create a TextProcessor starting with UppercaseStrategy
// TODO: Process the text 'hello world' and print the result
// TODO: Switch to LowercaseStrategy
// TODO: Process 'HELLO WORLD' and print the result
// TODO: Switch to TitleCaseStrategy
// TODO: Process 'the quick brown fox' and print the result
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesLibraries & ImportsIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsInstance VariablesConstructor BasicsRecap - Simple Calculator4Null Safety
Intro to Null SafetyNullable vs Non-NullableThe ? and ! OperatorsLate Keyword & Null SafetyNull-Aware OperatorsNull Safety in ClassesRecap - User Profile System7Abstract Classes & Interfaces
Abstract ClassesAbstract MethodsInterfaces in DartImplicit InterfacesImplementing vs ExtendingMultiple InterfacesRecap - Shape Calculator10Collections & Generics
List, Set, Map OverviewType-Safe CollectionsGeneric ClassesGeneric MethodsGeneric ConstraintsIterable & IteratorRecap - Generic Storage13Advanced OOP Concepts
Composition vs InheritanceExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums with Methods16Project: Library Management
Project OverviewBook and User Classes2Constructors in Dart
Default ConstructorNamed ConstructorsInitializer ListsConstant ConstructorsFactory ConstructorsRedirecting ConstructorsRecap - Shape Builder5Encapsulation
Public vs Private MembersThe _ Prefix ConventionLibrary-Level PrivacyGetters & Setters DepthInformation HidingRecap - Student Records8Mixins
Introduction to MixinsCreating MixinsUsing Multiple Mixinson Keyword in MixinsMixin vs InheritanceMixin vs InterfaceRecap - Animal System11Special Methods
toString() OverridehashCode & == OverrideComparable Interfacecall() MethodnoSuchMethod OverrideRecap - Custom Collection14Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern3Class Properties
Instance vs Static MembersFinal & Const FieldsLate VariablesStatic Methods & FieldsGetters and SettersRecap - Bank Account Manager6Inheritance
Basic InheritanceThe super KeywordMethod OverridingThe @override AnnotationThe final Class KeywordConstructors & InheritanceRecap - Employee Hierarchy9Polymorphism
Polymorphism BasicsPolymorphism via InterfacesRuntime Type CheckingThe is & as OperatorsCovariant KeywordRecap - Payment Processor