- Design Patterns.pdf
- DevIQ - Design Patterns
- Disciplined Agile - The Design Patterns Repository
- Microsoft Docs - Cloud Design Patterns
- GitHub - skimedic/presentations: Patterns
- Pluralsight - Design Patterns Library by David Starr, Niraj Bhatt, Robert Horvick, Scott Allen, Brian Lagunas, Steve Smith, Donald Belcham, John Sonmez, John Brown, Keith Sparkjoy, and Glenn Block
-
-
Save DilanLivera/e6755b9140895c23526385696d369b05 to your computer and use it in GitHub Desktop.
Incompatible interfaces between a client and a service provider.
Adapters convert the interface of one class into an interface a client expects.
One variation of class adapter that doesn't require multiple class inheritance simply implements the adapter interface. This variation can be used in languages like C# and Java. In C#, you'll mostly use object adapters. The reason is that C# doesn't support multiple inheritances, and a design principle of C# is to prefer composition over inheritance. - Steve Smith, "C# Design Patterns: Adapter"
Please find the code samples from C# Design Patterns: Adapter course at ardalis/DesignPatternsInCSharp: Adapter.
- The Decorator has a similar structure, but the decorator intends to add functionality.
- The Bridge pattern is very similar in structure, but its intent specifically is to allow interfaces and their implementations to vary independently from one another.
- The Proxy is structurally similar as well, but it intends to control access to a resource, not to convert an incompatible interface.
- The Repository pattern sometimes acts as an adapter, providing a common interface for persistence that can map various incompatible interfaces to a single common data access strategy.
- The Strategy pattern is frequently used with the Adapter pattern to inject different implementations of behaviour into a particular client class. The Facade design pattern's intent is similar to the adapters in that it alters an interface to make it easier for a client to use. The difference, though, is that the facade often sits in front of multiple different types. Its goal is to simplify a complex set of operations, not necessarily to provide a way to swap between different incompatible operations easily.
The adapter pattern can also be applied to result types, not just the service providers. In this case, to apply the pattern, you must create or choose a common result type that your client expects to consume. Then you can inherit from this type and use composition to delegate calls made to the common type to the specific incompatible type. - Steve Smith, "C# Design Patterns: Adapter"
Please find a code samples of this at ardalis/DesignPatternsInCSharp: Adapter: ResultWrapper.
- Pluralsight - Design Patterns Library by David Starr, Niraj Bhatt, Robert Horvick, Scott Allen, Brian Lagunas, Steve Smith, Donald Belcham, John Sonmez, John Brown, Keith Sparkjoy, and Glenn Block: Decorator Design Pattern
- Pluralsight - Dependency Injection in ASP.NET Core 6 by Steve Gordon: Applying the Decorator Pattern with Scrutor
Flyweight is a structural design pattern that allows programs to support vast quantities of objects by keeping their memory consumption low.The pattern achieves it by sharing parts of object state between multiple objects. In other words, the Flyweight saves RAM by caching the same data used by different objects.
Flyweight can be recognized by a creation method that returns cached objects instead of creating new.
Note Since the same flyweight object can be used in different contexts, you have to make sure that its state can’t be modified. A flyweight should initialize its state just once, via constructor parameters. It shouldn’t expose any setters or public fields to other objects.
- Divide fields of a class that will become a flyweight into two parts:
- The intrinsic state: the fields that contain unchanging data duplicated across many objects
- The extrinsic state: the fields that contain contextual data unique to each object
- Leave the fields that represent the intrinsic state in the class, but make sure they’re immutable. They should take their initial values only inside the constructor.
- Go over methods that use fields of the extrinsic state. For each field used in the method, introduce a new parameter and use it instead of the field.
- Optionally, create a factory class to manage the pool of flyweights. It should check for an existing flyweight before creating a new one. Once the factory is in place, clients must only request flyweights through it. They should describe the desired flyweight by passing its intrinsic state to the factory.
- The client must store or calculate values of the extrinsic state (context) to be able to call methods of flyweight objects. For the sake of convenience, the extrinsic state along with the flyweight-referencing field may be moved to a separate context class.
Car.cs
public class Car
{
public string Owner { get; set; }
public string Number { get; set; }
public string Company { get; set; }
public string Model { get; set; }
public string Color { get; set; }
}
Flyweight.cs
/*
* The Flyweight stores a common portion of the state(also called intrinsic
* state) that belongs to multiple real business entities.The Flyweight
* accepts the rest of the state(extrinsic state, unique for each entity)
* via its method parameters.
*/
public class Flyweight
{
private readonly Car _sharedState;
public Flyweight(Car car)
{
this._sharedState = car;
}
public void Operation(Car uniqueState)
{
string shared = JsonSerializer.Serialize(this._sharedState);
string unique = JsonSerializer.Serialize(uniqueState);
Console.WriteLine($"Flyweight: Displaying shared {shared} and unique {unique} state.");
}
}
FlyweightFactory.cs
/*
* The Flyweight Factory creates and manages the Flyweight objects.
* It ensures that flyweights are shared correctly.
* When the client requests a flyweight, the factory either returns an existing instance or
* creates a new one, if it doesn't exist yet.
*/
public class FlyweightFactory
{
private readonly IDictionary<string, Flyweight> flyweights =
new Dictionary<string, Flyweight>();
public FlyweightFactory(params Car[] cars)
{
Array.ForEach(cars, car => flyweights.Add(GetKey(car), new Flyweight(car)));
}
// Returns a Flyweight's string hash for a given state.
public string GetKey(Car key)
{
List<string> elements = new List<string>
{
key.Model,
key.Color,
key.Company
};
if (key.Owner != null && key.Number != null)
{
elements.Add(key.Number);
elements.Add(key.Owner);
}
elements.Sort();
return string.Join("_", elements);
}
// Returns an existing Flyweight with a given state or creates a new one.
public Flyweight GetFlyweight(Car car)
{
string key = GetKey(car);
if (!flyweights.ContainsKey(key))
{
Console.WriteLine("FlyweightFactory: Can't find a flyweight, creating new one.");
flyweights.Add(key, new Flyweight(car));
}
else
{
Console.WriteLine("FlyweightFactory: Reusing existing flyweight.");
}
return flyweights[key];
}
public void ListFlyweights()
{
Console.WriteLine($"\nFlyweightFactory: I have {flyweights.Count} flyweights:");
foreach (var (key, _) in flyweights)
{
Console.WriteLine(key);
}
}
}
Program.cs
class Program
{
static void Main()
{
// The client code usually creates a bunch of pre-populated
// flyweights in the initialization stage of the application.
var factory = new FlyweightFactory(
new Car { Company = "Chevrolet", Model = "Camaro2018", Color = "pink" },
new Car { Company = "Mercedes Benz", Model = "C300", Color = "black" },
new Car { Company = "Mercedes Benz", Model = "C500", Color = "red" },
new Car { Company = "BMW", Model = "M5", Color = "red" },
new Car { Company = "BMW", Model = "X6", Color = "white" }
);
factory.ListFlyweights();
AddCarToPoliceDatabase(factory, new Car
{
Number = "CL234IR",
Owner = "James Doe",
Company = "BMW",
Model = "M5",
Color = "red"
});
AddCarToPoliceDatabase(factory, new Car
{
Number = "CL234IR",
Owner = "James Doe",
Company = "BMW",
Model = "X1",
Color = "red"
});
factory.ListFlyweights();
}
public static void AddCarToPoliceDatabase(FlyweightFactory factory, Car car)
{
Console.WriteLine("\nClient: Adding a car to database.");
var flyweight = factory.GetFlyweight(new Car
{
Color = car.Color,
Model = car.Model,
Company = car.Company
});
/*
* The client code either stores or calculates extrinsic state and
* passes it to the flyweight's methods.
*
*/
flyweight.Operation(car);
}
}
Where a behaviour may be implemented in different ways, and one of the ways is to have no behaviour at all, treat the null case as one version.
//set-up the services
public interface INotificationService
{
Task SendNotification(string notification);
}
public class PushNotificationService : INotificationService
{
private readonly ILogger _logger;
public PushNotificationService(ILogger<PushNotificationService> logger)
{
_logger = logger;
}
public Task SendNotification(string notification)
{
//Do the work
_logger.LogInformation("{Notification} sent", notification);
return Task.CompletedTask;
}
}
public class NullNotificationService : INotificationService
{
public Task SendNotification(string notification)
{
return Task.CompletedTask;
}
}
//use the service
public class WeatherForecastsFetchedNotificationHandler
: INotificationHandler<WeatherForecastsFetchedNotification>
{
private readonly INotificationService _notificationService;
public WeatherForecastsFetchedNotificationHandler(
INotificationService notificationService)
{
_notificationService = notificationService;
}
public Task Handle(
WeatherForecastsFetchedNotification notification, CancellationToken cancellationToken)
{
return _notificationService.SendNotification("Weather forecasts got fetched");
}
}
//register the services
public class Startup
{
//removed for brevity
public void ConfigureServices(IServiceCollection services)
{
//removed for brevity
//resolving any services within the ConfigureServices method is not recommended
//this is done here for demo purposes
ServiceProvider provider = services.BuildServiceProvider();
IHostEnvironment hostEnvironment = provider.GetRequiredService<IHostEnvironment>();
if (hostEnvironment.IsDevelopment())
{
services.AddScoped<INotificationService, NullNotificationService>();
}
else
{
services.AddScoped<INotificationService, PushNotificationService>();
}
}
//removed for brevity
}
Need to control access to a type for performance, security, and other reasons.
Example
The following is from C# Design Patterns: Proxy course.
sequenceDiagram
participant Client
participant Proxy
participant Real Service
Client->>Proxy: SomeMethod()
activate Proxy
Proxy->>Real Service: SomeMethod()
activate Real Service
Real Service-->>Proxy: SomeMethod() response
deactivate Real Service
Proxy-->>Client: SomeMethod() response
deactivate Proxy
Typically proxy will mirror the interface of the real service. When the client makes a request to the proxy, the proxy has an opportunity to perform any logic needed before forwarding the request to the real service and, after receiving the response, before returning the response to the client (Eg. Caching the response if it is acting as a Cache).
A virtual proxy is a stand-in for an object that is expensive to create for real. Its purpose is generally to optimize for performance. Instead of being the real object, the proxy knows how to get the real object when required, after which it delegates all calls back to that real object. Two common examples of this approach are placeholders within UI screens to allow the screen to render quickly, even if real data isn't yet available. And lazy‑loaded entity properties, which are only populated if the property is accessed or perhaps when it is first enumerated. - Steve Smith, "C# Design Patterns: Proxy"
Please find the code samples from C# Design Patterns: Proxy course at ardalis/DesignPatternsInCSharp: Proxy: Virtual Proxy.
Remote Proxy is used to hide the details of working with remote data or services.
The goal of a remote proxy is to act as a local resource while hiding the details of how to connect to a remote resource over a network. The remote proxy centralises all knowledge of the network details, and often these proxies can be generated automatically based on some service definition file, like WSDL, .proto, a Swagger, or open API specification. - Steve Smith, "C# Design Patterns: Proxy"
Please find the code samples from C# Design Patterns: Proxy course at ardalis/DesignPatternsInCSharp: Proxy: Remote Proxy.
Smart Proxy performs additional actions when a resource is accessed.
A smart proxy is used to add additional logic around access to a resource. These can be useful to perform resource counting, or to manage the caching of a resource, or to lock access to shared resources. - Steve Smith, "C# Design Patterns: Proxy"
Please find the code samples from C# Design Patterns: Proxy course at ardalis/DesignPatternsInCSharp: Proxy: Smart Proxy.
Protective Proxy controls access to a sensitive resource by checking whether or not the client is authorised to perform those operations.
A protective or protection proxy is used to control access to a resource based on certain rules. This can help to eliminate having these checks live either in the client code or in the resource itself. Generally, this helps with separation of concerns, the don't repeat yourself principle, and the single responsibility principle. You can think of the protective proxy as being a kind of gatekeeper around the resource. - Steve Smith, "C# Design Patterns: Proxy"
Please find the code samples from C# Design Patterns: Proxy course at ardalis/DesignPatternsInCSharp: Proxy: Protective Proxy.
- The Decorator pattern has a very similar structure, but the Decorator pattern intends to add functionality, whereas the Proxy intends to control access. In the case of a Smart Proxy, this difference is very slim.
- The Prototype and the Proxy could be used to deal with an expensive object to create. Typically, a virtual proxy is used for this. However, the Proxy offers a stub or placeholder and fetches the real object on demand, while the Prototype pattern keeps a copy of the object on hand and can clone it when required.
- The Adapter is structurally very similar to the Proxy, but its purpose is to convert an incompatible interface into one that works for the client. It's not concerned with access control.
- The Flyweight pattern is also very similar to the Proxy pattern. However, the Flyweight pattern is designed to manage many references to a shared instance, while the Proxy is designed to wrap a single specific instance.
The following are scratch notes from the C# Design Patterns: Rules Engine Pattern by Steve Smith.
Design Patterns are like individual tools you can add to your toolkit as a software developer. In this course, C# Design Patterns: Rules Pattern, you'll learn to build and use a simple rules engine. First, you'll explore examples of problems and code smells that may benefit from applying rules. Next, you'll discover how to build a simple rules engine. Finally, you'll learn how to apply the engine in real application code and extend the application with new functionality. When you're finished with this course, you'll have the skills and knowledge of the rules engine pattern needed to apply it in your own applications.
According to Steve Smith,
A rules engine processes a set of rules and applies them to produce a result.
A rule describes a condition and may calculate a value.
- Places where the Open/Closed principle is violated(We need to modify the current implementation to extend its behaviour)
- Methods with lots of conditional complexity(i.e. cyclomatic complexity).
Note: To calculate the cyclomatic complexity in Visual Studio, Go to Analyze -> Calculate Code Metrics -> Select the Solution/Project
- Scoring games
- Calculating discounts for customer purchases
- Diagnosing health concerns
The key to tackling the complexity in an extensive method is teasing out individual cases, which we'll define as individual rules. As you extract these rules, make sure they're as small as practical(I(Steve Smith) didn't say as small as possible, but each rule should enforce a single case) and have a single responsibility.
Now rules need to be evaluated somewhere in your system. When refactoring, you might first assess them by hand in your existing "method". But eventually, you'll want the responsibility of evaluating a result from a collection of rules to reside in its own type, and that will be the rules engine class. As you start to think about how you'll pull out individual rules, consider what it will take to evaluate them correctly. Some cases are,
- The first rule that matches a condition is the only one that matters.
- In others, you'll need to evaluate every rule and then somehow aggregate or filter the individual results.
- In others, the order in which rules are evaluated may be necessary or offer performance advantages.
- Rule collection - This is the collection of the rules to apply for the input/context. (E.g. First time customer, Loyal customer, Veteran, Senior)
- Rules Engine - This is responsible for applying the rules to a given system context or scenario.
- Input from the system - E.g. customer, game state
- Keep individual rules simple
- Allow for complexity through combinations of simple rules
- Decide how rules will combine or be chosen
- Consider whether rule ordering will matter in the evaluation
- Accept rules collection in engine constructor
- Allow adding/removing rules or swapping sets of rules via methods
- Apply the rules to a given context or system state
- Choose the correct rule to apply or aggregate rules
- Follow refactoring fundamentals
- Extract methods for individual conditions
- Convert methods into Rule classes
- Create Rule Engine and evaluate Rules
- Replace original method logic with a call to Rules Engine
A template method is a method in a superclass that defines the skeleton of an operation in terms of higher-level steps. Subclass implement these steps. - Steve Smith, "C# Design Patterns: Template Method"
- Lockdown a process while allowing clients to alter certain steps in the process
- Generalize duplicate behaviour among methods in several classes
- Create and control extension points for future code implementations
- Very simple cases where you want to use inheritance, but you need to ensure base functionality is preserved. By default, child types can override virtual members of base types. When they do, the new method is called instead of the original. If the original method needs to be called and if it specifically must be called either before or after the child type's additional behaviour, there's no built‑in way to enforce this behaviour.
Please find the code samples from C# Design Patterns: Template Method course at ardalis/DesignPatternsInCSharp: Template Method.
- The Factory Method is often called by the template method. Frequently, the result of the template method is a returned object and part of the process of producing that object may be to invoke an appropriate factory method, perhaps in addition to other steps.
- The Strategy pattern provides a way to vary an entire algorithm used by a class by delegating it to another class via composition. The Template Method encapsulates an algorithm but allows it to vary among its child classes through inheritance.
- The Rules Engine pattern frequently leverages the Template Method pattern in its implementation. Individual rule processing may follow a process defined in a base rule class. Similarly, the engine itself may inherit behaviour from a base rule engine class that defines how it should operate.