Publish to my blog (weekly)
-
Flutter Design Patterns: Flyweight | kazlauskas.dev
- a creational design pattern that divides the construction of a complex object into several separate steps - Builder.
-
-
Flutter Design Patterns: Builder | kazlauskas.dev
- Builder is a creational design pattern
- To understand it better, let's say we are building a house. To build a house (object), the construction steps (build logic) are pretty much the same - you need the foundation, floor, some walls, doors, windows, a roof, etc. Even though the construction process of the house is the same, each of these steps could be adjusted, hence the final result would look completely different. And that is the main idea of the Builder design pattern - abstract the object's creation process so that construction steps could be adjusted to provide a different representation (final result).
- there is one additional layer in the Builder design pattern - Director.
- he Director is a simple class that is aware of the Builder interface and defines the order in which to execute the building steps.
- common to all types of builders for creating parts of a Product;
- Builder
- a specific implementation of the construction steps.
- Concrete Builder
- t defines and keeps track of the Product it creates;
- Director
- defines the order in which the construction steps are called;
- Product
- exposes interface/methods for assembling the parts into the final result;
- Client
- the pattern could be applied when the construction steps are similar but differ in details.
- a director class that manages the burger's build process.
-
BurgerMaker
- a specific builder implementation as a
burgerBuilder
property, -
prepareBurger()
method to build the burger -
getBurger()
method to return it. - the builder's implementation could be changed using the
changeBurgerBuilder()
method. - final List<Ingredient> _ingredients = [];
- late Burger burger;
- burger = Burger();
- return burger;
- burger.setPrice(price);
- burger.addIngredient(BigMacBun());
- burger.addIngredient(Cheese());
- burger.addIngredient(BeefPatty());
- burger.addIngredient(BigMacSauce());
- burger.addIngredient(GrillSeasoning());
- burger.addIngredient(Onions());
burger.addIngredient(PickleSlices());
burger.addIngredient(ShreddedLettuce()); - burger.addIngredient(RegularBun());
- burger.addIngredient(Cheese());
- burger.addIngredient(BeefPatty());
- burger.addIngredient(Ketchup());
burger.addIngredient(Mustard()); - burger.addIngredient(GrillSeasoning());
- burger.addIngredient(Onions());
burger.addIngredient(PickleSlices()); - // Not needed
- // Not needed
- // Not needed
- A director class that manages the burger's build process and returns the build result.
- BurgerBuilderBase burgerBuilder;
- this.burgerBuilder
- void changeBurgerBuilder(BurgerBuilderBase burgerBuilder) {
this.burgerBuilder = burgerBuilder;
} - return burgerBuilder.getBurger();
- burgerBuilder.createBurger();
- burgerBuilder.setBurgerPrice();
- burgerBuilder.addBuns();
burgerBuilder.addCheese();
burgerBuilder.addPatties();
burgerBuilder.addSauces();
burgerBuilder.addSeasoning();
burgerBuilder.addVegetables(); - the
BurgerMaker
class object - which is used to select the specific builder using UI.
- final BurgerMaker _burgerMaker = BurgerMaker(HamburgerBuilder());
- burgerBuilder: HamburgerBuilder(),
- burgerBuilder: CheeseburgerBuilder(),
- burgerBuilder: BigMacBuilder(),
- burgerBuilder: McChickenBuilder(),
- Burger _prepareSelectedBurger() {
_burgerMaker.prepareBurger();
return _burgerMaker.getBurger();
} - _burgerMaker.changeBurgerBuilder(selectedItem.burgerBuilder);
- _selectedBurger = _prepareSelectedBurger();
- onChanged: _onBurgerMenuItemChanged,
- The director class
BurgerMaker
does not care about the specific implementation of the builder - the specific implementation could be changed at run-time, hence providing a different result. - this kind of implementation allows easily adding a new builder (as long as it extends the
BurgerBuilderBase
class) to provide another different product's representation without breaking the existing code.
-
-
Flutter Design Patterns: Bridge | kazlauskas.dev
- Bridge, also known as Handle/Body, belongs to the category of structural design patterns.
- inheritance - an abstraction defines the interface while concrete subclasses implement it in different ways. However, this approach is not very flexible since it binds the implementation to abstraction at compile-time and makes it impossible to change the implementation at run-time
- What if we want the implementation to be selected and exchanged at run-time?
- a bridge - it bridges the abstraction and its implementation, letting them vary independently.
- a GUI (graphical user interface) and OS (operating system).
- Abstraction
- Refined abstraction
- defines an interface for the implementation classes. An Abstraction can only communicate with an Implementation object via methods that are declared there;
- Implementation
- Concrete implementations
- the pattern allows splitting the class into several class hierarchies which could be changed independently - it simplifies code maintenance, and smaller classes minimize the risk of breaking existing code
- A good example of this approach is when you want to use several different approaches in the persistence layer e.g. both database and file system persistence.
- the bridge design pattern should be used when both the abstractions and their implementations should be extensible by subclassing - the pattern allows combining different abstractions and implementations and extending them independently.
- the bridge design pattern is a lifesaver when you need to be able to switch implementations at run-time.
- the storage type of these repositories could be changed between the
FileStorage
andSqlStorage
separately and at the run-time. - final IStorage storage;
- this.storage
- return storage.fetchAll<Customer>();
- storage.store<Customer>(entityBase as Customer);
- final IStorage storage;
- this.storage
- return storage.fetchAll<Order>();
- storage.store<Order>(entityBase as Order);
- Type
- Type
- final List<IStorage> _storages = [SqlStorage(), FileStorage()];
- late IRepository _customersRepository;
- late IRepository _ordersRepository;
- Customer
- _customersRepository = CustomersRepository(_storages[index]);
- Order
- _ordersRepository = OrdersRepository(_storages[index]);
- _customersRepository =
CustomersRepository(_storages[_selectedCustomerStorageIndex]); - _ordersRepository = OrdersRepository(_storages[_selectedOrderStorageIndex]);
- storages: _storages,
- selectedIndex: _selectedCustomerStorageIndex,
- onChanged: _onSelectedCustomerStorageIndexChanged,
- storages: _storages,
- selectedIndex: _selectedOrderStorageIndex,
- onChanged: _onSelectedOrderStorageIndexChanged,
- the storage type could be changed for each repository separately and at run-time - it would not be possible by using the simple class inheritance approach.
-
-
Flutter Design Patterns: Decorator | kazlauskas.dev
- Decorator, also known as Wrapper, is a structural design pattern,
- a flexible alternative to subclassing for extending functionality.
- The Decorator design pattern provides a way of changing the skin of an object without changing its guts - it extends an object's functionality by wrapping it in an object of a Decorator class, leaving the original object intact without modification.
- the Open/Closed Principle
- The decorations (decorator classes) are independent of each other, hence they can be composed and chained together to add multiple behaviours
- this behaviour could be added at run-time
- very flexible reuse of code
- increase the complexity of code
- you need to implement the Component
- an indefinite amount of Decorator classes should be added to wrap it
- debugging and testing the component wrapped by several additional classes does not make the development easier, too.
- Component
- Concrete Component
- Base Decorator
- Concrete Decorators
- Client
- when you need to add extra responsibilities to objects dynamically (at run-time) without affecting other objects
- all the decorator objects implement the same interface, they can be used in various combinations and interchanged with each other.
- this design pattern is useful when extension by subclassing is impractical or even not possible.
- the Decorator design pattern could be simply used to refactor the code base and split components with hard-wired extensions (compile-time implementation dependencies) into separate classes.
- An object of this class (its behaviour) gets decorated by the specific decorator classes.
- final Pizza pizza;
- return pizza.getDescription();
- return pizza.getPrice();
- super.pizza
- return '${pizza.getDescription()} - $description';
- return pizza.getPrice() + 0.2;
- super.pizza
- return '${pizza.getDescription()} - $description';
- return pizza.getPrice() + 0.5;
- super.pizza
- return '${pizza.getDescription()} - $description';
- return pizza.getPrice() + 0.1;
- super.pizza
- return '${pizza.getDescription()} - $description';
- return pizza.getPrice() + 0.2;
- super.pizz
- return '${pizza.getDescription()} - $description';
- return pizza.getPrice() + 0.7;
- super.pizza
- return '${pizza.getDescription()} - $description';
- return pizza.getPrice() + 1.5;
- super.pizza
- return '${pizza.getDescription()} - $description';
- return pizza.getPrice() + 0.3;
- Map<int, PizzaToppingData> getPizzaToppingsDataMap() => _pizzaToppingsDataMap;
- return _getMargherita();
- return _getPepperoni();
- return _getCustom(pizzaToppingsDataMap);
- Pizza pizza = PizzaBase('Pizza Margherita');
pizza = Sauce(pizza);
pizza = Mozzarella(pizza);
pizza = Basil(pizza);
pizza = Oregano(pizza);
pizza = Pecorino(pizza);
pizza = OliveOil(pizza); - Pizza pizza = PizzaBase('Pizza Pepperoni');
pizza = Sauce(pizza);
pizza = Mozzarella(pizza);
pizza = Pepperoni(pizza);
pizza = Oregano(pizza); - Map<int, PizzaToppingData> pizzaToppingsDataMap
- pizza = Basil(pizza);
- pizza = Mozzarella(pizza);
- pizza = OliveOil(pizza);
- pizza = Oregano(pizza);
- pizza = Pecorino(pizza);
- pizza = Pepperoni(pizza);
- pizza = Sauce(pizza);
- final PizzaMenu pizzaMenu = PizzaMenu();
- late final Map<int, PizzaToppingData> _pizzaToppingsDataMap;
- late Pizza _pizza;
- _pizzaToppingsDataMap = pizzaMenu.getPizzaToppingsDataMap();
- _pizza = pizzaMenu.getPizza(0, _pizzaToppingsDataMap);
- _setSelectedIndex(index!);
_setSelectedPizza(index); - _selectedIndex = index;
- _setChipSelected(index, selected!);
_setSelectedPizza(_selectedIndex); - _pizzaToppingsDataMap[index]!.setSelected(isSelected: selected);
- _pizza = pizzaMenu.getPizza(index, _pizzaToppingsDataMap);
- onChanged: _onSelectedIndexChanged,
- CustomPizzaSelection(
pizzaToppingsDataMap: _pizzaToppingsDataMap,
onSelected: _onCustomPizzaChipSelected,
),
-
-
Flutter Design Patterns: Proxy | kazlauskas.dev
- Proxy, also known as Surrogate, belongs to the category of structural design patterns.
- The key idea in this pattern is to work through a separate proxy object that performs additional functionality when accessing an (already existing) object.
- the user's rights should be validated before accessing the object or the object's creation is very expensive so it makes sense to defer its creation until the object is actually needed.
- if you need to execute something either before or after the primary logic of the class, the proxy lets you do this without changing that class
- it can be passed to any client that expects a real service object.
- the proxy
- ServiceInterface
- defines the real object
- Service
- implements the same interface as the real service
- Proxy
- The Proxy class may be responsible for creating and deleting the real service's object;
- Client
- you can pass a proxy into any code that expects a service object.
- Lazy initialization (virtual proxy)
- Access control (protection proxy)
- Local execution of a remote service (remote proxy)
- Logging requests (logging proxy)
- Caching request results (caching proxy)
- Let's say we have a list of customers with some basic information - the customer's id and name. Any additional customer data should be loaded from an external web service. When providing the general list of customers, additional information is not loaded nor used. However, it could be accessed by selecting a specific customer and loading its data from the customer details service. To reduce the number of requests sent to the external service, it makes sense to introduce a caching layer to the application and provide the already loaded information from the cache for future requests.
-
CustomerDetailsServiceProxy
is a proxy service that contains the cache (dictionary object) and sends the request to the realCustomerDetailsService
only if the customer details object is not available in the cache. - the real customer details service.
- final email = faker.internet.email();
final hobby = faker.sport.name();
final position = faker.job.title();
return CustomerDetails(id, email, hobby, position); - a proxy for the real customer details service.
- the proxy service checks whether the customer details are already fetched and saved in the cache.
- final ICustomerDetailsService service;
- final Map<String, CustomerDetails> customerDetailsCache = {};
- if (customerDetailsCache.containsKey(id)) {
return customerDetailsCache[id]!;
} - final customerDetails = await service.getCustomerDetails(id);
customerDetailsCache[id] = customerDetails; -
ProxyExample
contains the proxy object of the real customer details service. - final ICustomerDetailsService _customerDetailsServiceProxy =
CustomerDetailsServiceProxy(CustomerDetailsService()); - service: _customerDetailsServiceProxy,
- onTap: () => _showCustomerDetails(customer),
- final ICustomerDetailsService service;
- widget.service.getCustomerDetails(widget.customer.id).then(
(CustomerDetails customerDetails) => setState(() {
widget.customer.details = customerDetails;
}),
); - widget.customer.details == null
- The
CustomerDetailsDialog
class does not care about the specific type of customer details service as long as it implements theICustomerDetailsService
interface. - when trying to load the specific customer's details for the first time, it takes some time for the information to load from the service. However, when the same information is accessed once again, it is provided from the cache stored in the proxy service, hence the request is not sent to the real customer details service - the customer details information is provided instantly.
-
-
Flutter Design Patterns: Prototype | kazlauskas.dev
- The Prototype is a creational design pattern
- instead of creating a new object, some prototype is used which allows the creation of new objects by copying from this prototype.
- you want to copy an object which has a complicated state
- your code only wants a copy of a specific object with the same state and you do not care about the details of how this state was reached - well, your code should just provide a way to copy (clone) it.
- The Prototype design pattern enables run-time flexibility since a class can be configured with different Prototype objects, which are copied to create new objects, and even more, Prototype objects can be added and removed dynamically at run-time.
- Prototype
- implements an operation for cloning itself. In addition to copying the original object's data to the clone, this method may also handle some edge cases of the cloning process related to cloning linked objects, untangling recursive dependencies, etc.;
- ConcretePrototype
- SubclassPrototype
- Client
- The Prototype design pattern should be used when your code should not depend on the concrete classes of objects that you need to copy.
- the client code with a general interface for working with all objects that support cloning
- the pattern could be used when you want to reduce the number of subclasses that only differ in the way they initialize their respective objects.
- Instead of instantiating a subclass that matches some configuration, the client can simply look for an appropriate prototype and clone it.
- the pattern is quite useful when you have a set of pre-built objects that are ready to be copied. These objects could be stored inside some kind of prototype registry from which you can access the frequently-used prototypes. In this way, you can instantiate a dynamically loaded class and use it inside your code.
-
clone()
- an abstract method to clone (copy) the specific shape; -
Shape.clone()
- a named constructor to create a shape object as a copy of the providedShape
value. - Shape.clone(Shape source) {
color = source.color;
} - Shape clone();
- Circle.clone(Circle source) : super.clone(source) {
radius = source.radius;
} -
Shape clone() {
return Circle.clone(this);
} - Rectangle.clone(Rectangle source) : super.clone(source) {
height = source.height;
width = source.width;
} -
Shape clone() {
return Rectangle.clone(this);
} - final Shape _circle = Circle.initial();
final Shape _rectangle = Rectangle.initial(); - _circleClone = _circle.clone();
- _rectangleClone = _rectangle.clone();
- onClonePressed: _cloneCircle,
- onClonePressed: _cloneRectangle,
- The
PrototypeExample
does not care about the specific type of shape object as long as it extends theShape
abstract class and implements all of its abstract methods.
-
-
Flutter Design Patterns: Memento | kazlauskas.dev
- Memento, also known as Token, belongs to the category of behavioural design patterns.
- Memento
- ConcreteMemento
- stores an Originator's internal state.
- protects against access by objects other than the Originator which has created the ConcreteMemento.
- Caretaker
- Originator
- creates a ConcreteMemento containing a snapshot of its current internal state
- provides the restore() method to restore the internal state using the ConcreteMemento.
- The Memento design pattern should be used when you want to produce snapshots of the object's state to be able to restore a previous state of the object.
- lets you make full copies of an object's state, including private fields, and store them separately from the object.
- the pattern could be used for safety reasons - when direct access to the object's fields/getters/setters violates its encapsulation.
- The Memento makes the object itself responsible for creating a snapshot of its state.
- No other object can read the snapshot, making the original object's state data safe and secure.
-
IMemento
is an abstract class that is used as an interface for the specific memento class: -
Memento
is a class that acts as a snapshot of the originator's internal state which is stored in thestate
property and returned via thegetState()
method. -
Originator
- a simple class that contains its internal state and stores the snapshot of it to theMemento
object using thecreateMemento()
method. Also, the originator's state could be restored from the provided Memento object using therestore()
method. - final Originator originator;
- late final IMemento _backup;
- _backup = originator.createMemento();
- final shape = originator.state;
- shape.color = Color.fromRGBO(
random.integer(255),
random.integer(255),
random.integer(255),
1.0,
);
shape.height = random.integer(150, min: 50).toDouble();
shape.width = random.integer(150, min: 50).toDouble(); - originator.restore(_backup);
- final ListQueue<ICommand> _commandList = ListQueue<ICommand>();
- _commandList.add(command);
- final command = _commandList.removeLast();
command.undo(); - An implementation of the
IMemento
interface which stores the snapshot ofOriginator's
internal state (Shape
object). - late final Shape _state;
- _state = Shape.copy(shape);
- A class that defines a
createMemento()
method to save the current internal state to aMemento
object. - late Shape state;
- state = Shape.initial();
- return Memento(state);
- state = memento.getState();
- final CommandHistory _commandHistory = CommandHistory();
- final Originator _originator = Originator();
- final command = RandomisePropertiesCommand(_originator);
_executeCommand(command); - command.execute();
_commandHistory.add(command); - _commandHistory.undo();
- onPressed: _randomiseProperties,
- onPressed: _commandHistory.isEmpty ? null : _undo,
- the Memento design pattern adds an additional layer to the example's state.
- It is stored inside the
Originator
object, the command itself does not mutate the state directly but through theOriginator
. - the backup (state's snapshot) stored inside the
Command
is aMemento
object and not the state (Shape
object) itself - in case of the state's restore (undo is triggered on the command), the specific command calls the
restore()
method on theOriginator
which restores its internal state to the value stored in the snapshot. - it allows restoring multiple property values (a whole complex state object) in a single request, while the state itself is completely separated from the command's code or UI logic.
-
-
Flutter Design Patterns: Command | kazlauskas.dev
- Command, also known as Action or Transaction, is one of the behavioural design patterns
- Command
- Concrete Commands (Command1/Command2)
- Invoker
- Receiver
- Client
- the operation (command) is extracted to a separate class
- which
- 관계대명사의 한정적 용법: a separate class, 어떤 class? 객체가 메소드의 아규먼트로 전달 될수도 있고, ~, 연결된 command가 런타임에 교체될 수도 있는
- object could be passed as a method argument, stored inside another object or the linked command could be switched at runtime.
- the Command design pattern is useful when you want to queue operations, schedule their execution, or execute them remotely. Since the command itself is just a simple class, its object (as any other object) could be serialized, stored, e.g. in the database or a text file and later restored as the initial command and executed. This is useful when you want to schedule a specific task that should be executed at a particular time, or on a recurring schedule.
- one of the most popular use-case for the Command is to use it for creating reversible operations. To be able to revert operations, you need to implement the history of performed operations.
- The command history is a stack that contains all executed command objects along with related backups of the application's state.
- The
Shape
is a receiver class that stores multiple properties defining the shape presented in UI:color
,height
andwidth
. - Shape shape;
- this.shape
- shape.color = Color.fromRGBO(
random.integer(255),
random.integer(255),
random.integer(255),
1.0,
); - Shape shape;
- this.shape
- shape.height = random.integer(150, min: 50).toDouble();
- Shape shape;
- this.shape
- shape.width = random.integer(150, min: 50).toDouble();
- final ListQueue<Command> _commandList = ListQueue<Command>();
- final command = _commandList.removeLast();
command.undo(); - final CommandHistory _commandHistory = CommandHistory();
- final Shape _shape = Shape.initial();
- final command = ChangeColorCommand(_shape);
- final command = ChangeHeightCommand(_shape);
- final command = ChangeWidthCommand(_shape);
- command.execute();
_commandHistory.add(command); - _commandHistory.undo();
-
-
Flutter Design Patterns: Abstract Factory | kazlauskas.dev
- Abstract Factory is a creational design pattern, also known as Kit.
- The main difference between these two patterns is that the Abstract Factory pattern provides a way to create a family of related objects - a single factory is responsible for creating several objects. As a result, you don't need to provide a separate factory for each specific class/component. In fact, you can consider the Factory Method design pattern as a subset of the Abstract Factory pattern - the Abstract Factory consists of several factory methods where each one of them creates only one specific object.
- Compile-time
- Run-time
- Abstract Factory
- Concrete Factory
- Product
- Concrete Product
- Client
-
-
Flutter Design Patterns: Factory Method | kazlauskas.dev
- Factory Method, also known as Virtual Constructor, belongs to the category of creational design patterns.
- Creator
- ConcreteCreator
- Product
- ConcreteProduct
- the factory method does not have to create a new instance of the object every single time. So if you want to save some system resources and reuse the already existing object instead of rebuilding it, implementing the Factory Method could be a way to go, e.g. by introducing a caching layer or storage which keeps the track of already created objects and returns the requested object when calling the factory method on a specific creator subclass.
- The Factory Method design pattern offers a relatively simple solution to this problem: every platform-specific widget has its concrete creator subclass extending the common abstract creator class. By introducing this, the UI code should only care about the common interface (base class) of all the specific components and based on the current platform an appropriate factory method would be called to create the widget - the code does not have to reference specific implementations of these components anymore.
- final dialog = create(context);
- AlertDialog
- CupertinoAlertDialog
- final List<CustomDialog> customDialogList = [
AndroidAlertDialog(),
IosAlertDialog(),
]; - final selectedDialog = customDialogList[_selectedDialogIndex];
- await selectedDialog.show(context);
- onPressed: () => _showCustomDialog(context),
-
-
Flutter Design Patterns: Iterator | kazlauskas.dev
- Iterator is a behavioural design pattern
- IterableCollection
- ConcreteCollection
- Iterator
- ConcreteIterator
- Client
- A class that stores the adjacency list of the graph. It is stored as a map data structure where the key represents the node's (vertex) id and the value is a list of vertices (ids of other nodes) adjacent to the vertex of that id (key).
- This algorithm uses the stack data structure to store vertices (nodes) which should be visited next using the
getNext()
method. - final DepthFirstTreeCollection treeCollection;
- final ListQueue<int> nodeStack = ListQueue<int>();
- nodeStack.add(_initialNode);
- Map<int, Set<int>> get adjacencyList => treeCollection.graph.adjacencyList;
- removeLast();
- visitedNodes.add(_currentNode);
- nodeStack.addLast(node);
- This algorithm uses the queue data structure to store vertices (nodes) that should be visited next using the
getNext()
method. - final BreadthFirstTreeCollection treeCollection;
- final ListQueue<int> nodeQueue = ListQueue<int>();
- nodeQueue.add(_initialNode);
- Map<int, Set<int>> get adjacencyList => treeCollection.graph.adjacencyList;
- removeFirst();
- visitedNodes.add(_currentNode);
- nodeQueue.addLast(node);
- final List<ITreeCollection> treeCollections = [];
- treeCollections.add(BreadthFirstTreeCollection(graph));
- treeCollections.add(DepthFirstTreeCollection(graph));
- final iterator =
treeCollections[_selectedTreeCollectionIndex].createIterator(); - onPressed: _currentNodeIndex == 0 ? _traverseTree : null,
- onPressed:
_isTraversing || _currentNodeIndex == 0 ? null : _reset,
-
-
Flutter Design Patterns: Interpreter | kazlauskas.dev
- The Interpreter is a behavioural design pattern
- AbstractExpression
- TerminalExpression
- NonterminalExpression
- Context
- Client
- the Interpreter represents the language itself, defines the behaviour, has some additional logic to interpret each entity in the tree, shares the same context between them - that's the main difference and the reason why this pattern is considered as a behavioural design pattern.
-
-
Flutter Design Patterns: Facade | kazlauskas.dev
- The Facade belongs to the category of structural design patterns
- The Facade pattern is very straightforward - it allows you to create a simplified class that wraps the set of methods/operations of the complex API or subsystem.
- Facade
- Additional Facade
- Subsystem classes
- Client
-
-
Flutter Design Patterns: State | kazlauskas.dev
- The State is a behavioural design pattern
- Context
- State
- ConcreteStates
- Client
- The main difference between these patterns - in the State pattern, the particular states may be aware of each other, while specific strategies almost never know any details about other implementations of the strategy.
- context.setState(LoadingState());
- context.setState(LoadingState());
- context.setState(LoadingState());
- context.setState(NoResultsState());
- context.setState(LoadedState(resultList));
- context.setState(ErrorState());
- final StateContext _stateContext = StateContext();
- await _stateContext.nextState();
- _stateContext.dispose();
super.dispose(); - StreamBuilder<IState>(
initialData: NoResultsState(),
stream: _stateContext.outState,
builder: (context, snapshot) => snapshot.data!.render(),
),
-
-
Flutter Design Patterns: Strategy | kazlauskas.dev
- Sorting algorithms
- Strategy, also known as policy, belongs to the category of behavioural design patterns.
- This enables compile-time flexibility - new algorithms can be added by defining new classes, and existing ones can be changed independently
- the extracted strategy class can be changed in the code dynamically at run-time
- the pattern is that allows you to isolate the code, internal data, and dependencies of various algorithms from the rest of the code - clients use a simple interface to execute the algorithms and switch them at run-time.
- Sorting algorithms
- Payment strategies
- Damage calculation in RPG game
- Strategy
- ConcreteStrategies
- Context
- Client
- The primary purpose of the Strategy design pattern is to encapsulate a family of algorithms (related algorithms) such that they could be callable through a common interface, hence being interchangeable based on the specific situation.
- ose of the Strateg
- A general rule of thumb - if you notice different behaviours lumped into a single class, or there are several conditional statements in the code for selecting a specific algorithm based on some kind of context or business rules (multiple if/else blocks, switch statements), this is a big indicator that you should use the Strategy design pattern and encapsulate the calculation logic in separate classes (strategies).
- software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
- final List<IShippingCostsStrategy> _shippingCostsStrategyList = [
InStorePickupStrategy(),
ParcelTerminalShippingStrategy(),
PriorityShippingStrategy(),
]; - final List<IShippingCostsStrategy> shippingOptions;
- final Order order;
- final IShippingCostsStrategy shippingCostsStrategy;
- double get shippingPrice => shippingCostsStrategy.calculate(order);
-
-
Flutter Design Patterns: Composite | kazlauskas.dev
- The Composite is one of the structural design patterns.
- Component
- Leaf
- Composite
- Client
- A general rule of thumb - if you have a set of groups or collections, this is a big indicator that you might be able to use the Composite design pattern
- The easier case to detect - you are using a tree structure.
-
-
Flutter Design Patterns: Singleton | kazlauskas.dev
- Singleton is a creational design pattern
-
-
Flutter Design Patterns: Template Method | kazlauskas.dev
- The Template Method is a behavioural design pattern
- var studentList = getStudentsData();
- studentList = doStudentsFiltering(studentList);
- _calculateStudentsBmi(studentList);
- return studentList;
- // Hook methods
- do
- // Abstract methods
- For this reason,
doStudentsFiltering()
method has a default implementation that does not change the workflow of the algorithm by default (hook operation). -
List<Student> doStudentsFiltering(List<Student> studentList) {
return studentList
.where((student) => student.age > 12 && student.age < 20)
.toList();
} - StudentsXmlBmiCalculator
- StudentsJsonBmiCalculator
- TeenageStudentsJsonBmiCalculator
- final StudentsBmiCalculator bmiCalculator;
-
-
Flutter Design Patterns: Adapter | kazlauskas.dev
- Adapter is a structural design pattern
- adapter: JsonContactsAdapter(),
- adapter: XmlContactsAdapter(),
- final IContactsAdapter adapter;
- final List<Contact> contacts = [];
- contacts.addAll(widget.adapter.getContacts());
- contacts: contacts,
- final List<Contact> contacts;
- required this.contacts,
- for (var contact in contacts)
ContactCard(
contact: contact,
)
-
댓글