The 23 design Patterns in Gof design patterns can be divided into three groups: Creation (creational), structural (Structural), behavioral (behavioral). Below to do a detailed analysis.
Create type
Create pattern processing object constructs and references. They abstracted the instantiation responsibility of the object instance from the customer code, keeping the code loosely coupled, placing the responsibility for creating complex objects in one place, following the principle of single responsibility and separation of concerns.
The following are the patterns in the Create type group:
1.Abstract Factory (Abstract Factory) mode: Provides an interface to create a set of related objects.
2.Factory method (Factory methods) mode: supports the responsibility of using a class to delegate the creation of valid objects.
3.Builder (Generator) mode: Separates the construction of the object itself so that it can construct different versions of the object.
4.Prototype (prototype) mode: the ability to copy or clone a class from a prototype instance, rather than creating a new instance.
5.Singleton (singleton) mode: Supports a class that instantiates only once and has only one global access point that can be used to access it.
Structural type
The structure pattern handles the combination and relationship of objects to meet the needs of large-scale systems.
The following are the patterns in the "structured" grouping:
1.Adapter (Adapter) mode: Enables classes of incompatible interfaces to be used together.
2.Bridge (bridging) mode: separates abstraction from its implementation, allowing implementations and abstractions to change independently of each other.
3.Composite (combined) mode: You can treat a group of objects that represent hierarchies as if they were a single instance of an object.
4.Decorator (decorative) mode: the ability to dynamically wrap a class and extend its behavior.
5.Facade (façade) mode: Provides a simple interface and controls access to a complex set of interfaces and subsystems.
6.Flyweight (enjoy meta) mode: Provides a way to efficiently share data among many small classes.
7.Proxy (proxy) mode: Provides a placeholder for a more complex class that instantiates expensive.
Behavioral type
Behavioral patterns handle communication between objects in terms of responsibilities and algorithms. The patterns in this grouping encapsulate complex behaviors and abstract them out of the system control flow, making complex systems easier to understand and maintain.
Here are the patterns in the "behavioral" grouping:
1.Chain of Responsibility (chain of responsibility) mode: Allows you to dynamically link commands to process requests.
2.Command (command) mode: Encapsulates a method into an object and separates the execution of the command from its callers.
3.Interpreter (interpreter) mode: Specifies how statements in a language are executed.
4.Iterator (iterator) mode: Provides a way to navigate a collection in a formalized manner.
5.Mediator (mediator) mode: Defines an object that allows other two objects to communicate without having to know each other.
6.Memento (Memo) Mode: Allows you to restore an object to its previous state.
7.Observer (Viewer) mode: Defines one or more classes to be alerted when another class changes.
8.State (State) mode: Allows an object to change its behavior by delegating to an independent, changing state object.
9.Strategy (Policy) mode: the ability to encapsulate an algorithm into a class and transform it at run time to alter the behavior of the object.
10.Template method (Template method) mode: Defines the algorithm flow control, but allows subclasses to override or implement the execution steps.
11.Vistor (visitor) mode: Ability to perform new functions on a class without affecting the structure of the class.
A number of design patterns and their groupings are described above. But how to choose and use it? Here are some things to keep in mind:
1. They cannot be used without understanding the pattern.
2. When designing, it is necessary to measure the complexity of introducing design patterns. It is best to measure the amount of time required to implement a pattern and the benefits it can bring. Remember the kiss principle: keep it simple and superficial.
3. Generalize the problem to identify the problem being handled in a more abstract way. Design patterns are a high-level solution, try to abstract the problem, and don't focus too much on the specifics of the problem.
4. Learn about patterns with similar properties and other patterns in the same group. Having used a pattern in the past does not mean that it is always the correct mode choice when solving a problem.
5. Package the changed parts. Understand what may change in your application. If you know that a particular quote discount algorithm will change over time, look for a pattern to help you change the algorithm without affecting the rest of the application.
6. After choosing a design pattern, make sure that the language and domain language of the pattern is used in the naming of the participants in the solution. For example, if you are using policy mode to provide solutions for different Express company pricing, then they are clearly, such as Fedexshippingcoststrategy. By combining common vocabularies and domain languages for patterns, you can make your code more readable and more understandable to other developers with schema knowledge.
There is no alternative to learning in terms of design patterns. The more you know about each design pattern, the better prepared you will be when you use them. When you encounter a problem looking for a solution, scan the purpose of each mode to evoke your own memory.
A good way to learn is to try to identify patterns in the. NET Framework, such as: ASP. NET cache uses the singleton mode, and the factory method mode is used when creating a new GUID instance, and the 2 XML class uses the factory Method mode, and version 1.0 is not used.
Let's take a quick-mode example to make it easier to deepen the image.
Create a new Class library project 0617. Daemonpattern.service, and then references the System.Web assembly.
First, add a Product.cs empty class as our model:
public class Product { }
Then add the ProductRepository.cs class as our data store, from where we can get the data entity object from the database:
public class Productrepository {public ilist<product> getallproductsin (int categoryId) { var Products = new list<product> (); Database operation to populate products. return products; } }
Finally add a class named ProductService.cs, with the following code:
public class Productservice {public productservice () { this.productrepository = new Productrepository (); } Private Productrepository productrepository; Public ilist<product> Getallproductsin (int. categoryId) { ilist<product> products; String Storagekey = String. Format ("products_in_category_id_{0}", categoryId); Products = (list<product>) HttpContext.Current.Cache.Get (storagekey); if (products = = null) {Products = Productrepository.getallproductsin (categoryId); HttpContext.Current.Cache.Insert (Storagekey, products); } return products; } }
From the logic of the code, we can clearly see that productservice through the Productrepository warehouse to obtain data from the database.
The following are some of the problems that this class library brings:
1.ProductService relies on the Productrepository class. If the API in the Productrepository class changes, it needs to be modified in the Productservice class.
2. The code is not testable. If you don't let the real productrepository class connect to a real database, you can't test the Productservice method, because there's a tight coupling between the two classes. Another problem with testing is that the code relies on using the HTTP context to cache the product. It is difficult to test this code that is tightly coupled to the HTTP context.
3. Forced to cache using HTTP context. In the current state, if you use a cache storage provider such as velocity or memcached, you need to modify the Productservice class and all other classes that use the cache. Both Verlocity and memcached are distributed memory object caching systems that can be used to override the default caching mechanism for ASP.
Arbitrarily, it seems that the code coupling is high, difficult to test, and not easy to replace.
Now that you know the problem, let's refactor it.
First, consider the problem that the Productservice class relies on the Productrepository class. In the current state, the Productservice class is very fragile, and if the API of the Productrepository class changes, you need to modify the Productservice class. this undermines the separation of concerns and the principle of single responsibility .
1. Dependency inversion principle (dependence on abstraction and not reliance on specifics)
The Productservice class and the Productrepository class can be decoupled by the dependency inversion principle, so that they all rely on abstraction: interfaces.
Right-clicking on the Productrepository class and selecting the "Refactor", "Extract Interface" option will automatically generate a IProductRepository.cs class for us:
Public interface iproductrepository { ilist<product> getallproductsin (int categoryId); }
Modify the existing Productrepository class to implement the newly created interface, with the following code:
public class Productrepository:iproductrepository {public ilist<product> getallproductsin (int CategoryId) { var products = new list<product> (); Database operation to populate products. return products; } }
Then update the Productservice class to ensure that it refers to an interface rather than a specific:
public class Productservice {public productservice () { this.productrepository = new Productrepository (); } Private Iproductrepository productrepository; Public ilist<product> Getallproductsin (int. categoryId) { ilist<product> products; String Storagekey = String. Format ("products_in_category_id_{0}", categoryId); Products = (list<product>) HttpContext.Current.Cache.Get (storagekey); if (products = = null) {Products = Productrepository.getallproductsin (categoryId); HttpContext.Current.Cache.Insert (Storagekey, products); } return products; } }
With this modification, the Productservice class now relies only on abstractions rather than concrete implementations, which means that the Productservice class is completely unaware of any implementation, ensuring that it is not so easily destroyed, and that the code is more resilient to change as a whole.
However, there is a problem here, as the Productservice class is still responsible for creating concrete implementations. It is not possible to test the code in the absence of a valid Productrepository class. So here we need to introduce another design principle to solve this problem: the dependency injection principle.
Since the Productservice class is still bound to the specific implementation of productrepository, we can move this process externally through the dependency injection principle, by injecting it through the class's constructor:
publi C class Productservice {public Productservice (iproductrepository productrepository) {THIS.PR Oductrepository = productrepository; } private Iproductrepository productrepository; Public ilist<product> Getallproductsin (int. categoryId) {ilist<product> products; String Storagekey = String. Format ("products_in_category_id_{0}", categoryId); Products = (list<product>) HttpContext.Current.Cache.Get (Storagekey); if (products = = null) {products = Productrepository.getallproductsin (categoryId); HttpContext.Current.Cache.Insert (Storagekey, products); } return products; } }
This allows the substitution to be passed to the Productservice class during testing, allowing the Productservice class to be tested in isolation. Removing the responsibility to get dependencies from the Productservice class ensures that the Productservice class follows a single responsibility principle: It now only cares about how to coordinate the retrieval of data from the cache or repository, rather than creating a concrete iproductrepository implementation.
There are three forms of dependency injection: constructors, methods, and properties . We're just using the constructor injection here.
Of course, the code now looks pretty basic, but once the cache mechanism is replaced, it will be tricky because the HTTP context-based cache is not encapsulated and replaced by the need to modify the current class. This undermines the open closure principle: open to extensions, and closed for modification.
Since the adapter (adapter) mode is primarily used to convert a class to a compatible interface, in the current example, we can modify the HttpContext cache API to Chengxiang the compatible API to be used. You can then use the dependency injection principle to inject the caching API into the Productservice class through an interface.
Here we create a xinjiekou called Icachestorage, which contains the following contract:
Public interface icachestorage { void Remove (string key); void Store (string key, Object data); T retrieve<t> (string key); }
In the Productservice class, we can replace the HttpContext-based cache instance with:
publi C class Productservice {public Productservice (iproductrepository productrepository,icachestorage cacheStroage) {this.productrepository = productrepository; This.cachestroage = Cachestroage; } private Iproductrepository productrepository; Private Icachestorage cachestroage; Public ilist<product> Getallproductsin (int. categoryId) {ilist<product> products; String Storagekey = String. Format ("products_in_category_id_{0}", categoryId); Products = cachestroage.retrieve<list<product>> (Storagekey); if (products = = null) {products = Productrepository.getallproductsin (categoryId); Cachestroage.store (Storagekey, products); } return products; } }
and the specific cache class we can inherit from Icachestorage to achieve:
public class Httpcacheadapterstorage:icachestorage {public void Remove (string key) { if ( Httpcontext.current.cache[key]! = null) HttpContext.Current.Cache.Remove (key); } public void Store (string key, Object data) { if (Httpcontext.current.cache[key]! = null) HttpContext.Current.Cache.Remove (key); HttpContext.Current.Cache.Insert (Key,data); } Public T Retrieve<t> (string key) { if (httpcontext.current.cache[key]!=null) return (T) Httpcontext.current.cache[key]; return default (T); } }
Now, looking back, we've solved the problems that have been enumerated, making the code easier to test, easier to read, and easier to understand.
The following is a UML diagram of the adapter (adapter) mode:
As you can see, the customer has a reference to the abstract (Target). Here, the abstraction is the Icachestorage interface. Adapter is an implementation of the target interface, which simply delegates the operation method to the Adaptee class, where the adapter class refers to our Httpcachestorage class, The Adaptee class, however, refers to the specific operation method provided by HttpContext.Current.Cache.
The specific description is as follows:
So, when we switch to memcached, or Ms Velocity, we just need to create a adapter and let the Productservice class interact with the cache storage provider through the common Icachestorage interface.
From here we know:
The adapter mode is very simple, and its only function is to allow classes with incompatible interfaces to work together.
Because the adapter mode is not the only pattern that can help with caching data, the following chapters will look at how the proxy design pattern can help solve the caching problem.
Here we have one last question that is not solved, that is, in the current design, in order to use the Productservice class, you always have to provide a icachestorage implementation for the constructor, but what if you do not want to cache the data? One approach is to provide a null reference, but this means that you need to check for an empty icachestorage implementation to mess up the code, and the better way to do this is to use the nullobject pattern to handle this particular case.
the null object pattern, which is sometimes referred to as a special case pattern, is also a very simple pattern. This pattern is useful when you do not want to specify or not specify a valid instance of a class and do not want to pass null references everywhere. The purpose of the null object is to replace the null reference and implement the same interface but with no behavior .
If you do not want the data to be cached in the Productservice class, the Null object pattern can be useful:
public class Nullobjectcache:icachestorage {public void Remove (string key) { } public void Store (string key, Object data) { } public T retrieve<t> (string key) { return default (T); } }
This way, when we request to cache the data, it does nothing and always returns a null value to Productservice, ensuring that no data is cached.
Finally, summarize:
Three design mode groupings.
Dependency Injection principle.
Adapter mode specific application.
The null object pattern is used to handle empty objects.