├── .github └── workflows │ └── dart_tests.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── analysis_options.yaml ├── lib ├── adapter │ ├── adapter.dart │ └── adapter_usage.dart ├── decorator │ ├── decorator.dart │ └── decorator_usage.dart ├── factory_method │ ├── factory_method.dart │ └── factory_method_usage.dart ├── mediator │ ├── mediator.dart │ └── mediator_usage.dart ├── observer │ ├── observer.dart │ └── observer_usage.dart ├── proxy │ ├── proxy.dart │ └── proxy_usage.dart ├── repository │ ├── repository.dart │ └── repository_usage.dart ├── singleton │ ├── singleton.dart │ └── singleton_usage.dart └── strategy │ ├── strategy.dart │ └── strategy_usage.dart ├── pubspec.lock ├── pubspec.yaml └── test ├── adapter └── adapter_test.dart ├── decorator └── decorator_test.dart ├── factory_method └── factory_method_test.dart ├── mediator └── mediator_test.dart ├── observer └── observer_test.dart ├── proxy └── proxy_test.dart ├── repository └── repository_test.dart ├── singleton └── singleton_test.dart └── strategy └── strategy_test.dart /.github/workflows/dart_tests.yml: -------------------------------------------------------------------------------- 1 | name: Dart tests and analysis 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | name: Dart Design Patterns Analysis and Tests 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Setup Repository 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Dart 18 | uses: dart-lang/setup-dart@v1 19 | 20 | - name: Install Pub Dependencies 21 | run: dart pub get 22 | 23 | - name: Verify Formatting 24 | run: dart format --output=none --set-exit-if-changed . 25 | 26 | - name: Analyze Project Source 27 | run: dart analyze 28 | 29 | - name: Run tests 30 | run: dart test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build output. 6 | build/ 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Common Design Patterns in Dart 2 | 3 | This repository contains examples of common design patterns with accompanying diagrams, class implementations, and information about their usage, pros and cons. 4 | 5 | ### What are Design Patterns? 6 | 7 | Design patterns are reusable solutions to common software engineering problems. They provide a standard way to solve recurring problems in software development, making the code more modular, maintainable, and extensible. There are many design patterns, and understanding how to use them can significantly improve the quality of your code. 8 | 9 | ### Repository Contents 10 | 11 | The repository contains examples of the following design patterns: 12 | 13 | - [Adapter Pattern](lib/adapter/) 14 | - [Decorator Pattern](lib/decorator/) 15 | - [Factory Method Pattern](lib/factory_method/) 16 | - [Mediator Pattern](lib/mediator/) 17 | - [Observer Pattern](lib/observer/) 18 | - [Proxy Pattern](lib/proxy/) 19 | - [Repository Pattern](lib/repository/) 20 | - [Singleton Pattern](lib/singleton/) 21 | - [Strategy Pattern](lib/strategy/) 22 | 23 | --- 24 | 25 | ### [Adapter Pattern](lib/adapter/) 26 | 27 | The Adapter Pattern is a structural design pattern that allows two incompatible interfaces to work together by creating an adapter object that acts as a bridge between them. The Adapter Pattern is used when the interface of an existing class does not match the interface that the client expects. It is also used to reuse existing code with a new system, without having to modify the existing code. 28 | 29 | ``` 30 | +--------------+ +--------------------+ 31 | | Target | | Adapter | +---------------------- 32 | +--------------+ +--------------------+ | Adaptee | 33 | | + request() | <------ | - adaptee: Adaptee | -----> +---------------------+ 34 | +--------------+ | + request() | | + specificRequest() | 35 | +--------------------+ +---------------------+ 36 | ``` 37 | 38 | #### Pros: 39 | 40 | - Reusability: The Adapter pattern allows existing classes to be reused in new contexts without modifying their original code. This can save time and effort, as well as promote code reuse. 41 | - Interoperability: The Adapter pattern provides a way to interface between two incompatible or unrelated classes. This can be especially useful when working with third-party libraries or legacy code. 42 | - Decoupling: The Adapter pattern can help to decouple client code from the details of the Adaptee class, making it easier to modify or replace the Adaptee without affecting the client code. 43 | - Flexibility: The Adapter pattern can provide a flexible solution for integrating different components or systems, allowing them to work together seamlessly. 44 | 45 | #### Cons: 46 | 47 | - Complexity: Adding an adapter can introduce an additional layer of complexity to your code, which can make it harder to understand and maintain. 48 | - Overuse: Overusing the Adapter pattern can lead to code that is difficult to understand and maintain, as well as unnecessary layers of abstraction. 49 | - Performance: The use of an adapter can introduce a performance penalty, as it requires additional processing to translate between the two interfaces. However, this is usually negligible for most applications. 50 | 51 | ### [Decorator Pattern](lib/decorator/) 52 | 53 | The Decorator pattern is a structural design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects in the same class. 54 | 55 | In the Decorator pattern, a decorator class is used to wrap the original object and provide additional functionality to it. The decorator class has the same interface as the original object, so it can be used in the same way as the original object. However, the decorator class adds new functionality by modifying the behavior of the original object. 56 | 57 | ``` 58 | +----------------+ 59 | | Component | 60 | +----------------+ 61 | | + operation() | 62 | +----------------+ 63 | ^ 64 | | 65 | +----------------+ 66 | | Decorator | 67 | +----------------+ 68 | | - component | 69 | | + operation() | 70 | +----------------+ 71 | ^ 72 | | 73 | +----------------------------+ 74 | | | 75 | +----------------+ +----------------+ 76 | | Concrete CompA | | Concrete CompB | 77 | +----------------+ +----------------+ 78 | | + operation() | | + operation() | 79 | +----------------+ +----------------+ 80 | ``` 81 | 82 | #### Pros: 83 | 84 | - Encapsulation: The Decorator pattern allows the functionality of an object to be encapsulated within a decorator object. This makes it easy to add or remove functionality without affecting the underlying object, and helps to keep the code organized and maintainable. 85 | - Flexibility: The Decorator pattern allows new functionality to be added to an object at runtime, which can be useful in situations where the behavior of an object needs to be customized or extended dynamically. 86 | - Open-closed principle: The Decorator pattern supports the open-closed principle, which states that classes should be open for extension but closed for modification. This means that new functionality can be added to the application without modifying existing code. 87 | - Single Responsibility Principle: The Decorator pattern follows the Single Responsibility Principle, which states that a class should have only one reason to change. This makes the code more modular and easier to understand. 88 | 89 | #### Cons: 90 | 91 | - Complexity: The Decorator pattern can result in a large number of small classes, which can be difficult to manage and maintain. This can make the code more complex and harder to understand, especially if there are many layers of decorators. 92 | - Performance: The Decorator pattern can have a performance impact, especially if many decorators are used. Each decorator adds a layer of indirection, which can slow down the application. 93 | - Design overhead: The Decorator pattern requires a more complex design than simpler patterns like inheritance or composition. This can increase the initial design overhead and make it harder to get started with the project. 94 | - Dependency injection: The Decorator pattern can make it harder to use dependency injection frameworks, as the objects being decorated may need to be constructed in a certain order. 95 | 96 | ### [Factory Method Pattern](lib/factory_method/) 97 | 98 | The Factory Method pattern is a creational design pattern that provides an interface for creating objects, but allows subclasses to alter the type of objects that will be created. The Factory Method pattern is useful when you want to decouple the creation of an object from its use, and when you need to create objects that have varying implementations or configurations. 99 | 100 | ``` 101 | +-------------------+ +----------------+ 102 | | Creator | | Product | 103 | +-------------------+ +----------------+ 104 | | factoryMethod() |<-----------+ | operation() | 105 | +-------------------+ +----------------+ 106 | ^ ^ 107 | | | 108 | +---------------------------------+ +-------------------------------+ 109 | | ConcreteCreator | | ConcreteProduct | 110 | +---------------------------------+ +-------------------------------+ 111 | | factoryMethod() : Product | | operation() : void | 112 | +---------------------------------+ +-------------------------------+ 113 | ``` 114 | 115 | #### Pros: 116 | 117 | - Encapsulation: The Factory Method Pattern encapsulates the object creation process and allows clients to use the created objects without needing to know how they are created. 118 | - Code reuse: By using the Factory Method Pattern, you can reuse the same code to create different types of objects by creating different subclasses of the factory. 119 | - Flexibility: The Factory Method Pattern makes it easy to add new types of objects to a system by simply creating a new subclass of the factory. 120 | - Loose coupling: The Factory Method Pattern promotes loose coupling between classes, making it easy to change the implementation of a class without affecting the rest of the system. 121 | 122 | #### Cons: 123 | 124 | - Complexity: The Factory Method Pattern can add complexity to a system, especially if there are a large number of different types of objects that need to be created. 125 | - Overhead: Using the Factory Method Pattern can introduce additional overhead in terms of object creation, which may not be necessary in simpler systems. 126 | - Inflexibility: If the object creation process needs to be modified or customized on a per-instance basis, the Factory Method Pattern may not be flexible enough to handle these requirements. 127 | - Abstraction: The Factory Method Pattern requires a level of abstraction and generalization that may not be appropriate for all systems. 128 | 129 | ### [Mediator Pattern](lib/mediator/) 130 | 131 | The Mediator pattern is a behavioral design pattern that helps to reduce the dependencies between the objects in a system by introducing a mediator object that acts as a communication hub between the objects. In this pattern, the objects don't communicate with each other directly, but instead, they communicate through the mediator. This helps to improve the maintainability and scalability of the system. 132 | 133 | ``` 134 | +-------------+ +------------------------+ 135 | | Colleague | | Mediator | 136 | +----------- + +------------------------+ 137 | | | | +colleagueChanged() | 138 | |+send(msg) | | | 139 | | |<----------->| -colleague1: Colleague | 140 | | | | -colleague2: Colleague | 141 | +-------------+ | ... | 142 | +------------------------+ 143 | / | \ 144 | / | \ 145 | / | \ 146 | +------------+ +------------+ +------------+ 147 | | Colleague1 | | Colleague2 | | Colleague3 | 148 | +------------+ +------------+ +------------+ 149 | | | | | | | 150 | | | | | | | 151 | +------------+ +------------+ +------------+ 152 | ``` 153 | 154 | #### Pros: 155 | 156 | - Loose coupling: The Mediator Pattern promotes loose coupling between objects by encapsulating their interactions in a mediator object, which helps to reduce the dependencies between objects and makes the code easier to maintain and modify. 157 | - Centralized control: The Mediator Pattern provides a centralized control point for coordinating the interactions between objects, which can help to simplify the code and reduce the complexity of the system. 158 | - Reusability: The Mediator Pattern can promote code reuse by providing a reusable mediator object that can be used across multiple objects and interactions. 159 | - Extensibility: The Mediator Pattern makes it easy to add new objects to a system and incorporate them into the interactions between existing objects, without having to modify the code for the existing objects. 160 | 161 | #### Cons: 162 | 163 | - Overhead: The Mediator Pattern can introduce additional overhead in terms of the mediator object and the communication between objects, which may not be necessary in simpler systems. 164 | - Complexity: The Mediator Pattern can add complexity to a system, especially if there are a large number of objects and interactions that need to be coordinated. 165 | - Single point of failure: The Mediator Pattern can create a single point of failure in the system, since all objects rely on the mediator object to coordinate their interactions. 166 | - Tight coupling with mediator: If objects become too tightly coupled with the mediator object, it can lead to increased complexity and reduced flexibility. 167 | 168 | ### [Observer Pattern](lib/observer/) 169 | 170 | The Observer pattern is a behavioral design pattern that is used to establish a one-to-many relationship between objects. In this pattern, an object (the subject) maintains a list of its dependents (observers) and notifies them automatically when any changes to its state occur. The Observer pattern is useful when you have a complex system with many objects that need to stay updated with the state changes of one or more objects. 171 | 172 | ``` 173 | +----------------+ +----------------------+ 174 | | Subject |<>--------->| Observer | 175 | +----------------+ +----------------------+ 176 | | +notify() | | +update() | 177 | +----------------+ +----------------------+ 178 | ^ 179 | | 180 | +--------------------+ 181 | | Concrete Subject | 182 | +--------------------+ 183 | | | 184 | +--------------------+ 185 | ``` 186 | 187 | #### Pros: 188 | 189 | - Loose coupling: The Observer Pattern promotes loose coupling between objects, since the dependent objects don't need to know about the implementation details of the observed object, but only need to be notified of changes. 190 | - Flexibility: The Observer Pattern makes it easy to add and remove observers from the system without affecting the observed object or other observers. 191 | - Reusability: The Observer Pattern can promote code reuse by providing a common interface for observers, which can be used across different observed objects. 192 | - Scalability: The Observer Pattern can be used to create complex systems with many objects, since it allows for efficient communication between objects without requiring them to be tightly coupled. 193 | 194 | #### Cons: 195 | 196 | - Overhead: The Observer Pattern can introduce additional overhead in terms of the notifications and updates between objects, which may not be necessary in simpler systems. 197 | - Unwanted notifications: The Observer Pattern can lead to unwanted notifications, especially if there are many observers and the observed object changes frequently. 198 | - Inconsistent state: The Observer Pattern can lead to inconsistent state between objects, especially if the dependent objects are not updated in the correct order or frequency. 199 | - Security issues: The Observer Pattern can create security issues if sensitive information is passed along to untrusted observers. 200 | 201 | ### [Proxy Pattern](lib/proxy/) 202 | 203 | The Proxy Design Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. It allows us to create an intermediary object that acts as a stand-in for another object, called the "real subject," and manages all communication with the real subject. 204 | In general, the Proxy Design Pattern is used to provide a level of indirection between clients and the real object, with the proxy object acting as an intermediary. This can provide benefits such as increased security, performance improvements, and more flexible or modular code design. 205 | 206 | ``` 207 | +-------------+ 208 | | Subject | 209 | +-------------+ 210 | | + request() | 211 | +------+------+ 212 | | 213 | +-------------+ 214 | | RealSubject | 215 | +-------------+ 216 | | + request() | 217 | +------+------+ 218 | | 219 | +--------------+-------------+ 220 | | Proxy | 221 | +----------------------------+ 222 | | - realSubject: RealSubject | 223 | | + request() | 224 | +----------------------------+ 225 | ``` 226 | 227 | #### Pros: 228 | 229 | - Increased security: By using a proxy object to control access to the real object, we can ensure that only authorized clients can access the real object. 230 | - Improved performance: If the real object is expensive to create or use, the proxy object can cache results, delay the creation of the real object until it is actually needed, or perform other optimizations to improve performance. 231 | - Modular design: The proxy object can provide a modular design that separates concerns between the client and the real object. This can make the code easier to maintain and update. 232 | - Flexibility: The proxy object can provide additional functionality such as logging, caching, and error checking, without modifying the real object's code. 233 | 234 | #### Cons: 235 | 236 | - Additional complexity: The use of a proxy object can add an additional layer of complexity to the code, especially if the proxy object is designed to provide additional functionality. 237 | - Potential overhead: The use of a proxy object can potentially add overhead to the system by introducing additional processing and communication between the client and the real object. 238 | - Reduced performance: In some cases, the use of a proxy object can actually reduce performance, especially if the proxy object is designed to perform additional checks or validations before forwarding the request to the real object. 239 | 240 | ### [Repository Pattern](lib/repository/) 241 | 242 | The Repository Pattern is a structural pattern that defines a structure for organizing the code that separates the concerns of data access from the business logic of an application. It does this by introducing a layer of abstraction between the application and the data storage layer, which helps to decouple the code and improve its modularity. 243 | 244 | ``` 245 | +-----------------------+ +-----------------------+ 246 | | Application Code | | Repository | 247 | +-----------------------+ +-----------------------+ 248 | | + fetch() | uses | + fetch() | 249 | | + update() | --------> | + update() | 250 | | + delete() | | + delete() | 251 | +-----------------------+ | + ... | 252 | | +-----------------------+ 253 | v | 254 | +-----------------+ v 255 | | Data | +-----------------------+ 256 | +-----------------+ | Data Access Code | 257 | | + field: type | +-----------------------+ 258 | | + field: type | | + fetch() | 259 | | + ... | | + update() | 260 | +-----------------+ | + delete() | 261 | +-----------------------+ 262 | ``` 263 | 264 | #### Pros: 265 | 266 | - Separation of concerns: The Repository Pattern helps to separate the data access logic from the rest of the application code. This makes it easier to maintain and test the code, as well as making it - more modular and flexible. 267 | - Abstraction: The Repository Pattern provides an abstraction layer between the application code and the data access code, which allows the data access implementation to be changed without affecting the application code. 268 | - Reusability: The Repository Pattern promotes reusability of code by defining a common interface that can be used by multiple parts of the application. 269 | 270 | #### Cons: 271 | 272 | - Overhead: Implementing the Repository Pattern can add extra overhead to the code, as it requires additional classes and interfaces to be defined. This can make the code more complex and difficult to understand. 273 | - Complexity: The Repository Pattern can be complex to implement correctly, especially when dealing with complex data structures or queries. 274 | - Learning curve: The Repository Pattern requires a certain level of understanding of software design patterns and abstraction, which may take time to learn and implement correctly. 275 | 276 | ### [Singleton Pattern](lib/singleton/) 277 | 278 | The Singleton pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to it. 279 | 280 | ``` 281 | +----------------------------+ 282 | | Singleton | 283 | +----------------------------+ 284 | | -instance: Singleton | 285 | +----------------------------+ 286 | | +getInstance(): Singleton | 287 | +----------------------------+ 288 | ``` 289 | 290 | #### Pros: 291 | 292 | - Controlled access: The Singleton Pattern provides a single point of access to the instance of a class, which can help to control and regulate access to the object. 293 | - Global access: The Singleton Pattern provides a global point of access to the object, which can be useful in situations where multiple objects need to share the same state or resources. 294 | - Efficiency: The Singleton Pattern can improve performance by ensuring that only one instance of an object is created, which can reduce memory usage and increase efficiency. 295 | - Easy to implement: The Singleton Pattern is relatively easy to implement and can be used in a wide range of scenarios. 296 | 297 | #### Cons: 298 | 299 | - Tight coupling: The Singleton Pattern can create tight coupling between the singleton class and other classes that depend on it, which can make the code less flexible and harder to maintain. 300 | - Testability: The Singleton Pattern can make it more difficult to test code, since it relies on a global instance that cannot be easily replaced or modified for testing purposes. 301 | - Concurrency issues: The Singleton Pattern can lead to concurrency issues in multithreaded environments, since multiple threads may attempt to access or modify the same instance of the object. 302 | - Hidden dependencies: The Singleton Pattern can create hidden dependencies in the code, since other classes may rely on the singleton instance without explicitly stating their dependencies. 303 | 304 | ### [Strategy Pattern](lib/strategy/) 305 | 306 | The Strategy Pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one as an object, and make them interchangeable at runtime. The pattern separates the algorithm implementation from the client code that uses it, which makes it easier to add new algorithms or modify existing ones without affecting the client code. 307 | 308 | The basic idea behind the Strategy Pattern is to define a common interface for all algorithms in the family. This interface defines a set of methods that the client code can use to interact with the algorithm objects. Each algorithm implementation is then encapsulated in a separate class that implements the common interface. 309 | 310 | ``` 311 | +---------------------+ +------------------+ +------------------+ 312 | | Context |<>----------| Strategy |<>-------->| ConcreteStrategy | 313 | |---------------------| |------------------| |------------------| 314 | | -strategy: | | +execute():void | | +execute():void | 315 | | +setStrategy() | +------------------| +------------------+ 316 | | +executeStrategy() | 317 | +---------------------+ 318 | ``` 319 | 320 | #### Pros: 321 | 322 | - Reusability: The Strategy Pattern promotes code reuse by allowing the client code to reuse the same Context object with different Strategy objects, rather than creating a new object for each algorithm. 323 | - Flexibility: The Strategy Pattern makes it easy to switch between different algorithm implementations at runtime, which can be useful in situations where the algorithm requirements change frequently. 324 | - Maintainability: The Strategy Pattern separates the algorithm implementation from the client code, which makes it easier to modify or add new algorithms without affecting the existing code. 325 | - Testability: The Strategy Pattern makes it easier to test the algorithm implementations in isolation, which can be useful in situations where the algorithms are complex or require extensive testing. 326 | 327 | #### Cons: 328 | 329 | - Complexity: The Strategy Pattern adds an additional layer of complexity to the code, which can make it harder to understand or maintain. 330 | - Memory overhead: The Strategy Pattern requires the creation of additional objects for each algorithm implementation, which can increase memory usage. 331 | - Increased development time: Implementing the Strategy Pattern requires additional development time to create the interface or abstract class, implement the concrete classes, and integrate them with the client code. 332 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | exclude: 3 | 4 | strong-mode: 5 | implicit-dynamic: false 6 | errors: 7 | todo: ignore 8 | missing_required_param: warning 9 | missing_return: warning 10 | # https://github.com/flutter/flutter/pull/24528 11 | sdk_version_async_exported_from_core: ignore 12 | 13 | linter: 14 | rules: 15 | # Taken from https://github.com/dart-lang/linter/blob/master/example/all.yaml 16 | - always_declare_return_types 17 | - always_put_control_body_on_new_line 18 | - always_put_required_named_parameters_first 19 | - always_require_non_null_named_parameters 20 | - always_specify_types 21 | - annotate_overrides 22 | - avoid_bool_literals_in_conditional_expressions 23 | - avoid_catches_without_on_clauses 24 | - avoid_empty_else 25 | - avoid_equals_and_hash_code_on_mutable_classes 26 | - avoid_field_initializers_in_const_classes 27 | - avoid_function_literals_in_foreach_calls 28 | - avoid_init_to_null 29 | - avoid_null_checks_in_equality_operators 30 | - avoid_positional_boolean_parameters 31 | # - avoid_print 32 | - avoid_redundant_argument_values 33 | - avoid_relative_lib_imports 34 | - avoid_renaming_method_parameters 35 | - avoid_return_types_on_setters 36 | - avoid_returning_null 37 | - avoid_returning_null_for_future 38 | - avoid_returning_null_for_void 39 | # - avoid_setters_without_getters 40 | - avoid_shadowing_type_parameters 41 | - avoid_single_cascade_in_expression_statements 42 | - avoid_slow_async_io 43 | - avoid_types_as_parameter_names 44 | - avoid_unnecessary_containers 45 | - avoid_unused_constructor_parameters 46 | - avoid_void_async 47 | - await_only_futures 48 | - camel_case_types 49 | - cancel_subscriptions 50 | # - cascade_invocations 51 | - close_sinks 52 | # - comment_references 53 | - constant_identifier_names 54 | - control_flow_in_finally 55 | - curly_braces_in_flow_control_structures 56 | # - directives_ordering 57 | - empty_catches 58 | - empty_constructor_bodies 59 | - empty_statements 60 | - exhaustive_cases 61 | - file_names 62 | - flutter_style_todos 63 | - hash_and_equals 64 | - implementation_imports 65 | - iterable_contains_unrelated_type 66 | - join_return_with_assignment 67 | - library_names 68 | - library_prefixes 69 | # - lines_longer_than_80_chars 70 | - list_remove_unrelated_type 71 | - no_adjacent_strings_in_list 72 | - no_duplicate_case_values 73 | - non_constant_identifier_names 74 | - null_closures 75 | - only_throw_errors 76 | - overridden_fields 77 | - package_api_docs 78 | - package_names 79 | - package_prefixed_library_names 80 | - parameter_assignments 81 | - prefer_adjacent_string_concatenation 82 | - prefer_asserts_in_initializer_lists 83 | - prefer_collection_literals 84 | - prefer_conditional_assignment 85 | - prefer_const_constructors 86 | - prefer_const_constructors_in_immutables 87 | - prefer_const_declarations 88 | - prefer_const_literals_to_create_immutables 89 | - prefer_constructors_over_static_methods 90 | - prefer_contains 91 | - prefer_equal_for_default_values 92 | - prefer_expression_function_bodies # https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods 93 | - prefer_final_fields 94 | - prefer_final_in_for_each 95 | - prefer_final_locals 96 | - prefer_foreach 97 | - prefer_function_declarations_over_variables 98 | - prefer_generic_function_type_aliases 99 | - prefer_initializing_formals 100 | - prefer_int_literals 101 | - prefer_interpolation_to_compose_strings 102 | - prefer_is_empty 103 | - prefer_is_not_empty 104 | - prefer_iterable_whereType 105 | - prefer_mixin 106 | - prefer_single_quotes 107 | - prefer_typing_uninitialized_variables 108 | - prefer_void_to_null 109 | # - public_member_api_docs 110 | - recursive_getters 111 | - slash_for_doc_comments 112 | - sort_constructors_first 113 | # - sort_pub_dependencies 114 | - sort_unnamed_constructors_first 115 | - test_types_in_equals 116 | - throw_in_finally 117 | - type_init_formals 118 | - unawaited_futures 119 | - unnecessary_await_in_return 120 | - unnecessary_brace_in_string_interps 121 | - unnecessary_const 122 | - unnecessary_getters_setters 123 | - unnecessary_new 124 | - unnecessary_null_aware_assignments 125 | - unnecessary_null_in_if_null_operators 126 | - unnecessary_overrides 127 | - unnecessary_parenthesis 128 | - unnecessary_statements 129 | - unnecessary_this 130 | - unrelated_type_equality_checks 131 | - use_function_type_syntax_for_parameters 132 | - use_rethrow_when_possible 133 | # - use_setters_to_change_properties 134 | - use_string_buffers 135 | - use_to_and_as_if_applicable 136 | - valid_regexps 137 | - void_checks 138 | -------------------------------------------------------------------------------- /lib/adapter/adapter.dart: -------------------------------------------------------------------------------- 1 | abstract class Target { 2 | void request(); 3 | } 4 | 5 | class Adaptee { 6 | void specificRequest() => print("Adaptee's specificRequest called."); 7 | } 8 | 9 | class Adapter implements Target { 10 | Adapter(Adaptee adaptee) { 11 | _adaptee = adaptee; 12 | } 13 | 14 | late Adaptee _adaptee; 15 | 16 | @override 17 | void request() => _adaptee.specificRequest(); 18 | } 19 | 20 | void clientCode(Target target) => target.request(); 21 | -------------------------------------------------------------------------------- /lib/adapter/adapter_usage.dart: -------------------------------------------------------------------------------- 1 | import 'adapter.dart'; 2 | 3 | void main() { 4 | final Adaptee adaptee = Adaptee(); 5 | final Target target = Adapter(adaptee); 6 | 7 | clientCode(target); 8 | } 9 | -------------------------------------------------------------------------------- /lib/decorator/decorator.dart: -------------------------------------------------------------------------------- 1 | abstract class Component { 2 | void operation(); 3 | } 4 | 5 | class ConcreteComponent implements Component { 6 | @override 7 | void operation() => print('$runtimeType operation'); 8 | } 9 | 10 | abstract class Decorator implements Component { 11 | Decorator(Component component) { 12 | _component = component; 13 | } 14 | 15 | late Component _component; 16 | 17 | @override 18 | void operation() => _component.operation(); 19 | } 20 | 21 | class ConcreteDecoratorA extends Decorator { 22 | ConcreteDecoratorA(Component component) : super(component); 23 | 24 | @override 25 | void operation() { 26 | super.operation(); 27 | 28 | print('$runtimeType operation'); 29 | } 30 | } 31 | 32 | class ConcreteDecoratorB extends Decorator { 33 | ConcreteDecoratorB(Component component) : super(component); 34 | 35 | @override 36 | void operation() { 37 | super.operation(); 38 | 39 | print('$runtimeType operation'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/decorator/decorator_usage.dart: -------------------------------------------------------------------------------- 1 | import 'decorator.dart'; 2 | 3 | void main() { 4 | Component component = ConcreteComponent(); 5 | 6 | component = ConcreteDecoratorA(component); 7 | component = ConcreteDecoratorB(component); 8 | 9 | component.operation(); 10 | } 11 | -------------------------------------------------------------------------------- /lib/factory_method/factory_method.dart: -------------------------------------------------------------------------------- 1 | abstract class Product { 2 | void doSomething(T data); 3 | } 4 | 5 | abstract class Creator { 6 | Product createProduct(); 7 | } 8 | 9 | class ConcreteProductA implements Product { 10 | @override 11 | void doSomething(String data) => 12 | print('Doing something with data: $data in Product A'); 13 | } 14 | 15 | class ConcreteProductB implements Product { 16 | @override 17 | void doSomething(int data) => 18 | print('Doing something with data: $data in Product B'); 19 | } 20 | 21 | class ConcreteCreatorA implements Creator { 22 | @override 23 | Product createProduct() => ConcreteProductA(); 24 | } 25 | 26 | class ConcreteCreatorB implements Creator { 27 | @override 28 | Product createProduct() => ConcreteProductB(); 29 | } 30 | -------------------------------------------------------------------------------- /lib/factory_method/factory_method_usage.dart: -------------------------------------------------------------------------------- 1 | import 'factory_method.dart'; 2 | 3 | void main() { 4 | final Creator creatorA = ConcreteCreatorA(); 5 | final Product productA = creatorA.createProduct(); 6 | 7 | productA.doSomething('hello'); 8 | 9 | final Creator creatorB = ConcreteCreatorB(); 10 | final Product productB = creatorB.createProduct(); 11 | 12 | productB.doSomething(123); 13 | } 14 | -------------------------------------------------------------------------------- /lib/mediator/mediator.dart: -------------------------------------------------------------------------------- 1 | abstract class BaseMediator { 2 | final List> _colleagues = >[]; 3 | 4 | void register(BaseColleague colleague) => _colleagues.add(colleague); 5 | 6 | void notify(BaseColleague sender, T message) { 7 | for (final BaseColleague colleague in _colleagues) { 8 | if (colleague != sender) { 9 | colleague.receive(message); 10 | } 11 | } 12 | } 13 | } 14 | 15 | abstract class BaseColleague { 16 | BaseColleague(this._mediator); 17 | 18 | final BaseMediator _mediator; 19 | 20 | void send(T message) => _mediator.notify(this, message); 21 | 22 | void receive(T message); 23 | } 24 | 25 | class ConcreteMediator extends BaseMediator { 26 | @override 27 | void notify(BaseColleague sender, String message) { 28 | for (final BaseColleague colleague in _colleagues) { 29 | if (colleague != sender) { 30 | colleague.receive(message); 31 | } 32 | } 33 | } 34 | } 35 | 36 | class ConcreteColleague extends BaseColleague { 37 | ConcreteColleague(BaseMediator mediator, this.name) : super(mediator); 38 | 39 | String name; 40 | 41 | @override 42 | void receive(String message) => print('$name received message: $message'); 43 | } 44 | -------------------------------------------------------------------------------- /lib/mediator/mediator_usage.dart: -------------------------------------------------------------------------------- 1 | import 'mediator.dart'; 2 | 3 | void main() { 4 | final ConcreteMediator mediator = ConcreteMediator(); 5 | 6 | final ConcreteColleague colleague1 = 7 | ConcreteColleague(mediator, 'Colleague 1'); 8 | final ConcreteColleague colleague2 = 9 | ConcreteColleague(mediator, 'Colleague 2'); 10 | 11 | mediator.register(colleague1); 12 | mediator.register(colleague2); 13 | 14 | colleague1.send('Hello, Colleague 2!'); 15 | colleague2.send('Hi, Colleague 1!'); 16 | } 17 | -------------------------------------------------------------------------------- /lib/observer/observer.dart: -------------------------------------------------------------------------------- 1 | abstract class BaseObserver { 2 | void update(T message); 3 | } 4 | 5 | class Subject { 6 | final List> _observers = >[]; 7 | 8 | int get observersCount => _observers.length; 9 | 10 | void addObserver(BaseObserver observer) => _observers.add(observer); 11 | 12 | void removeObserver(BaseObserver observer) => _observers.remove(observer); 13 | 14 | void notifyObservers(T message) { 15 | for (final BaseObserver observer in _observers) { 16 | observer.update(message); 17 | } 18 | } 19 | } 20 | 21 | class ConcreteObserverA implements BaseObserver { 22 | @override 23 | void update(String message) => 24 | print('$runtimeType received message: $message'); 25 | } 26 | 27 | class ConcreteObserverB implements BaseObserver { 28 | @override 29 | void update(String message) => 30 | print('$runtimeType received message: $message'); 31 | } 32 | -------------------------------------------------------------------------------- /lib/observer/observer_usage.dart: -------------------------------------------------------------------------------- 1 | import 'observer.dart'; 2 | 3 | void main() { 4 | final Subject subject = Subject(); 5 | 6 | final ConcreteObserverA observerA = ConcreteObserverA(); 7 | final ConcreteObserverB observerB = ConcreteObserverB(); 8 | 9 | subject.addObserver(observerA); 10 | subject.addObserver(observerB); 11 | 12 | subject.notifyObservers('Hello World!'); 13 | 14 | subject.removeObserver(observerA); 15 | 16 | subject.notifyObservers('Goodbye World!'); 17 | } 18 | -------------------------------------------------------------------------------- /lib/proxy/proxy.dart: -------------------------------------------------------------------------------- 1 | abstract class Subject { 2 | void request(T message); 3 | } 4 | 5 | class RealSubject implements Subject { 6 | @override 7 | void request(T message) => 8 | print('RealSubject: Handling request with message $message.'); 9 | } 10 | 11 | class Proxy implements Subject { 12 | final RealSubject _realSubject = RealSubject(); 13 | 14 | @override 15 | void request(T message) => _realSubject.request(message); 16 | } 17 | -------------------------------------------------------------------------------- /lib/proxy/proxy_usage.dart: -------------------------------------------------------------------------------- 1 | import 'proxy.dart'; 2 | 3 | void main() { 4 | final Proxy proxy = Proxy(); 5 | 6 | proxy.request('Hello World.'); 7 | proxy.request('Hello Dart.'); 8 | } 9 | -------------------------------------------------------------------------------- /lib/repository/repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | 3 | abstract class BaseRepository { 4 | Future getById(int id); 5 | Future> getAll(); 6 | Future insert(T item); 7 | Future update(T item); 8 | Future delete(int id); 9 | } 10 | 11 | class NetworkUserRepository implements BaseRepository { 12 | @override 13 | Future getById(int id) => throw UnimplementedError(); 14 | 15 | @override 16 | Future> getAll() => throw UnimplementedError(); 17 | 18 | @override 19 | Future insert(UserModel item) => throw UnimplementedError(); 20 | 21 | @override 22 | Future update(UserModel item) => throw UnimplementedError(); 23 | 24 | @override 25 | Future delete(int id) => throw UnimplementedError(); 26 | } 27 | 28 | class MockUserRepository implements BaseRepository { 29 | final List datasource = []; 30 | 31 | @override 32 | Future getById(int id) async => 33 | datasource.firstWhereOrNull((UserModel element) => element.id == id); 34 | 35 | @override 36 | Future> getAll() async => datasource; 37 | 38 | @override 39 | Future insert(UserModel user) async => datasource.add(user); 40 | 41 | @override 42 | Future update(UserModel user) async { 43 | final int index = 44 | datasource.indexWhere((UserModel element) => element.id == user.id); 45 | 46 | datasource[index] = user; 47 | } 48 | 49 | @override 50 | Future delete(int id) async => 51 | datasource.removeWhere((UserModel element) => element.id == id); 52 | } 53 | 54 | class UserModel { 55 | UserModel({ 56 | required this.id, 57 | required this.name, 58 | required this.email, 59 | }); 60 | 61 | final int id; 62 | final String name; 63 | final String email; 64 | } 65 | -------------------------------------------------------------------------------- /lib/repository/repository_usage.dart: -------------------------------------------------------------------------------- 1 | import 'repository.dart'; 2 | 3 | Future main() async { 4 | final BaseRepository repository = MockUserRepository(); 5 | 6 | final UserModel newUser = 7 | UserModel(id: 1, name: 'John Doe', email: 'johndoe@example.com'); 8 | await repository.insert(newUser); 9 | 10 | final UserModel? user = await repository.getById(1); 11 | 12 | print('id: ${user?.id}, name: ${user?.name}, email:${user?.email}'); 13 | 14 | final UserModel updatedUser = 15 | UserModel(id: 1, name: 'New Jane Doe', email: 'janedoe@example.com'); 16 | await repository.update(updatedUser); 17 | 18 | final List users = await repository.getAll(); 19 | 20 | print('user count: ${users.length}'); 21 | 22 | for (final UserModel user in users) { 23 | print('id: ${user.id}, name: ${user.name}, email:${user.email}'); 24 | } 25 | 26 | await repository.delete(1); 27 | 28 | final List usersAfterDelete = await repository.getAll(); 29 | 30 | print('user count: ${users.length}'); 31 | 32 | for (final UserModel user in usersAfterDelete) { 33 | print('id: ${user.id}, name: ${user.name}, email:${user.email}'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/singleton/singleton.dart: -------------------------------------------------------------------------------- 1 | class Singleton { 2 | factory Singleton() => _instance; 3 | 4 | Singleton._internal(); 5 | 6 | static final Singleton _instance = Singleton._internal(); 7 | } 8 | 9 | class LazySingleton { 10 | factory LazySingleton() => _instance ??= LazySingleton._internal(); 11 | 12 | LazySingleton._internal(); 13 | 14 | static LazySingleton? _instance; 15 | } 16 | -------------------------------------------------------------------------------- /lib/singleton/singleton_usage.dart: -------------------------------------------------------------------------------- 1 | import 'singleton.dart'; 2 | 3 | void main() { 4 | final Singleton singleton1 = Singleton(); 5 | final Singleton singleton2 = Singleton(); 6 | 7 | print(singleton1 == singleton2); 8 | 9 | final LazySingleton lazySingleton1 = LazySingleton(); 10 | final LazySingleton lazySingleton2 = LazySingleton(); 11 | 12 | print(lazySingleton1 == lazySingleton2); 13 | } 14 | -------------------------------------------------------------------------------- /lib/strategy/strategy.dart: -------------------------------------------------------------------------------- 1 | abstract class Strategy { 2 | void execute(); 3 | } 4 | 5 | class ConcreteStrategyA implements Strategy { 6 | @override 7 | void execute() => print('Executing Concrete Strategy A'); 8 | } 9 | 10 | class ConcreteStrategyB implements Strategy { 11 | @override 12 | void execute() => print('Executing Concrete Strategy B'); 13 | } 14 | 15 | class Context { 16 | Context(this._strategy); 17 | 18 | Strategy _strategy; 19 | 20 | void setStrategy(Strategy strategy) => _strategy = strategy; 21 | 22 | void executeStrategy() => _strategy.execute(); 23 | } 24 | -------------------------------------------------------------------------------- /lib/strategy/strategy_usage.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_design_patterns/strategy/strategy.dart'; 2 | 3 | void main() { 4 | final ConcreteStrategyA strategyA = ConcreteStrategyA(); 5 | final ConcreteStrategyB strategyB = ConcreteStrategyB(); 6 | 7 | final Context context = Context(strategyA); 8 | 9 | context.executeStrategy(); 10 | 11 | context.setStrategy(strategyB); 12 | context.executeStrategy(); 13 | } 14 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "40.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "4.1.0" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.3.1" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.9.0" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.1.0" 39 | collection: 40 | dependency: "direct main" 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.17.0" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "3.1.0" 53 | coverage: 54 | dependency: transitive 55 | description: 56 | name: coverage 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.6.2" 60 | crypto: 61 | dependency: transitive 62 | description: 63 | name: crypto 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "3.0.2" 67 | file: 68 | dependency: transitive 69 | description: 70 | name: file 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "6.1.4" 74 | frontend_server_client: 75 | dependency: transitive 76 | description: 77 | name: frontend_server_client 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "2.1.3" 81 | glob: 82 | dependency: transitive 83 | description: 84 | name: glob 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "2.1.1" 88 | http_multi_server: 89 | dependency: transitive 90 | description: 91 | name: http_multi_server 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "3.2.1" 95 | http_parser: 96 | dependency: transitive 97 | description: 98 | name: http_parser 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "4.0.2" 102 | io: 103 | dependency: transitive 104 | description: 105 | name: io 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "1.0.4" 109 | js: 110 | dependency: transitive 111 | description: 112 | name: js 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "0.6.5" 116 | lints: 117 | dependency: "direct dev" 118 | description: 119 | name: lints 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "1.0.1" 123 | logging: 124 | dependency: transitive 125 | description: 126 | name: logging 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.1.0" 130 | matcher: 131 | dependency: transitive 132 | description: 133 | name: matcher 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "0.12.12" 137 | meta: 138 | dependency: transitive 139 | description: 140 | name: meta 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "1.9.0" 144 | mime: 145 | dependency: transitive 146 | description: 147 | name: mime 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "1.0.3" 151 | node_preamble: 152 | dependency: transitive 153 | description: 154 | name: node_preamble 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "2.0.1" 158 | package_config: 159 | dependency: transitive 160 | description: 161 | name: package_config 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "2.1.0" 165 | path: 166 | dependency: transitive 167 | description: 168 | name: path 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "1.8.3" 172 | pool: 173 | dependency: transitive 174 | description: 175 | name: pool 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "1.5.1" 179 | pub_semver: 180 | dependency: transitive 181 | description: 182 | name: pub_semver 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "2.1.2" 186 | shelf: 187 | dependency: transitive 188 | description: 189 | name: shelf 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "1.3.2" 193 | shelf_packages_handler: 194 | dependency: transitive 195 | description: 196 | name: shelf_packages_handler 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "3.0.1" 200 | shelf_static: 201 | dependency: transitive 202 | description: 203 | name: shelf_static 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "1.1.1" 207 | shelf_web_socket: 208 | dependency: transitive 209 | description: 210 | name: shelf_web_socket 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "1.0.2" 214 | source_map_stack_trace: 215 | dependency: transitive 216 | description: 217 | name: source_map_stack_trace 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "2.1.1" 221 | source_maps: 222 | dependency: transitive 223 | description: 224 | name: source_maps 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "0.10.10" 228 | source_span: 229 | dependency: transitive 230 | description: 231 | name: source_span 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "1.9.1" 235 | stack_trace: 236 | dependency: transitive 237 | description: 238 | name: stack_trace 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "1.10.0" 242 | stream_channel: 243 | dependency: transitive 244 | description: 245 | name: stream_channel 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "2.1.1" 249 | string_scanner: 250 | dependency: transitive 251 | description: 252 | name: string_scanner 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "1.1.1" 256 | term_glyph: 257 | dependency: transitive 258 | description: 259 | name: term_glyph 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "1.2.1" 263 | test: 264 | dependency: "direct dev" 265 | description: 266 | name: test 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "1.21.4" 270 | test_api: 271 | dependency: transitive 272 | description: 273 | name: test_api 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "0.4.12" 277 | test_core: 278 | dependency: transitive 279 | description: 280 | name: test_core 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "0.4.16" 284 | typed_data: 285 | dependency: transitive 286 | description: 287 | name: typed_data 288 | url: "https://pub.dartlang.org" 289 | source: hosted 290 | version: "1.3.1" 291 | vm_service: 292 | dependency: transitive 293 | description: 294 | name: vm_service 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "9.4.0" 298 | watcher: 299 | dependency: transitive 300 | description: 301 | name: watcher 302 | url: "https://pub.dartlang.org" 303 | source: hosted 304 | version: "1.0.2" 305 | web_socket_channel: 306 | dependency: transitive 307 | description: 308 | name: web_socket_channel 309 | url: "https://pub.dartlang.org" 310 | source: hosted 311 | version: "2.3.0" 312 | webkit_inspection_protocol: 313 | dependency: transitive 314 | description: 315 | name: webkit_inspection_protocol 316 | url: "https://pub.dartlang.org" 317 | source: hosted 318 | version: "1.2.0" 319 | yaml: 320 | dependency: transitive 321 | description: 322 | name: yaml 323 | url: "https://pub.dartlang.org" 324 | source: hosted 325 | version: "3.1.1" 326 | sdks: 327 | dart: ">=2.16.2 <3.0.0" 328 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_design_patterns 2 | description: A sample command-line application. 3 | 4 | version: 1.0.0 5 | 6 | environment: 7 | sdk: ">=2.16.2 <3.0.0" 8 | 9 | dependencies: 10 | collection: ^1.17.0 11 | 12 | dev_dependencies: 13 | lints: ^1.0.0 14 | test: ^1.16.0 15 | -------------------------------------------------------------------------------- /test/adapter/adapter_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:dart_design_patterns/adapter/adapter.dart'; 4 | 5 | void main() { 6 | group('Adapter pattern test =>', () { 7 | test( 8 | 'Client code calls the Target interface and Adaptee\'s specificRequest method is invoked.', 9 | () { 10 | final Adaptee adaptee = Adaptee(); 11 | final Adapter adapter = Adapter(adaptee); 12 | 13 | expect( 14 | () => clientCode(adapter), 15 | prints("Adaptee's specificRequest called.\n"), 16 | ); 17 | }); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /test/decorator/decorator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:dart_design_patterns/decorator/decorator.dart'; 4 | 5 | void main() { 6 | group('Decorator pattern test =>', () { 7 | test('should add behavior to ConcreteComponent through ConcreteDecoratorA.', 8 | () { 9 | final ConcreteComponent component = ConcreteComponent(); 10 | final ConcreteDecoratorA decoratorA = ConcreteDecoratorA(component); 11 | 12 | expectLater( 13 | decoratorA.operation, 14 | prints('ConcreteComponent operation\nConcreteDecoratorA operation\n'), 15 | ); 16 | }); 17 | 18 | test('should add behavior to ConcreteComponent through ConcreteDecoratorB.', 19 | () { 20 | final ConcreteComponent component = ConcreteComponent(); 21 | final ConcreteDecoratorB decoratorB = ConcreteDecoratorB(component); 22 | 23 | expectLater( 24 | decoratorB.operation, 25 | prints('ConcreteComponent operation\nConcreteDecoratorB operation\n'), 26 | ); 27 | }); 28 | 29 | test('should chain multiple decorators.', () { 30 | final ConcreteComponent component = ConcreteComponent(); 31 | final ConcreteDecoratorA decoratorA = ConcreteDecoratorA(component); 32 | final ConcreteDecoratorB decoratorB = ConcreteDecoratorB(decoratorA); 33 | 34 | expectLater( 35 | decoratorB.operation, 36 | prints( 37 | 'ConcreteComponent operation\nConcreteDecoratorA operation\nConcreteDecoratorB operation\n', 38 | ), 39 | ); 40 | }); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /test/factory_method/factory_method_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:dart_design_patterns/factory_method/factory_method.dart'; 4 | 5 | void main() { 6 | group('Factory Method pattern test =>', () { 7 | test('Test creating and using ConcreteCreatorA.', () { 8 | final ConcreteCreatorA creator = ConcreteCreatorA(); 9 | final Product product = creator.createProduct(); 10 | 11 | product.doSomething('test data'); 12 | 13 | expect(product is ConcreteProductA, true); 14 | }); 15 | 16 | test('Test creating and using ConcreteCreatorB.', () { 17 | final ConcreteCreatorB creator = ConcreteCreatorB(); 18 | final Product product = creator.createProduct(); 19 | 20 | product.doSomething(123); 21 | 22 | expect(product is ConcreteProductB, true); 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /test/mediator/mediator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:dart_design_patterns/mediator/mediator.dart'; 4 | 5 | void main() { 6 | group('Mediator pattern test =>', () { 7 | test( 8 | 'concrete colleague should receives message from concrete mediator.', 9 | () { 10 | final ConcreteMediator mediator = ConcreteMediator(); 11 | 12 | final ConcreteColleague colleague1 = 13 | ConcreteColleague(mediator, 'Colleague 1'); 14 | final ConcreteColleague colleague2 = 15 | ConcreteColleague(mediator, 'Colleague 2'); 16 | 17 | mediator.register(colleague1); 18 | mediator.register(colleague2); 19 | 20 | expect(colleague1.name, equals('Colleague 1')); 21 | expect(colleague2.name, equals('Colleague 2')); 22 | 23 | colleague1.send('Hello, Colleague 2!'); 24 | 25 | expect( 26 | () => colleague2.receive('Hello, Colleague 2!'), returnsNormally); 27 | }, 28 | ); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /test/observer/observer_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:dart_design_patterns/observer/observer.dart'; 4 | 5 | void main() { 6 | group('Observer pattern test =>', () { 7 | late Subject subject; 8 | 9 | setUp(() => subject = Subject()); 10 | 11 | test('addObserver should add an observer.', () { 12 | final _MockObserver observer = _MockObserver(); 13 | 14 | subject.addObserver(observer); 15 | 16 | expect(subject.observersCount, equals(1)); 17 | }); 18 | 19 | test('removeObserver should remove an observer.', () { 20 | final _MockObserver observer = _MockObserver(); 21 | 22 | subject.addObserver(observer); 23 | 24 | subject.removeObserver(observer); 25 | 26 | expect(subject.observersCount, equals(0)); 27 | }); 28 | 29 | test('notifyObservers should call update on all observers.', () { 30 | const int message = 42; 31 | 32 | final _MockObserver observer1 = _MockObserver(); 33 | final _MockObserver observer2 = _MockObserver(); 34 | 35 | subject.addObserver(observer1); 36 | subject.addObserver(observer2); 37 | 38 | subject.notifyObservers(message); 39 | 40 | expect(observer1.updateCalledWith, equals(message)); 41 | expect(observer2.updateCalledWith, equals(message)); 42 | }); 43 | }); 44 | } 45 | 46 | class _MockObserver implements BaseObserver { 47 | int updateCalledWith = 0; 48 | 49 | @override 50 | void update(int message) => updateCalledWith = message; 51 | } 52 | -------------------------------------------------------------------------------- /test/proxy/proxy_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:dart_design_patterns/proxy/proxy.dart'; 4 | 5 | void main() { 6 | group('Proxy pattern test =>', () { 7 | test('request() method should forward message to RealSubject.', () { 8 | const String message = 'Hello, world!'; 9 | 10 | final Proxy proxy = Proxy(); 11 | 12 | expect( 13 | () => proxy.request(message), 14 | prints('RealSubject: Handling request with message $message.\n'), 15 | ); 16 | }); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /test/repository/repository_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:dart_design_patterns/repository/repository.dart'; 4 | 5 | void main() { 6 | group('Repository pattern test =>', () { 7 | late final BaseRepository userRepository; 8 | 9 | setUpAll(() => userRepository = MockUserRepository()); 10 | 11 | test('should insert a user and get it by id.', () async { 12 | final UserModel user = 13 | UserModel(id: 1, name: 'John', email: 'john@example.com'); 14 | 15 | await userRepository.insert(user); 16 | 17 | final UserModel? retrievedUser = await userRepository.getById(user.id); 18 | 19 | expect(retrievedUser, equals(user)); 20 | }); 21 | 22 | test('should update a user.', () async { 23 | final UserModel user = 24 | UserModel(id: 1, name: 'John', email: 'john@example.com'); 25 | 26 | await userRepository.insert(user); 27 | 28 | final UserModel updatedUser = UserModel( 29 | id: 1, 30 | name: 'Updated John', 31 | email: 'updated_john@example.com', 32 | ); 33 | 34 | await userRepository.update(updatedUser); 35 | 36 | final UserModel? retrievedUser = await userRepository.getById(user.id); 37 | 38 | expect(retrievedUser, equals(updatedUser)); 39 | }); 40 | 41 | test('should delete a user.', () async { 42 | final UserModel user = 43 | UserModel(id: 1, name: 'John', email: 'john@example.com'); 44 | 45 | await userRepository.insert(user); 46 | await userRepository.delete(user.id); 47 | 48 | final UserModel? users = await userRepository.getById(user.id); 49 | 50 | expect(users, isNull); 51 | }); 52 | 53 | test('should get all users.', () async { 54 | final List users = [ 55 | UserModel(id: 1, name: 'John', email: 'john@example.com'), 56 | UserModel(id: 2, name: 'Jane', email: 'jane@example.com'), 57 | UserModel(id: 3, name: 'Bob', email: 'bob@example.com'), 58 | ]; 59 | 60 | for (final UserModel user in users) { 61 | await userRepository.insert(user); 62 | } 63 | 64 | final List retrievedUsers = await userRepository.getAll(); 65 | 66 | expect(retrievedUsers, equals(users)); 67 | }); 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /test/singleton/singleton_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:dart_design_patterns/singleton/singleton.dart'; 4 | 5 | void main() { 6 | group('Singleton pattern test =>', () { 7 | test('should return the same instance.', () { 8 | final Singleton s1 = Singleton(); 9 | final Singleton s2 = Singleton(); 10 | 11 | expect(s1, same(s2)); 12 | }); 13 | }); 14 | 15 | group('Lazy Singleton pattern test =>', () { 16 | test('should return the same instance.', () { 17 | final LazySingleton s1 = LazySingleton(); 18 | final LazySingleton s2 = LazySingleton(); 19 | 20 | expect(s1, same(s2)); 21 | }); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /test/strategy/strategy_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:dart_design_patterns/strategy/strategy.dart'; 4 | 5 | void main() { 6 | group('strategy pattern test =>', () { 7 | test('should execute concrete strategy a.', () { 8 | final Context context = Context(ConcreteStrategyA()); 9 | 10 | expect(() => context.executeStrategy(), 11 | prints('Executing Concrete Strategy A\n')); 12 | }); 13 | 14 | test('should execute concrete strategy b.', () { 15 | final Context context = Context(ConcreteStrategyB()); 16 | 17 | expect(() => context.executeStrategy(), 18 | prints('Executing Concrete Strategy B\n')); 19 | }); 20 | 21 | test('should change strategy at runtime.', () { 22 | final Context context = Context(ConcreteStrategyA()); 23 | 24 | expect( 25 | () => context.executeStrategy(), 26 | prints('Executing Concrete Strategy A\n'), 27 | ); 28 | 29 | context.setStrategy(ConcreteStrategyB()); 30 | 31 | expect( 32 | () => context.executeStrategy(), 33 | prints('Executing Concrete Strategy B\n'), 34 | ); 35 | }); 36 | }); 37 | } 38 | --------------------------------------------------------------------------------