Présentation du cours
Ce cours fournit un aperçu complet des Modèles de conception en C# et .NET de une perspective pratique. Ce cours couvre en particulier les modèles avec l’utilisation de :
- Les dernières versions de C# et le framework .NET
- Utilisation d’approches de programmation modernes : injection de dépendances, programmation réactive, etc.
- Utilisation d’outils de développement modernes tels que ReSharper
- Discussions sur le modèle variantes et approches alternatives
Ce cours donne un aperçu de toute la conception du Gang of Four (GoF) modèles tels que décrits dans leur livre séminal, ainsi que des variations modernes, des ajustements, des discussions sur l’utilisation intrinsèque des modèles dans la langue.
Quoi sont des Design Patterns ?
Les Design Patterns sont des solutions réutilisables aux problèmes de programmation courants. Ils ont été popularisés avec le livre de 1994 Design Patterns : Elements of Reusable Object-Oriented Software par Erich Gamma, John Vlissides, Ralph Johnson et Richard Helm (qui sont communément connus sous le nom de Gang of Four, d’où l’acronyme GoF).
Le livre original a été écrit en utilisant C++ et Smalltalk comme exemples, mais depuis lors, les modèles de conception ont été adaptés à tous les langages de programmation imaginables : C#, Java, PHP et même les langages de programmation qui ne sont pas strictement orientés objet, comme JavaScript.
L’attrait des modèles de conception est immortel : nous les voyons dans les bibliothèques , certains d’entre eux sont intrinsèques aux langages de programmation, et vous les utilisez probablement quotidiennement même si vous ne vous rendez pas compte qu’ils sont là.
Quels modèles ce cours couvre-t-il ?
Ce cours couvre tous les modèles de conception du GoF. . En fait, voici la liste complète de ce qui est couvert :
- Principes de conception SOLIDES : principe de responsabilité unique, ouverture -Principe fermé, principe de substitution de Liskov, principe de ségrégation d’interface et principe d’inversion de dépendance
- Modèles de conception créatifs : constructeur, usines (méthode d’usine et usine abstraite), prototype et singleton</li >
- Modèles de conception structurelle : adaptateur, pont, composite, décorateur, façade, poids mouche et proxy
- Modèles de conception comportementale : chaîne de responsabilité, Commande, interprète, itérateur, médiateur, mémento, objet nul, observateur, état, stratégie, méthode de modèle et visiteur
Qui Le cours est-il pour ?
Ce cours est destiné aux développeurs .NET/C# qui veulent voir non seulement des exemples de manuels de modèles de conception, mais également les différentes variantes et astuces qui peuvent être appliquées pour mettre en œuvre les modèles de conception de manière moderne. Par exemple, l’introduction du DLR nous permet d’utiliser un ImpromptuObject, de sorte que notre DynamicObject expose n’importe quelle interface que nous désirons. Cela permet une programmation dynamique et de nombreux modèles de conception sont présentés en termes de variations statiques et basées sur le DLR.
Style de présentation
Ce cours est présenté comme une (très grande) série de démonstrations en direct effectuées dans Microsoft Visual Studio. La plupart des démos sont à fichier unique, vous pouvez donc télécharger le fichier joint à la leçon et l’exécuter dans Visual Studio, Visual Studio Code, Rider ou un autre IDE de votre choix.
Ce cours n’utilise pas de diagrammes de classes UML ; toutes les démos sont codées en direct. J’utilise Visual Studio, divers packages NuGet, le lanceur de tests unitaires R# et même dotMemoryUnit.
A taste of things to come...
The SOLID Design Principles
What are SOLID principles, where do they come from and why do we care?
A look at the Single Responsibility Principle, which states that a class should only have one reason to change. Also tied to the concept of Separation of Concerns which is basically stating the same thing.
A discussion of the Open-Closed Principle, which states that classes should be open for extension, but closed for modification. In other words, you should extend functionality using interfaces and inheritance rather than jumping back into already-written/tested code and adding to it or changing it.
The Liskov Substitution Principle states that subtypes should be substitutable for their base types.
The Interface Segregation Principle is simple: don't throw everything in the kitchen sink into an interface because then all its users will have to implement things they do not need. Instead, split the interface into several smaller ones.
Not to be confused with dependency injection, dependency inversion specifies that high-level modules should not depend on low-level ones; both should depend on abstractions. Confusing, huh?
A summary of the things we've learned in this section of the course.
Builder
A brief note about the three categories of design patterns: creational, structural and behavioral.
A discussion of the Builder design pattern and what it's used for.
A look at why you'd want to have a builder in the first place.
We implement a simple builder for constructing trees of HTML elements.
We make the builder fluent by returning this from builder methods.
Inheriting fluent interfaces is not easy because this cannot be returned in a virtual way. But we can make it work using recursive generics.
We can extend a builder without breaking OCP using a functional approach and extension methods.
We look at a more complicated builder facade that exposes several sub-builders (builder facets) for building up parts of an object in a fluent manner.
A summary of the things we've learned about the Builder pattern.
Factories
A discussion of the general concept of factories and the two design patterns: Factory Method and Abstract Factory.
A scenario where having a factory interface actually makes sense.
Implementing a factory method is easy, and you can turn a constructor into a factory method using ReSharper.
We want to perform async initialization, but constructors cannot be marked async. Factories to the rescue!
When you want all the factory methods in a separate class.
An external factory needs the created object's constructor to be public. But what if you want it to be private? The solution is simple: stick a factory into the class whose instances it creates!
Sometimes, you want abstract factories with abstract objects; we support DIP but break OCP in the process.
Can we fix an OCP violation without introducing an IoC container? Seems we can.
A summary of the things we've learned in this module.
Prototype
A discussion of the Prototype factory (not to be confused with a rather good game of the same name) and what it's used for.
The .net Framework comes with an ICloneable
interface but its use is not recommended. Why not?
Another suspect approach from the land of C++. While it avoids the confusion of ICloneable, it's not clear-cut either. Plus, we still have to do things recursively, which is tiring.
Let's be clear about what we're doing.
How would you succinctly implement inheritance when deep copying is concerned?
How to make a copy of the entire object graph without writing any copy-specific code? Easy, just serialize and deserialize!
A summary of all the things we've learned about the prototype pattern.
Singleton
Ahh, the much maligned Singleton? Is it really that evil? Let's find out...
Avoiding all the philosophical nonsense surrounding double-checked locking (it’s not thread-safe) and implementations involving inner static classes (with an empty static
constructor to avoid beforefieldinit
), we simply look at a safe .net 4-like way of making a lazy, thread-safe singleton.
The singleton works fine, so what's the problem? Turns out, hard reference to a type means we cannot fake it in our tests. Oops!
The only socially acceptable way of using a singleton is with a DI framework.
Typically, marking a component as a singleton is trivial. Check out my Dependency
Injection course! (link below)
A variation on a Singleton pattern, the Monostate lets the client instantiate as many copies of the singleton class as they want, but all those copies refer to the same static
data. Is this a good idea? Let’s find out!
An alternative that completely skirts the issue of thread safety.
A very common and simple design pattern.
A summary of all that we've learned about the Singleton. As you can see, it's not really that evil provided it can be substituted by a different type (polymorphism). Also, lifetime management is best left to a specialized system (i.e. a DI container).
Adapter
A look at the Adapter design pattern.
We are going to build a simple adapter for the rending of vector data where only a raster renderer is available.
An adapter can generate a large number of temporary objects. Caching helps us avoid doing extra work more than once.
Unlike C++, C# does not allow us to use literal values (numbers, strings) as generic arguments. Generic Value Adapter is a pattern that helps us deal with this. This lecture also uses the Factory Method design pattern and recursive generics.
Let's take a look at how Autofac supports the creation of adapters. For more info on Dependency Injection, see my Autofac course!
A summary of all the things we've learned about the Adapter pattern.
Bridge
A discussion of the Bridge pattern and what it's used for.
A simple illustration of how to build a bridge.
A summary of all the important things we've learned in this section of the course.
Composite
A discussion of what the Composite pattern is for and how it's used.
Let's implement the Composite pattern by considering individual geometric shapes as well as grouping of shapes.
Let's apply the Composite pattern to the implementation of simple neural networks (individual neurons and layers of neurons).
A look back at our OCP demo, where we use the Composite pattern to introduce a base class useful for making combinators.
A summary of all the things we've learned about the Composite design pattern.
Decorator
A look at the Decorator design pattern.
StringBuilder is unfortunately sealed. Let's see how we can have its de facto inheritor as a Decorator.
Here we build a pattern which is both a decorator (over a StringBuilder) and an adapter (adapting string's operator support).
When you implement pseudo-multiple inheritance using interfaces, you quite often end up implementing the Decorator pattern.
C#8 introduces default interface members. Does this change the MI situation? Not really.
A look at how to make decorators-of-decorators.
How can you handle a decorator being applied more than once?
Can decorators be composed as nested generic type arguments? They can, but things aren't as rosy in .NET as they are in C++.
Let's take a look at how Autofac supports decorators. For more info on Dependency Injection, see my Autofac course!
A summary of all the things we've learned about the Decorator design pattern.
Façade
A look at the Facade design pattern. Also an explanation of that weird letter C.
Instead of building a clinical example, let's take a look at a real-life Facade!
A summary of the things we've learned about the Facade design pattern.
Flyweight
A discussion of the Flyweight design pattern and what it's used for.
So what if .NET does string interning? We can still find a scenario where string space optimization is possible.
Text formatting is a classic example of a Flyweight. Instead of keeping formatting flags for every single character in a line of text, let's implement ranges!
A summary of all the things we've learned about the Flyweight design pattern.
Proxy
A look at the Proxy design pattern.
Let's implement a proxy which adds access control to the object.
One very common scenario is where developers replace ordinary properties with Property<T>-typed objects. Let's build out own implementation and discuss what it's for.
A proxy for a single value? Why would you need this?
A mixture of the Composite and Proxy design patterns used to solve the 'array of structures' problem.
We build a boolean composite property proxy and then refactor the code to use array-backed properties.
A dynamic proxy is created at runtime, and saves us from having to replicate every single interface member individually. Let's implement such a proxy for logging.
Almost like "Alien vs. Predator" — a comparison of the Proxy and Decorator design patterns.
A proxy/decorator approach to helping UI bind to data while keeping notifications separate to comply with SRP. Part of the MVVM paradigm.
A fun proxy/adapter implementation that shows up in many scenarios. In this case, we iterate all the combinations of several operators using a simple ++ increment.
A summary of the things we've learned about the Proxy design pattern.
Chain of Responsibility
A look at the Chain of Responsibility design pattern and discussion of what it's used for.
Brief discussion of the concept of Command Query Separation (CQS).
Chain of Responsibility implemented by chaining method calls together.
A more sophisticated approach to implementing the chain of responsibility.
A summary of the things we've learned about the Chain of Responsibility design pattern.
Command
A look at the Command design pattern.
Let's implement the Command design pattern in a simple scenario.
A simple demonstration of how to implement Undo functionality while using Command.
Composite commands (a.k.a. macros) are very common.
A summary of all the things we've learned about the Command design pattern.
Interpreter
An overview of the Interpreter design pattern, which actually brings with it a whole field of Computer Science typically called Compiler Theory.
Lexing is the process of splitting textual input into lexical tokens.
Parsing is the process of converting a series of tokens into an Abstract Syntax Tree (AST).
Parsers are typically made with specialized parser frameworks and ANTLR is one such example.
A summary of the things we've learned about the Interpreter design pattern.
Iterator
A look at the Iterator design pattern.
A look at how to build a handmade iterator. This is the 'C++ Way', so isn't really recommended in C#.
A much more natural way of providing iteration functionality.
Can you iterate an object that doesn’t even implement IEnumerable<T>
? You sure can. foreach
works on duck typing, i.e. finding elements by name and type. If they’re all there, it works. Let’s reuse our old iterator, then!
What happens if you want to iterate the value of every single property (or a specific range of properties) in your application? Well, this is rather easy: just make a single array-typed backing field for every single property!
A summary of all the things we've learned about iterators in .NET.
Mediator
A look at the Mediator design pattern.
A classic example of the Mediator pattern is a chat room. Well, let's build one!
A more advanced Mediator scenario, making use of Reactive Extensions and Dependency Injection (Autofac). Check out the Reactive Extensions course (link below).
A look at Jimmy Bogard's shrink-wrapped Mediator package.
A summary of the things we've learned about the Mediator design pattern.
Memento
A look at the Memento design pattern.
Let's turn back to our classic BankAccount example and implement the Memento pattern.
If we keep every single change as a Memento stored internally, we can easily implement Undo/Redo operations.
One less intuitive reason to use Memento is in interop, for example when you are using C++ code from C#. Why? Because only simple data (scalars and arrays) can go through P/Invoke. Classes, unfortunately, cannot.
A summary of the things we've learned about the Memento design pattern.
Null Object
A discussion on what the Null Object is useful for. Note this is not a GoF pattern.
A demonstration of a very simple implementation of a Null Object.
How can we completely encapsulate a singleton Null Object so that the client never gets to see it?
If the interface is too complicated, building a Null Object on it is a bit tedious. So instead, what you can do is generate a DLR-based Null Object that conforms to a specific interface. Warning: use of dynamic programming implies a big performance hit on method calls. Probably not the best approach for production code, but adequate for testing.
A summary of all the things we've learned about the Null Object design pattern.
Observer
A look at the Observer design pattern.
The Observer pattern is baked into C# with the event keyword.
If an object subscribes to an event on an object that lives longer, the object may end up staying alive for a lot more than necessary, even after there are no references to it. This can cause a de facto memory leak. We look at how this problem can be solved.
What if our subscriptions were separate, disposable objects? Oh wait, that's exactly what Reactive Extensions try to do.
A look at how the Observer design pattern is implemented for collections and sequences. More info about observable collections in my Reactive Extensions course!
One-way binding is easy, but what if we want two members of two different classes to bind their values together?
It's easy to mutually bind two properties if they have setters. How about a more complicated scenario — a property that affects one or more other (possibly read-only) properties?
Using an IoC container, a declarative approach to event subscriptions is also feasible.
A summary of all the things we've learned about the Observer design pattern.
State
A look at the State design pattern.
The classic implementation of state machine. Bulky, unreadable and generally not recommended.
Let's hand-roll a simple finite state machine.
You can implement an entire state machine as a single switch statement.
C#8 switch expressions allow for very informal definition of simple state machines.
A look at how to make state machines with the excellent Stateless library.
A summary of the things we've learned about the State design pattern.
Strategy
A look at the strategy design pattern.
An implementation of dynamic strategy pattern which lets us change strategies at runtime.
A static implementation of the Strategy pattern forces us to choose the strategy to use at compile time.
The .NET BCL uses the Strategy pattern for custom equality and comparison operations.
A summary of the things we've learned about the Strategy design pattern.
Template Method
A look at the Template Method design pattern.
Let's write a template method for a typical board game.
You can construct a template method without the use of classes and inheritance by instead using functions passed as parameters to the template method.
A summary of the things we've learned about the Template Method design pattern.
Visitor
A look at the Visitor design pattern.
Let's break OCP to support expression printing.
Another approach to printing expressions, this time by using reflection and checking against types. Not very efficient!
Finally, we implement the classic Visitor pattern using double dispatch.
The DLR allows us to choose an overload to call based on argument type, providing the argument is actually dynamic. This means we take a massive performance hit but can do without altering the hierarchy of types.
An alternative to the GoF Visitor implementation, courtesy of Robert C Martin (a.k.a. Uncle Bob).
A summary of the things we've learned about the Visitor design pattern.
Course Summary
A summary of all the Creational patterns that we've met in this course: Builder, Factory (Factory Method, Abstract Factory), Singleton and Prototype.
A summary of all the Structural design patterns we've met in this course: Adapter, Bridge, Composite, Decorator, Facade, Flyweight and Proxy.
A summary of all the Behavioral design patterns we've met in this course: Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Null Object, Observer, State, Strategy, Template Method and Visitor.
A few last words before we end the course... I'm so sad it's over. Ah well, there's always cake.
Bonus Lectures: Related Concepts
A more real-life example of an ASCII-specialized .NET string.
CPS is a pattern of algorithmic decomposition. Typically associated with JavaScript, it can also be used in C# development. Let's take a look!
Did you think that Inversion of Control is only relevant to Dependency Injection? You are wrong! Here is an example of local inversion of
control — nothing to do with DI, but also a lot of fun!
We looked at both a ‘plain’ event broker as well as one based on Reactive Extensions. Time for the heavy artillery! We are now going to implement a declarative event broker (where event subscriptions are done using attributes) using the Unity DI framework.
C# 6 introduced the ?. (Elvis) operator for chaining checks against null. But what if we also want other types of checks (e.g., if) involved in those chains? Let's see how we can implement this with a bit of functional programming.
An introductory look at CQRS and Event Sourcing.
Congratulations on completing this course!
Now that you've mastered design patterns, check out some of my other .NET/C# courses:
- Mastering LINQ with C# and .NET
- Learn Parallel Programming with C# and .NET
- Dependency Injection in C# and .NET with the Autofac Library
- Mastering Reactive Extensions with C# and .NET
- Mastering .NET and C# Unit Testing with NUnit and Moq
- What's New in C# 7 and 7.1
Enjoy!
Dmitri
Bonus Lectures: Functional Patterns in F#
An overview of the design patterns we're going to implement in a functional way.
Let's use F# list support to build HTML in a more natural way.
Function decorators done in a functional way.
F# lets us create class-less implementations of interfaces in place. Let's see how to use this to make factories.
A real-life example of parsing data into F# discriminated unions. Warning: this demo is rather complicated.
The F# way of the Strategy pattern is to use higher-order functions.
The F# way for the Template Method is to also use higher-order functions.
A summary of the things we've learned about pattern implementations in F#.