├── .gitignore ├── bin ├── creational │ ├── singleton │ │ ├── singleton.dart │ │ └── README.md │ ├── prototype │ │ ├── prototype.dart │ │ └── README.md │ ├── builder │ │ ├── builder.dart │ │ └── README.md │ ├── factory_method │ │ ├── factory_method.dart │ │ └── README.md │ └── abstract_factory │ │ ├── abstract_factory.dart │ │ └── README.md └── structural │ ├── adapter │ ├── adapter.dart │ └── README.md │ ├── bridge │ ├── bridge.dart │ └── README.md │ ├── decorator │ ├── decorator.dart │ └── README.md │ └── composite │ ├── README.md │ └── composite.dart ├── LICENSE ├── README.md └── CODE_OF_CONDUCT.md /.gitignore: -------------------------------------------------------------------------------- 1 | */pubspec.lock -------------------------------------------------------------------------------- /bin/creational/singleton/singleton.dart: -------------------------------------------------------------------------------- 1 | // Singleton Class 2 | class Singleton { 3 | // Private static instance 4 | static Singleton? _instance; 5 | 6 | // Private constructor 7 | Singleton._(); 8 | 9 | // Public static method to access the singleton instance 10 | static Singleton getInstance() { 11 | _instance ??= Singleton._(); 12 | return _instance!; 13 | } 14 | 15 | // Example method in the Singleton class 16 | void doSomething() { 17 | print('Singleton instance is doing something!'); 18 | } 19 | } 20 | 21 | void main() { 22 | // Get the singleton instance and call its methods 23 | Singleton instance1 = Singleton.getInstance(); 24 | instance1.doSomething(); 25 | 26 | // Get another instance (which should be the same) 27 | Singleton instance2 = Singleton.getInstance(); 28 | instance2.doSomething(); 29 | 30 | // Check if both instances are the same 31 | print('Both instances are the same: ${instance1 == instance2}'); 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ilia Khuzhakhmetov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bin/structural/adapter/adapter.dart: -------------------------------------------------------------------------------- 1 | class BookStoreBook { 2 | String title; 3 | String author; 4 | String ISBN; 5 | 6 | BookStoreBook(this.title, this.author, this.ISBN); 7 | 8 | String getDetails() { 9 | return 'Title: $title, Author: $author, ISBN: $ISBN'; 10 | } 11 | } 12 | 13 | class LibraryBook { 14 | String title; 15 | String author; 16 | String ISBN; 17 | int shelfNumber; 18 | 19 | LibraryBook(this.title, this.author, this.ISBN, this.shelfNumber); 20 | 21 | String getBookDetails() { 22 | return 'Title: $title, Author: $author, ISBN: $ISBN, Shelf Number: $shelfNumber'; 23 | } 24 | } 25 | 26 | class BookStoreToLibraryAdapter { 27 | LibraryBook adapt(BookStoreBook bookStoreBook, int shelfNumber) { 28 | return LibraryBook(bookStoreBook.title, bookStoreBook.author, 29 | bookStoreBook.ISBN, shelfNumber); 30 | } 31 | } 32 | 33 | void main() { 34 | BookStoreBook bookStoreBook = 35 | BookStoreBook('Dart Programming', 'John Doe', '123-456-789'); 36 | print(bookStoreBook.getDetails()); 37 | 38 | BookStoreToLibraryAdapter adapter = BookStoreToLibraryAdapter(); 39 | LibraryBook libraryBook = adapter.adapt(bookStoreBook, 10); 40 | print(libraryBook.getBookDetails()); 41 | } 42 | -------------------------------------------------------------------------------- /bin/creational/prototype/prototype.dart: -------------------------------------------------------------------------------- 1 | abstract class Shape { 2 | Shape clone(); 3 | void display(); 4 | } 5 | 6 | class Circle implements Shape { 7 | String color; 8 | 9 | Circle({required this.color}); 10 | 11 | @override 12 | Circle clone() { 13 | return Circle(color: color); 14 | } 15 | 16 | @override 17 | void display() { 18 | print('Circle with color: $color'); 19 | } 20 | } 21 | 22 | class Square implements Shape { 23 | String texture; 24 | 25 | Square({required this.texture}); 26 | 27 | @override 28 | Square clone() { 29 | return Square(texture: texture); 30 | } 31 | 32 | @override 33 | void display() { 34 | print('Square with texture: $texture'); 35 | } 36 | } 37 | 38 | void main() { 39 | // Create original objects 40 | Circle circle = Circle(color: 'red'); 41 | Square square = Square(texture: 'brick'); 42 | 43 | // Display original objects 44 | circle.display(); // Output: Circle with color: red 45 | square.display(); // Output: Square with texture: brick 46 | 47 | // Clone and modify objects 48 | Circle circleClone = circle.clone(); 49 | circleClone.color = 'blue'; 50 | 51 | Square squareClone = square.clone(); 52 | squareClone.texture = 'wood'; 53 | 54 | // Display cloned objects 55 | circleClone.display(); // Output: Circle with color: blue 56 | squareClone.display(); // Output: Square with texture: wood 57 | } 58 | -------------------------------------------------------------------------------- /bin/creational/builder/builder.dart: -------------------------------------------------------------------------------- 1 | class Pizza { 2 | String? dough; 3 | String? sauce; 4 | String? topping; 5 | 6 | String getDescription() { 7 | return 'Pizza with $dough dough, $sauce sauce, and $topping topping.'; 8 | } 9 | } 10 | 11 | abstract class PizzaBuilder { 12 | void setDough(String dough); 13 | void setSauce(String sauce); 14 | void setTopping(String topping); 15 | } 16 | 17 | class ItalianPizzaBuilder implements PizzaBuilder { 18 | final Pizza _pizza; 19 | 20 | ItalianPizzaBuilder() : _pizza = Pizza(); 21 | 22 | @override 23 | void setDough(String dough) { 24 | _pizza.dough = dough; 25 | } 26 | 27 | @override 28 | void setSauce(String sauce) { 29 | _pizza.sauce = sauce; 30 | } 31 | 32 | @override 33 | void setTopping(String topping) { 34 | _pizza.topping = topping; 35 | } 36 | 37 | Pizza build() { 38 | return _pizza; 39 | } 40 | } 41 | 42 | class PizzaDirector { 43 | void makeItalianPizza(PizzaBuilder builder) { 44 | builder.setDough('thin crust'); 45 | builder.setSauce('marinara'); 46 | builder.setTopping('mozzarella and basil'); 47 | } 48 | } 49 | 50 | void main() { 51 | PizzaDirector director = PizzaDirector(); 52 | ItalianPizzaBuilder builder = ItalianPizzaBuilder(); 53 | 54 | director.makeItalianPizza(builder); 55 | Pizza pizza = builder.build(); 56 | 57 | print(pizza.getDescription()); 58 | // Output: Pizza with thin crust dough, marinara sauce, and mozzarella and basil topping. 59 | } 60 | -------------------------------------------------------------------------------- /bin/creational/factory_method/factory_method.dart: -------------------------------------------------------------------------------- 1 | // Defining the product interface 2 | abstract class Product { 3 | String getDescription(); 4 | } 5 | 6 | // Concrete product classes 7 | class ConcreteProductA implements Product { 8 | @override 9 | String getDescription() { 10 | return 'This is Concrete Product A'; 11 | } 12 | } 13 | 14 | class ConcreteProductB implements Product { 15 | @override 16 | String getDescription() { 17 | return 'This is Concrete Product B'; 18 | } 19 | } 20 | 21 | // Enum for product types 22 | enum ProductType { A, B } 23 | 24 | // Creator class 25 | abstract class Creator { 26 | // The factory method 27 | Product createProduct(ProductType type); 28 | } 29 | 30 | // Concrete creator class 31 | class ConcreteCreator extends Creator { 32 | @override 33 | Product createProduct(ProductType type) { 34 | Product product; 35 | 36 | switch (type) { 37 | case ProductType.A: 38 | product = ConcreteProductA(); 39 | break; 40 | case ProductType.B: 41 | product = ConcreteProductB(); 42 | break; 43 | } 44 | 45 | return product; 46 | } 47 | } 48 | 49 | void main() { 50 | Creator creator = ConcreteCreator(); 51 | 52 | Product productA = creator.createProduct(ProductType.A); 53 | print(productA.getDescription()); // Output: This is Concrete Product A 54 | 55 | Product productB = creator.createProduct(ProductType.B); 56 | print(productB.getDescription()); // Output: This is Concrete Product B 57 | } 58 | -------------------------------------------------------------------------------- /bin/structural/bridge/bridge.dart: -------------------------------------------------------------------------------- 1 | // Message abstraction 2 | abstract class Message { 3 | final MessageSender sender; 4 | 5 | Message(this.sender); 6 | 7 | void send(String content); 8 | } 9 | 10 | // Concrete abstraction for SMS message 11 | class SmsMessage extends Message { 12 | SmsMessage(MessageSender sender) : super(sender); 13 | 14 | @override 15 | void send(String content) { 16 | sender.sendMessage('SMS: $content'); 17 | } 18 | } 19 | 20 | // Concrete abstraction for Email message 21 | class EmailMessage extends Message { 22 | EmailMessage(MessageSender sender) : super(sender); 23 | 24 | @override 25 | void send(String content) { 26 | sender.sendMessage('Email: $content'); 27 | } 28 | } 29 | 30 | // Interface for message sender 31 | abstract interface class MessageSender { 32 | void sendMessage(String content); 33 | } 34 | 35 | // Concrete sender through HTTP API 36 | class HttpApiSender implements MessageSender { 37 | @override 38 | void sendMessage(String content) { 39 | print('Sending via HTTP API: $content'); 40 | } 41 | } 42 | 43 | // Concrete sender through SMTP 44 | class SmtpSender implements MessageSender { 45 | @override 46 | void sendMessage(String content) { 47 | print('Sending via SMTP: $content'); 48 | } 49 | } 50 | 51 | void main() { 52 | MessageSender httpApiSender = HttpApiSender(); 53 | MessageSender smtpSender = SmtpSender(); 54 | 55 | Message smsMessage = SmsMessage(httpApiSender); 56 | smsMessage.send('Hello via HTTP API'); 57 | 58 | Message emailMessage = EmailMessage(smtpSender); 59 | emailMessage.send('Hello via SMTP'); 60 | } 61 | -------------------------------------------------------------------------------- /bin/structural/decorator/decorator.dart: -------------------------------------------------------------------------------- 1 | // Base Interface for the Logger 2 | abstract interface class Logger { 3 | void log(String message); 4 | } 5 | 6 | // Basic Implementation 7 | class BasicLogger implements Logger { 8 | @override 9 | void log(String message) { 10 | print(message); 11 | } 12 | } 13 | 14 | // Base class for decorators 15 | abstract class LoggerDecorator implements Logger { 16 | final Logger _wrappedLogger; 17 | 18 | const LoggerDecorator(this._wrappedLogger); 19 | 20 | @override 21 | void log(String message) { 22 | _wrappedLogger.log(message); 23 | } 24 | } 25 | 26 | // Decorator for adding timestamps 27 | class TimestampDecorator extends LoggerDecorator { 28 | TimestampDecorator(Logger logger) : super(logger); 29 | 30 | @override 31 | void log(String message) { 32 | final timestamp = DateTime.now().toIso8601String(); 33 | super.log('$timestamp: $message'); 34 | } 35 | } 36 | 37 | // Decorator for adding log levels 38 | class LevelDecorator extends LoggerDecorator { 39 | final String level; 40 | 41 | LevelDecorator(Logger logger, this.level) : super(logger); 42 | 43 | @override 44 | void log(String message) { 45 | super.log('[$level] $message'); 46 | } 47 | } 48 | 49 | // Decorator for writing to a file 50 | class FileWriteDecorator extends LoggerDecorator { 51 | final String filePath; 52 | 53 | FileWriteDecorator(Logger logger, this.filePath) : super(logger); 54 | 55 | @override 56 | void log(String message) { 57 | super.log(message); 58 | // In a real implementation, we would write to a file here 59 | print('Also writing "$message" to $filePath'); 60 | } 61 | } 62 | 63 | void main() { 64 | // Create a basic logger 65 | final basicLogger = BasicLogger(); 66 | 67 | // Create a decorated logger with multiple decorators 68 | final decoratedLogger = FileWriteDecorator( 69 | LevelDecorator( 70 | TimestampDecorator(basicLogger), 71 | 'INFO' 72 | ), 73 | 'log.txt' 74 | ); 75 | 76 | // Use the decorated logger 77 | decoratedLogger.log('This is a test message'); 78 | 79 | // Use it again to show caching won't apply here 80 | decoratedLogger.log('This is another test message'); 81 | } -------------------------------------------------------------------------------- /bin/creational/abstract_factory/abstract_factory.dart: -------------------------------------------------------------------------------- 1 | // Abstract Products 2 | abstract class Chair { 3 | String getDescription(); 4 | } 5 | 6 | abstract class Table { 7 | String getMaterial(); 8 | } 9 | 10 | // Concrete Products 11 | class ModernChair implements Chair { 12 | @override 13 | String getDescription() { 14 | return 'Modern chair'; 15 | } 16 | } 17 | 18 | class ClassicChair implements Chair { 19 | @override 20 | String getDescription() { 21 | return 'Classic chair'; 22 | } 23 | } 24 | 25 | class ModernTable implements Table { 26 | @override 27 | String getMaterial() { 28 | return 'Glass'; 29 | } 30 | } 31 | 32 | class ClassicTable implements Table { 33 | @override 34 | String getMaterial() { 35 | return 'Wood'; 36 | } 37 | } 38 | 39 | // Abstract Factory 40 | abstract class FurnitureFactory { 41 | Chair createChair(); 42 | Table createTable(); 43 | } 44 | 45 | // Concrete Factories 46 | class ModernFurnitureFactory implements FurnitureFactory { 47 | @override 48 | Chair createChair() { 49 | return ModernChair(); 50 | } 51 | 52 | @override 53 | Table createTable() { 54 | return ModernTable(); 55 | } 56 | } 57 | 58 | class ClassicFurnitureFactory implements FurnitureFactory { 59 | @override 60 | Chair createChair() { 61 | return ClassicChair(); 62 | } 63 | 64 | @override 65 | Table createTable() { 66 | return ClassicTable(); 67 | } 68 | } 69 | 70 | void main() { 71 | // Create a Modern Furniture Factory 72 | FurnitureFactory modernFurnitureFactory = ModernFurnitureFactory(); 73 | Chair modernChair = modernFurnitureFactory.createChair(); 74 | Table modernTable = modernFurnitureFactory.createTable(); 75 | print(modernChair.getDescription()); // Output: Modern chair 76 | print(modernTable.getMaterial()); // Output: Glass 77 | 78 | // Create a Classic Furniture Factory 79 | FurnitureFactory classicFurnitureFactory = ClassicFurnitureFactory(); 80 | Chair classicChair = classicFurnitureFactory.createChair(); 81 | Table classicTable = classicFurnitureFactory.createTable(); 82 | print(classicChair.getDescription()); // Output: Classic chair 83 | print(classicTable.getMaterial()); // Output: Wood 84 | } 85 | -------------------------------------------------------------------------------- /bin/structural/composite/README.md: -------------------------------------------------------------------------------- 1 | # Composite Pattern: Task Management System 2 | 3 | Hey there, fellow Dart enthusiasts! 👋 Today, we're diving into the wonderful world of the Composite pattern. Buckle up, because we're about to see how this pattern can make our lives easier when dealing with hierarchical structures - in this case, a task management system. 4 | 5 | ## What's This All About? 6 | 7 | Imagine you're building the next big thing in project management apps. You need a way to handle tasks, subtasks, and groups of tasks without losing your mind. Enter the Composite pattern - your new best friend in dealing with tree-like structures. 8 | 9 | ## The Code: Where the Magic Happens 10 | 11 | Let's break down the key players in our `composite.dart`: 12 | 13 | ### TaskComponent 14 | 15 | This abstract class is the backbone of our structure. It defines the common interface for both individual tasks and task groups. Think of it as the blueprint for everything task-related. 16 | 17 | ### Task 18 | 19 | The humble `Task` class represents a single, indivisible unit of work. It's the leaf in our tree structure - no children, just pure, unadulterated task goodness. 20 | 21 | ### TaskGroup 22 | 23 | Now we're talking! `TaskGroup` is where things get interesting. It can contain other `TaskComponent`s, be they individual tasks or other groups. It's like a Russian nesting doll, but for tasks. 24 | 25 | ### The Main Event 26 | 27 | In our `main()` function, we put all these pieces together to create a complex project structure. It's like playing with LEGO, but instead of bricks, we're using tasks and groups. 28 | 29 | ## Why Should You Care? 30 | 31 | 1. **Flexibility**: Need to add a new level of hierarchy? No problem! Just create another `TaskGroup`. 32 | 2. **Simplicity**: Treat individual tasks and groups of tasks uniformly. Your code stays clean, and you stay sane. 33 | 3. **Real-world applicability**: This isn't just theoretical mumbo-jumbo. You can use this in real Flutter apps for project management, to-do lists, or anywhere you need to manage hierarchical data. 34 | 35 | ## Further Reading 36 | 37 | - [Refactoring Guru: Composite](https://refactoring.guru/design-patterns/composite) 38 | - [Wikipedia: Composite pattern](https://en.wikipedia.org/wiki/Composite_pattern) 39 | -------------------------------------------------------------------------------- /bin/structural/adapter/README.md: -------------------------------------------------------------------------------- 1 | # Adapter Pattern 2 | 3 | The Adapter pattern is a structural design pattern that allows objects with incompatible interfaces to work together. This pattern involves a single class, the adapter, which is responsible for communication between the two different interfaces. 4 | 5 | ## adapter.dart 6 | ``` 7 | class BookStoreBook { 8 | String title; 9 | String author; 10 | String ISBN; 11 | 12 | BookStoreBook(this.title, this.author, this.ISBN); 13 | 14 | String getDetails() { 15 | return 'Title: $title, Author: $author, ISBN: $ISBN'; 16 | } 17 | } 18 | 19 | class LibraryBook { 20 | String title; 21 | String author; 22 | String ISBN; 23 | int shelfNumber; 24 | 25 | LibraryBook(this.title, this.author, this.ISBN, this.shelfNumber); 26 | 27 | String getBookDetails() { 28 | return 'Title: $title, Author: $author, ISBN: $ISBN, Shelf Number: $shelfNumber'; 29 | } 30 | } 31 | 32 | class BookStoreToLibraryAdapter { 33 | LibraryBook adapt(BookStoreBook bookStoreBook, int shelfNumber) { 34 | return LibraryBook(bookStoreBook.title, bookStoreBook.author, bookStoreBook.ISBN, shelfNumber); 35 | } 36 | } 37 | 38 | void main() { 39 | BookStoreBook bookStoreBook = BookStoreBook('Dart Programming', 'John Doe', '123-456-789'); 40 | print(bookStoreBook.getDetails()); 41 | 42 | BookStoreToLibraryAdapter adapter = BookStoreToLibraryAdapter(); 43 | LibraryBook libraryBook = adapter.adapt(bookStoreBook, 10); 44 | print(libraryBook.getBookDetails()); 45 | } 46 | ``` 47 | 48 | ## Key Concepts 49 | - **Target**: This is the interface that the **Client** uses. 50 | - **Client**: This is the class that interacts with a service it can't use directly through the Target. 51 | - **Adaptee**: This is the interface that needs adapting for the **Client** to use. 52 | - **Adapter**: This is the class that makes the **Adaptee**'s interface compatible with the Target's interface. 53 | 54 | ## Pros and Cons 55 | 56 | ### Pros 57 | 58 | - Single Responsibility Principle: You can separate the interface or data conversion code from the primary business logic of the program. 59 | - Open/Closed Principle: You can introduce new types of adapters into the program without breaking the existing client code. 60 | 61 | ### Cons 62 | - The overall complexity of the code increases because you need to introduce a set of new interfaces and classes. 63 | 64 | # Read more 65 | - [Refactoring Guru: Adapter](https://refactoring.guru/design-patterns/adapter) 66 | - [Wikipedia: Adapter pattern](https://en.wikipedia.org/wiki/Adapter_pattern) -------------------------------------------------------------------------------- /bin/creational/singleton/README.md: -------------------------------------------------------------------------------- 1 | # Singleton Pattern 2 | 3 | The Singleton pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance. This pattern is useful when you want to have a single instance of a class that is accessible from any part of your application. 4 | 5 | ## singleton.dart 6 | ``` 7 | // Singleton Class 8 | class Singleton { 9 | // Private static instance 10 | static Singleton? _instance; 11 | 12 | // Private constructor 13 | Singleton._(); 14 | 15 | // Public static method to access the singleton instance 16 | static Singleton getInstance() { 17 | _instance ??= Singleton._(); 18 | return _instance!; 19 | } 20 | 21 | // Example method in the Singleton class 22 | void doSomething() { 23 | print('Singleton instance is doing something!'); 24 | } 25 | } 26 | 27 | void main() { 28 | // Get the singleton instance and call its methods 29 | Singleton instance1 = Singleton.getInstance(); 30 | instance1.doSomething(); 31 | 32 | // Get another instance (which should be the same) 33 | Singleton instance2 = Singleton.getInstance(); 34 | instance2.doSomething(); 35 | 36 | // Check if both instances are the same 37 | print('Both instances are the same: ${instance1 == instance2}'); 38 | } 39 | ``` 40 | 41 | ## Key Concepts 42 | - **Singleton**: The class that has only one instance and provides a global access point to that instance. 43 | - **Private Constructor**: A private constructor is used to prevent creating new instances of the Singleton class from outside the class. 44 | - **Static Instance**: A static instance of the Singleton class holds the unique instance of the class. 45 | - **Static Method**: A public static method is used to access the Singleton instance, creating it if it does not exist. 46 | 47 | ## Relationship with Other Patterns 48 | - The **Facade** pattern can be implemented as a Singleton since usually only one facade object is needed. 49 | - The **Flyweight** pattern may resemble Singleton if, for a specific task, you managed to reduce the number of objects to one. However, remember that there are two fundamental differences between the patterns: 50 | - Unlike Singleton, you can have multiple flyweight objects. 51 | - Flyweight objects must be immutable, whereas Singleton objects can have their state changed. 52 | - **Abstract Factory**, **Builder**, and **Prototype** can all be implemented using the Singleton pattern. 53 | 54 | ## Pros and Cons 55 | 56 | ### Pros 57 | - Ensures there is only one instance of a class. 58 | - Provides a global access point to the instance. 59 | - Implements lazy initialization for the Singleton object. 60 | 61 | ### Cons 62 | - Violates the Single Responsibility Principle. 63 | - Can mask poor design choices. 64 | - Multithreading issues may arise. 65 | - Requires constant creation of Mock objects during unit testing. 66 | 67 | # Read more 68 | - [Refactoring Guru: Singleton](https://refactoring.guru/design-patterns/singleton) 69 | - [Wikipedia: Singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) -------------------------------------------------------------------------------- /bin/structural/bridge/README.md: -------------------------------------------------------------------------------- 1 | # Bridge Pattern 2 | 3 | The Bridge pattern is a structural design pattern that decouples an abstraction from its implementation, allowing both to vary independently. This is especially useful when a class can be extended in multiple dimensions, enabling the creation of platform-independent classes and applications. 4 | 5 | ## bridge.dart 6 | ```dart 7 | // Abstract message class 8 | abstract class Message { 9 | final MessageSender sender; 10 | 11 | Message(this.sender); 12 | 13 | void send(String content); 14 | } 15 | 16 | // Concrete class for SMS message 17 | class SmsMessage extends Message { 18 | SmsMessage(MessageSender sender) : super(sender); 19 | 20 | @override 21 | void send(String content) { 22 | sender.sendMessage('SMS: $content'); 23 | } 24 | } 25 | 26 | // Concrete class for Email message 27 | class EmailMessage extends Message { 28 | EmailMessage(MessageSender sender) : super(sender); 29 | 30 | @override 31 | void send(String content) { 32 | sender.sendMessage('Email: $content'); 33 | } 34 | } 35 | 36 | // Interface for message sender 37 | abstract class MessageSender { 38 | void sendMessage(String content); 39 | } 40 | 41 | // Concrete sender class via HTTP API 42 | class HttpApiSender implements MessageSender { 43 | @override 44 | void sendMessage(String content) { 45 | print('Sending via HTTP API: $content'); 46 | } 47 | } 48 | 49 | // Concrete sender class via SMTP 50 | class SmtpSender implements MessageSender { 51 | @override 52 | void sendMessage(String content) { 53 | print('Sending via SMTP: $content'); 54 | } 55 | } 56 | 57 | void main() { 58 | MessageSender httpApiSender = HttpApiSender(); 59 | MessageSender smtpSender = SmtpSender(); 60 | 61 | Message smsMessage = SmsMessage(httpApiSender); 62 | smsMessage.send('Hello via HTTP API'); 63 | 64 | Message emailMessage = EmailMessage(smtpSender); 65 | emailMessage.send('Hello via SMTP'); 66 | } 67 | ``` 68 | 69 | ## Key Concepts 70 | 71 | - **Abstraction**: Outlines the interface for the control part of the two class hierarchies and maintains a reference to the implementer. 72 | - **Implementer**: Outlines the interface for implementation classes, providing basic operations used by the abstraction. 73 | - **Concrete Implementations**: Implement the interface defined by the implementer, detailing concrete operations. 74 | - **Refined Abstractions**: Extend the abstraction to add more functionality. 75 | 76 | ## Pros and Cons 77 | 78 | ### Pros 79 | 80 | - **Separation of Concerns**: By separating the abstraction from the implementation, each can be modified independently without affecting the other. 81 | - **Enhanced Flexibility**: Both the abstraction and the implementation can evolve separately. 82 | - **Easier Maintenance**: The structure simplifies code maintenance and minimizes the risk of errors when modifying the system. 83 | 84 | ### Cons 85 | 86 | - **Added Complexity**: Introducing the Bridge pattern can increase the complexity of the codebase due to the additional layers of abstraction. 87 | 88 | ## Further Reading 89 | 90 | - [Refactoring Guru: Bridge](https://refactoring.guru/design-patterns/bridge) 91 | - [Wikipedia: Bridge pattern](https://en.wikipedia.org/wiki/Bridge_pattern) 92 | -------------------------------------------------------------------------------- /bin/structural/composite/composite.dart: -------------------------------------------------------------------------------- 1 | abstract interface class TaskComponent { 2 | String get name; 3 | int get estimatedTime; 4 | void addSubtask(TaskComponent task); 5 | void removeSubtask(TaskComponent task); 6 | List getSubtasks(); 7 | void display([String indent = '']); 8 | } 9 | 10 | class Task implements TaskComponent { 11 | final String _name; 12 | 13 | final int _estimatedTime; 14 | 15 | const Task(String name, int estimatedTime) 16 | : _name = name, 17 | _estimatedTime = estimatedTime; 18 | 19 | @override 20 | String get name => _name; 21 | 22 | @override 23 | int get estimatedTime => _estimatedTime; 24 | 25 | @override 26 | void addSubtask(TaskComponent task) { 27 | throw UnsupportedError('Cannot add subtask to a Task'); 28 | } 29 | 30 | @override 31 | void removeSubtask(TaskComponent task) { 32 | throw UnsupportedError('Cannot remove subtask from a Task'); 33 | } 34 | 35 | @override 36 | List getSubtasks() => []; 37 | 38 | @override 39 | void display([String indent = '']) { 40 | print('$indent- $name (${estimatedTime}h)'); 41 | } 42 | } 43 | 44 | class TaskGroup implements TaskComponent { 45 | final String _name; 46 | final List _subtasks = []; 47 | 48 | TaskGroup(String name) : _name = name; 49 | 50 | @override 51 | String get name => _name; 52 | 53 | @override 54 | int get estimatedTime { 55 | return _subtasks.fold(0, (sum, task) => sum + task.estimatedTime); 56 | } 57 | 58 | @override 59 | void addSubtask(TaskComponent task) { 60 | _subtasks.add(task); 61 | } 62 | 63 | @override 64 | void removeSubtask(TaskComponent task) { 65 | _subtasks.remove(task); 66 | } 67 | 68 | @override 69 | List getSubtasks() => List.unmodifiable(_subtasks); 70 | 71 | @override 72 | void display([String indent = '']) { 73 | print('$indent+ $name (${estimatedTime} h)'); 74 | for (var subtask in _subtasks) { 75 | subtask.display('$indent '); 76 | } 77 | } 78 | } 79 | 80 | void main() { 81 | final project = TaskGroup('Mobile App Development'); 82 | 83 | final planning = TaskGroup('Planning Phase'); 84 | planning.addSubtask(Task('Market Research', 10)); 85 | planning.addSubtask(Task('Define MVP', 5)); 86 | 87 | final design = TaskGroup('Design Phase'); 88 | design.addSubtask(Task('Create Wireframes', 8)); 89 | design.addSubtask(Task('Design UI', 15)); 90 | 91 | final development = TaskGroup('Development Phase'); 92 | final frontend = TaskGroup('Frontend Development'); 93 | frontend.addSubtask(Task('Implement UI', 40)); 94 | frontend.addSubtask(Task('Integrate APIs', 20)); 95 | 96 | final backend = TaskGroup('Backend Development'); 97 | backend.addSubtask(Task('Design Database', 10)); 98 | backend.addSubtask(Task('Implement Server Logic', 30)); 99 | 100 | development.addSubtask(frontend); 101 | development.addSubtask(backend); 102 | 103 | final testing = TaskGroup('Testing Phase'); 104 | testing.addSubtask(Task('Unit Testing', 15)); 105 | testing.addSubtask(Task('Integration Testing', 10)); 106 | testing.addSubtask(Task('User Acceptance Testing', 5)); 107 | 108 | project.addSubtask(planning); 109 | project.addSubtask(design); 110 | project.addSubtask(development); 111 | project.addSubtask(testing); 112 | 113 | project.display(); 114 | 115 | print('\nTotal estimated time: ${project.estimatedTime} hours'); 116 | } 117 | -------------------------------------------------------------------------------- /bin/creational/factory_method/README.md: -------------------------------------------------------------------------------- 1 | # Factory Method Pattern in Dart 2 | 3 | The Factory Method pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. This pattern is especially useful when the creation process of an object is complex, or when the same creation process should be shared among multiple related classes. 4 | 5 | In this implementation, we have an abstract `Product` class representing the different types of products, and concrete `ConcreteProductA` and `ConcreteProductB` classes implementing the `Product` interface. We also have an abstract `Creator` class that defines a factory method `createProduct` for creating `Product` instances, and a concrete ConcreteCreator class that implements the `createProduct` method. 6 | 7 | This Dart implementation uses an enum `ProductType` to represent the different product types, making the code more type-safe and efficient. 8 | 9 | ## Usage 10 | 11 | ``` 12 | import 'factory_method.dart'; 13 | 14 | void main() { 15 | Creator creator = ConcreteCreator(); 16 | 17 | Product productA = creator.createProduct(ProductType.A); 18 | print(productA.getDescription()); // Output: This is Concrete Product A 19 | 20 | Product productB = creator.createProduct(ProductType.B); 21 | print(productB.getDescription()); // Output: This is Concrete Product B 22 | } 23 | ``` 24 | 25 | ## Key Concepts 26 | - `Product Interface`: This is the base interface or abstract class for all product types that the factory method will create. 27 | - `Concrete Products`: These classes implement the Product interface, representing specific types of products. 28 | - `Creator Class`: This is the base class that contains the factory method to create products. Subclasses of this class can override the factory method to create different types of products. 29 | - `Concrete Creator`: This class extends the Creator class and provides an implementation for the factory method, which creates and returns instances of the concrete products based on the input type. 30 | - `ProductType Enum`: The enum is used to represent the different product types in a more type-safe and efficient way. 31 | 32 | ## Relationship with Other Patterns 33 | - Many architectures start with the Factory Method (simpler and extensible through subclasses) and evolve towards Abstract Factory, Prototype, or Builder (more flexible, but also more complex). 34 | - Abstract Factory classes are often implemented using the Factory Method, although they can also be built based on the Prototype. 35 | - The Factory Method can be used in conjunction with the Iterator pattern to allow collection subclasses to create suitable iterators for themselves. 36 | - The Prototype pattern does not rely on inheritance but requires a complex initialization operation. On the contrary, the Factory Method is built on inheritance but does not require complex initialization. 37 | - The Factory Method can be considered a special case of the Template Method pattern. Additionally, the Factory Method is often part of a larger class with Template Methods. 38 | 39 | ## Pros and Cons 40 | 41 | ### Pros 42 | 43 | - The Factory Method pattern promotes loose coupling between the concrete products and the code that uses them. 44 | - It allows for easy addition or modification of product types, without affecting the client code. 45 | - The Factory Method pattern encourages the use of composition over inheritance, which can lead to more flexible and maintainable code. 46 | 47 | ### Cons 48 | - The Factory Method pattern can lead to a proliferation of classes, as each concrete product requires a corresponding concrete creator. 49 | -------------------------------------------------------------------------------- /bin/creational/builder/README.md: -------------------------------------------------------------------------------- 1 | # Builder Pattern in Dart 2 | 3 | The Builder pattern is a creational design pattern that separates the construction of a complex object from its representation. By doing so, the same construction process can be used to create different representations. This pattern is particularly useful when the object creation process involves many steps, and the object can have different configurations. 4 | 5 | In this example, we use the Builder pattern to create a `Pizza` object. The `Pizza` class represents the final product, and the `PizzaBuilder` abstract class defines the steps to create a `Pizza`. The `ItalianPizzaBuilder` class implements these steps for an Italian pizza. The `PizzaDirector` class is responsible for the construction process, using a specific `PizzaBuilder` to create a pizza. 6 | 7 | ## Usage 8 | 9 | ``` 10 | void main() { 11 | PizzaDirector director = PizzaDirector(); 12 | ItalianPizzaBuilder builder = ItalianPizzaBuilder(); 13 | 14 | director.makeItalianPizza(builder); 15 | Pizza pizza = builder.build(); 16 | 17 | print(pizza.getDescription()); 18 | // Output: Pizza with thin crust dough, marinara sauce, and mozzarella and basil topping. 19 | } 20 | ``` 21 | 22 | ## Key Concepts 23 | - *Product*: This is the final object that the Builder pattern constructs. In this example, it's the`Pizza` class. 24 | - *Builder* Interface: This is the common interface for all concrete builders. It defines the steps needed to create the final product. In this example, it's the `PizzaBuilder` abstract class. 25 | - *Concrete Builder*: This class implements the Builder interface, providing an implementation for each step of the object construction process. In this example, it's the `ItalianPizzaBuilder` class. 26 | - *Director*: This class is responsible for managing the construction process. It uses a specific Builder to create the final product. In this example, it's the `PizzaDirector` class. 27 | 28 | ## Relationship with Other Patterns 29 | 30 | - Many architectures begin with the implementation of the Factory Method pattern (simpler and extendable through subclasses) and gradually evolve toward Abstract Factory, Prototype, or Builder patterns (more flexible but also more complex). 31 | - Builder focuses on the step-by-step construction of complex objects, while Abstract Factory emphasizes the creation of families of related products. The Builder returns a product only after completing all steps, whereas Abstract Factory returns the product immediately. 32 | - The Builder pattern allows for the incremental construction of a Composite tree. 33 | - The Builder pattern can be implemented as a Bridge, with the director playing the role of an abstraction and the builders acting as implementations. 34 | - Abstract Factory, Builder, and Prototype patterns can all be realized using the Singleton design pattern. 35 | 36 | ## Pros and Cons 37 | 38 | ### Pros 39 | - The Builder pattern allows for constructing complex objects step by step, resulting in more maintainable and readable code. 40 | - It enables creating different representations of the same object using the same construction process. 41 | - It promotes separation of concerns by keeping the object construction code separate from the object representation code. 42 | 43 | ### Cons 44 | - The Builder pattern can introduce additional complexity to the code, as it requires creating multiple classes and interfaces. 45 | - Clients may be tied to specific builder classes, as the director's interface may not have a method for obtaining the result. 46 | 47 | # Read more 48 | If you'd like to learn more about design patterns in programming, you can check out the following [resource](https://refactoring.guru/design-patterns/builder) -------------------------------------------------------------------------------- /bin/creational/abstract_factory/README.md: -------------------------------------------------------------------------------- 1 | # Abstract Factory Pattern in Dart 2 | 3 | The Abstract Factory pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. This pattern is useful when you have multiple sets of related objects that you want to create and manage together. 4 | 5 | In this implementation, we have an abstract `FurnitureFactory` that creates two types of products: chairs and tables. There are two concrete furniture factories: `ModernFurnitureFactory` and `ClassicFurnitureFactory`. Each concrete factory creates its own set of products, such as `ModernChair`, `ClassicChair`, `ModernTable`, and `ClassicTable`. 6 | 7 | This Dart implementation demonstrates how to create instances of the concrete factories and use them to create different types of chairs and tables. 8 | 9 | ## Usage 10 | 11 | ``` 12 | import 'abstract_factory.dart'; 13 | 14 | void main() { 15 | // Create a Modern Furniture Factory 16 | FurnitureFactory modernFurnitureFactory = ModernFurnitureFactory(); 17 | Chair modernChair = modernFurnitureFactory.createChair(); 18 | Table modernTable = modernFurnitureFactory.createTable(); 19 | print(modernChair.getDescription()); // Output: Modern chair 20 | print(modernTable.getMaterial()); // Output: Glass 21 | 22 | // Create a Classic Furniture Factory 23 | FurnitureFactory classicFurnitureFactory = ClassicFurnitureFactory(); 24 | Chair classicChair = classicFurnitureFactory.createChair(); 25 | Table classicTable = classicFurnitureFactory.createTable(); 26 | print(classicChair.getDescription()); // Output: Classic chair 27 | print(classicTable.getMaterial()); // Output: Wood 28 | } 29 | ``` 30 | 31 | ## Key Concepts 32 | - `Abstract Products`: These are the base interfaces or abstract classes for all product types that the abstract factory will create. 33 | - `Concrete Products`: These classes implement the abstract product interfaces, representing specific types of products. 34 | - `Abstract Factory`: This is the base interface that contains the factory methods for creating products. Concrete factories implement this interface to create instances of the concrete products. 35 | - `Concrete Factories`: These classes implement the Abstract Factory interface, creating instances of the concrete products based on the factory method implementation. 36 | 37 | ## Relationship with Other Patterns 38 | 39 | - Numerous design approaches begin with the simpler and subclass-extensible Factory Method and progress toward more flexible and intricate patterns like Abstract Factory, Prototype, or Builder. 40 | - While the Builder pattern emphasizes the step-by-step construction of complex objects, the Abstract Factory is tailored to produce families of interconnected products. Builders deliver the final product after completing all necessary steps, whereas Abstract Factories provide products instantly. 41 | - Abstract Factory classes frequently use the Factory Method for implementation, but they may also be built upon the Prototype pattern. 42 | - The Abstract Factory can replace the Facade pattern as a way to conceal platform-specific classes. 43 | - Collaborating with the Bridge pattern, the Abstract Factory can be advantageous when working with abstractions that are compatible only with certain implementations. In such cases, the factory determines the types of both the created abstractions and the implementations. 44 | - Implementations of the Abstract Factory, Builder, and Prototype patterns can all leverage the Singleton pattern. 45 | 46 | ## Pros and Cons 47 | 48 | ### Pros 49 | 50 | - Ensures compatibility of created products. 51 | - Decouples client code from specific product classes. 52 | - Consolidates product creation code in one place, simplifying code maintenance. 53 | - Streamlines the process of adding new products to the program. 54 | - Implements the Open/Closed Principle. 55 | 56 | ### Cons 57 | 58 | - Complicates program code due to the introduction of numerous additional classes. 59 | - Requires the presence of all product types in each variation. 60 | 61 | # Read more 62 | If you'd like to learn more about design patterns in programming, you can check out the following [resource](https://refactoring.guru/design-patterns/abstract-factory) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design Patterns in Dart 2 | 3 | This repository contains implementations of various design patterns in the Dart programming language. These patterns can help you write clean, reusable, and maintainable code. Each pattern is implemented as a separate Dart project, along with a dedicated `README.md` file explaining the pattern, its key concepts, and usage. 4 | 5 | ## Table of Contents 6 | 7 | - [Creational Patterns](#creational-patterns) 8 | - [Factory Method](#factory-method) 9 | - [Abstract Factory](#abstract-factory) 10 | - [Builder](#builder) 11 | - [Prototype](#prototype) 12 | - [Singleton](#singleton) 13 | - [Structural Patterns](#structural-patterns) 14 | - [Adapter](#adapter) 15 | - [Bridge](#bridge) 16 | - [Composite](#composite) 17 | - [Decorator](#decorator) 18 | - [Behavioral Patterns](#behavioral-patterns) 19 | 20 | ## Creational Patterns 21 | 22 | Creational patterns deal with the process of object creation. 23 | 24 | ### Factory Method 25 | 26 | The Factory Method pattern provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. This pattern is useful when the creation process of an object is complex or when the same creation process should be shared among multiple related classes. 27 | 28 | - [Implementation](bin/creational/factory_method/factory_method.dart) 29 | - [README](bin/creational/factory_method/README.md) 30 | 31 | ### Absract Factory 32 | 33 | The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. This pattern is useful when you have multiple sets of related objects that you want to create and manage together. 34 | 35 | - [Implementation](bin/creational/abstract_factory/abstract_factory.dart) 36 | - [README](bin/creational/abstract_factory/README.md) 37 | 38 | ### Builder 39 | 40 | The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. This pattern is useful when you need to build complex objects with various configurations while maintaining the construction process clean and organized. 41 | 42 | - [Implementation](bin/creational/builder/builder.dart) 43 | - [README](bin/creational/builder/README.md) 44 | 45 | ### Prototype 46 | 47 | The Prototype pattern allows the creation of new objects by cloning existing ones, without depending on their concrete classes. This pattern is useful when object creation is expensive or complicated, and you want to reuse existing instances to save resources and time. 48 | 49 | - [Implementation](bin/creational/prototype/prototype.dart) 50 | - [README](bin/creational/prototype/README.md) 51 | 52 | ### Singleton 53 | 54 | The Singleton pattern ensures that a class has only one instance and provides a global access point to that instance. This pattern is beneficial when the creation of multiple instances is unnecessary or expensive, and you want to manage resources efficiently by reusing the single instance. 55 | 56 | - [Implementation](bin/creational/singleton/singleton.dart) 57 | - [README](bin/creational/singleton/README.md) 58 | 59 | ## Structural Patterns 60 | 61 | Structural patterns are concerned with the composition of classes and objects. 62 | 63 | ### Adapter 64 | 65 | The Adapter pattern is a structural design pattern that allows objects with incompatible interfaces to work together. This pattern involves a single class, the adapter, which is responsible for communication between the two different interfaces. 66 | 67 | - [Implementation](bin/structural/adapter/singleton.dart) 68 | - [README](bin/structural/adapter/README.md) 69 | 70 | ### Bridge 71 | 72 | The Bridge pattern is a structural design pattern that decouples an abstraction from its implementation, allowing both to vary independently. This is especially useful when a class can be extended in multiple dimensions, enabling the creation of platform-independent classes and applications. 73 | 74 | - [Implementation](bin/structural/bridge/bridge.dart) 75 | - [README](bin/structural/bridge/README.md) 76 | 77 | ### Composite 78 | 79 | The Composite pattern is a structural design pattern that allows you to compose objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly. This pattern is particularly useful when you need to work with objects that form a tree-like structure, enabling you to perform operations on the entire structure as if it were a single object. 80 | 81 | - [Implementation](bin/structural/composite/composite.dart) 82 | - [README](bin/structural/composite/README.md) 83 | 84 | ### Decorator 85 | 86 | The Decorator Pattern is a structural design pattern that allows you to dynamically add behaviors to objects by wrapping them in special wrapper objects called decorators. This pattern provides a flexible alternative to subclassing for extending functionality. 87 | 88 | - [Implementation](bin/structural/decorator/decorator.dart) 89 | - [README](bin/structural/decorator/README.md) 90 | 91 | ## Behavioral Patterns 92 | 93 | Behavioral patterns define the ways in which objects interact and communicate with each other. 94 | 95 | ## Contributing 96 | 97 | Feel free to contribute by submitting a pull request or opening an issue if you find any problems or have suggestions for improvements. 98 | -------------------------------------------------------------------------------- /bin/creational/prototype/README.md: -------------------------------------------------------------------------------- 1 | # Prototype Pattern 2 | 3 | The Prototype pattern is a creational design pattern that enables cloning of objects, generating a new instance with the same properties as the original. This pattern is useful when creating a new object with similar characteristics to an existing one, offering a more efficient alternative to constructing a new object from scratch. 4 | 5 | ## prtotype.dart 6 | ``` 7 | abstract class Shape { 8 | Shape clone(); 9 | void display(); 10 | } 11 | 12 | class Circle implements Shape { 13 | String color; 14 | 15 | Circle({required this.color}); 16 | 17 | @override 18 | Circle clone() { 19 | return Circle(color: color); 20 | } 21 | 22 | @override 23 | void display() { 24 | print('Circle with color: $color'); 25 | } 26 | } 27 | 28 | class Square implements Shape { 29 | String texture; 30 | 31 | Square({required this.texture}); 32 | 33 | @override 34 | Square clone() { 35 | return Square(texture: texture); 36 | } 37 | 38 | @override 39 | void display() { 40 | print('Square with texture: $texture'); 41 | } 42 | } 43 | 44 | void main() { 45 | // Create original objects 46 | Circle circle = Circle(color: 'red'); 47 | Square square = Square(texture: 'brick'); 48 | 49 | // Display original objects 50 | circle.display(); // Output: Circle with color: red 51 | square.display(); // Output: Square with texture: brick 52 | 53 | // Clone and modify objects 54 | Circle circleClone = circle.clone(); 55 | circleClone.color = 'blue'; 56 | 57 | Square squareClone = square.clone(); 58 | squareClone.texture = 'wood'; 59 | 60 | // Display cloned objects 61 | circleClone.display(); // Output: Circle with color: blue 62 | squareClone.display(); // Output: Square with texture: wood 63 | } 64 | ``` 65 | 66 | ## Key Concepts 67 | - **Prototype**: The base interface or abstract class that defines the method for cloning itself. 68 | - **Concrete Prototypes**: Classes that implement the **Prototype** interface and define the cloning behavior. 69 | - **Client**: The class that creates and uses cloned objects. 70 | 71 | ## Prototype Pattern vs. `copyWith` 72 | The Prototype pattern and `copyWith` method are two ways of creating new objects based on existing ones. While both can be used to achieve similar results, there are some key differences between them. 73 | 74 | ### Prototype Pattern 75 | - The Prototype pattern relies on a `clone` method (or a similar one) that is defined in an interface or abstract class. Concrete classes implement this method to define their cloning behavior. 76 | - When using the Prototype pattern, objects are responsible for cloning themselves. This makes it possible to create new instances without knowing the exact type of the object being cloned. 77 | - The Prototype pattern is useful when you need to create complex object structures with shared and/or deep copying behavior. 78 | 79 | ### `copyWith` Method 80 | - The `copyWith` method is a named method that is usually defined directly in the class. 81 | - Using `copyWith` requires the client code to know the exact type of the object being copied, as the method is specific to each class. 82 | - The `copyWith` method is more suitable for simple objects and data classes, where only a few properties need to be changed when creating a new instance. 83 | 84 | In conclusion, if you have a simple object with few properties and need a new instance with some changes, the `copyWith` method is a suitable choice. On the other hand, if you need to deal with complex objects with shared or deep copying behavior, or if you want to create new instances without knowing the exact object type, the Prototype pattern is a better option. 85 | 86 | 87 | ## Relationship with Other Patterns 88 | 89 | - Many architectures start with the **Factory Method** (simpler and extensible through subclasses) and evolve towards **Abstract Factory**, **Prototype**, or **Builder** (more flexible, but also more complex). 90 | **Abstract Factory** classes are often implemented using the **Factory Method**, but they can also be built based on the **Prototype**. 91 | - The **Prototype** pattern can be helpful for the Command pattern when the command needs to be copied before being added to the history of executed commands. 92 | - Architectures based on **Composites** and **Decorators** can often be improved by implementing the **Prototype** pattern, which allows cloning complex object structures instead of rebuilding them from scratch. 93 | - **Prototype** does not rely on inheritance but requires a complex initialization operation, whereas the Factory Method is built on inheritance but does not require complex initialization. 94 | - Memento can sometimes be replaced with the **Prototype** if the object, the state of which needs to be saved in history, is simple enough and does not have active links to external resources or can be easily restored. 95 | - **Abstract Factory**, **Builder**, and **Prototype** can all be implemented using the **Singleton** pattern. 96 | 97 | ## Pros and Cons 98 | 99 | ### Pros 100 | - Simplifies the cloning process of objects with numerous fields and nested objects. 101 | - Reduces the need for repetitive code in object creation. 102 | - Facilitates adding new object types to the system. 103 | 104 | ### Cons 105 | - Requires a complex implementation for objects with circular references. 106 | - Cloning objects may not be always clear, as it depends on the specific cloning logic. 107 | 108 | # Read more 109 | - [Refactoring Guru: Prototype](https://refactoring.guru/design-patterns/prototype) 110 | - [Wikipedia: Prototype pattern](https://en.wikipedia.org/wiki/Prototype_pattern) -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /bin/structural/decorator/README.md: -------------------------------------------------------------------------------- 1 | # Decorator Pattern: Dynamically Extending Objects 2 | 3 | Hello, Dart enthusiasts and design pattern adventurers! 👋 Today, we're diving into the fascinating world of the Decorator pattern. Buckle up as we explore how this pattern can elegantly solve the challenge of extending object functionality without subclassing madness. 4 | 5 | ## What's the Decorator Pattern All About? 6 | 7 | The Decorator pattern allows you to add new behaviors to objects dynamically by placing these objects inside special wrapper objects. Imagine it as a way to "decorate" your objects with new capabilities, like adding toppings to your favorite ice cream 🍦 without changing the original ice cream. 8 | 9 | Key points: 10 | - Adds functionality to objects without altering their structure 11 | - Provides a flexible alternative to subclassing 12 | - Follows the Open/Closed Principle: open for extension, closed for modification 13 | 14 | ## When Should You Use It? 15 | 16 | Consider the Decorator pattern when: 17 | 1. You need to add responsibilities to objects dynamically and transparently 18 | 2. You want to avoid a class hierarchy explosion 19 | 3. You can't extend an object's behavior using inheritance (e.g., sealed classes) 20 | 21 | ## The Anatomy of a Decorator 22 | 23 | Let's break down the key components: 24 | 25 | 1. **Component Interface**: Defines the interface for objects that can have responsibilities added to them dynamically. 26 | 27 | 2. **Concrete Component**: The basic object to which new behaviors can be attached. 28 | 29 | 3. **Decorator**: Maintains a reference to a Component object and defines an interface that conforms to Component's interface. 30 | 31 | 4. **Concrete Decorators**: Add responsibilities to the component. 32 | 33 | ## Show Me The Code! 34 | 35 | Let's look at a simplified version of our logging example to illustrate the pattern: 36 | 37 | ```dart 38 | // Base Interface for the Logger 39 | abstract interface class Logger { 40 | void log(String message); 41 | } 42 | 43 | // Basic Implementation 44 | class BasicLogger implements Logger { 45 | @override 46 | void log(String message) { 47 | print(message); 48 | } 49 | } 50 | 51 | // Base class for decorators 52 | abstract class LoggerDecorator implements Logger { 53 | final Logger _wrappedLogger; 54 | 55 | const LoggerDecorator(this._wrappedLogger); 56 | 57 | @override 58 | void log(String message) { 59 | _wrappedLogger.log(message); 60 | } 61 | } 62 | 63 | // Decorator for adding timestamps 64 | class TimestampDecorator extends LoggerDecorator { 65 | TimestampDecorator(Logger logger) : super(logger); 66 | 67 | @override 68 | void log(String message) { 69 | final timestamp = DateTime.now().toIso8601String(); 70 | super.log('$timestamp: $message'); 71 | } 72 | } 73 | 74 | // Decorator for adding log levels 75 | class LevelDecorator extends LoggerDecorator { 76 | final String level; 77 | 78 | LevelDecorator(Logger logger, this.level) : super(logger); 79 | 80 | @override 81 | void log(String message) { 82 | super.log('[$level] $message'); 83 | } 84 | } 85 | 86 | // Decorator for writing to a file 87 | class FileWriteDecorator extends LoggerDecorator { 88 | final String filePath; 89 | 90 | FileWriteDecorator(Logger logger, this.filePath) : super(logger); 91 | 92 | @override 93 | void log(String message) { 94 | super.log(message); 95 | // In a real implementation, we would write to a file here 96 | print('Also writing "$message" to $filePath'); 97 | } 98 | } 99 | ``` 100 | 101 | ## Putting It All Together 102 | 103 | Here's how you might use these decorators: 104 | 105 | ```dart 106 | void main() { 107 | // Create a basic logger 108 | final basicLogger = BasicLogger(); 109 | 110 | // Create a decorated logger with multiple decorators 111 | final decoratedLogger = FileWriteDecorator( 112 | LevelDecorator( 113 | TimestampDecorator(basicLogger), 114 | 'INFO' 115 | ), 116 | 'log.txt' 117 | ); 118 | 119 | // Use the decorated logger 120 | decoratedLogger.log('This is a test message'); 121 | 122 | // Use it again to show caching won't apply here 123 | decoratedLogger.log('This is another test message'); 124 | 125 | // Output: 126 | // 2024-07-27T15:27:47.876490: [INFO] This is a test message 127 | // Also writing "This is a test message" to log.txt 128 | // 2024-07-27T15:27:47.877530: [INFO] This is another test message 129 | // Also writing "This is another test message" to log.txt 130 | } 131 | ``` 132 | 133 | ## Why Should You Care? 134 | 135 | 1. **Flexibility**: Add or remove responsibilities from objects at runtime. 136 | 2. **Composition Over Inheritance**: Achieve behavior modification through composition, avoiding class explosion. 137 | 3. **Single Responsibility Principle**: Each decorator class focuses on a single concern. 138 | 4. **Open/Closed Principle**: Extend object behavior without modifying existing code. 139 | 140 | ## Real-World Applications 141 | 142 | The Decorator pattern isn't just for loggers! Here are some other use cases: 143 | - Adding compression or encryption to data streams 144 | - Extending UI component functionalities in Flutter 145 | - Adding caching or retry mechanisms to network requests 146 | - Enhancing database query objects with additional operations 147 | - etc. 148 | 149 | ## Learn More 150 | 151 | - [Refactoring Guru: Decorator Pattern](https://refactoring.guru/design-patterns/decorator) 152 | - [Design Patterns in Dart: Decorator](https://scottt2.github.io/design-patterns-in-dart/decorator/) 153 | - [Wikipedia: Decorator pattern](https://en.wikipedia.org/wiki/Decorator_pattern) 154 | 155 | Remember, the key to mastering the Decorator pattern is practice and creative thinking. Look for opportunities in your code where you need to add behavior dynamically, and the Decorator might just be your new best friend! Happy coding! 🚀 156 | --------------------------------------------------------------------------------