Creating patterns for detailed explanation of design patterns — single example, prototype and factory

Time:2022-5-3

I believe that as a program development, we have more or less contacted and even used design patterns, and we are even familiar with the concepts of some design patterns. However, whether we have used these patterns in the actual development project may be relatively few or even none. Some design patterns are indeed more practical in architecture, which is partly why. However, at least several common design patterns still need to be understood. This paper introduces several common design patterns, hoping that readers can gain something from them and apply what they have learned.

What is a design pattern

Design pattern is the summary of code development experience of predecessors and a series of routines to solve specific problems. It is not a syntax specification, but a set of code to improveReusability, maintainability, readability, robustness and securitySolutions.

Suppose there is an empty room, we should put something in it day after day. Of course, the simplest way is to throw these things directly, but over time, you will find it difficult to find what you want from this house, and it is not easy to adjust the position of some things. It may be a better choice for us in the maintenance stage, but it will bring us better benefits in the cabinet. The rule of using these cabinets to store things may be a model.

In 1994, Erich gamma, Richard helm, Ralph Johnson and John vlisides jointly published a book called design patterns – Elements of reusable object-oriented software, which first mentioned the concept of design patterns in software development.
Four authors collectivelyGOF (gang of four)。 The design patterns they put forward are mainly based on the followingobject-orientedDesign principles.

  • Programming interfaces rather than implementations — Dependency Inversion Principle
  • Give priority to object composition rather than inheritance – the principle of composite reuse.

Six principles of design mode:

  1. Single responsibility principle. Clear responsibilities
  2. Liskov Substitution Principle – where a base class is used, its subclasses can be used transparently
  3. Law of Demeter – an object should have the least understanding of other objects, that is, high aggregation and low coupling
  4. Dependency Inversion Principle — it depends on abstraction rather than details
  5. Interface aggregation principle – the client should not rely on the interface it does not need; The dependence of one class on another should be based on the smallest interface;
  6. Open closed principle – for extension development, close for modification

Create mode

Singleton pattern

Singleton mode must be familiar to everyone. This is a very common design mode and the simplest design mode. It provides the best way to create objects. This pattern involves a single class that is responsible for creating its own objects whileMake sure there is only a single objectIs created. This class provides a way to access its unique object, which can be accessed directly without instantiating the object of this class.

Examples using singleton mode: Net core dependency injection singleton in the lifecycle. The service whose life cycle is singleton is globally unique, and each call is the same service

There are two ways to write singleton mode,LazyandHungry Han style

Lazy type, as the name suggests, is lazy. It doesn’t create objects at the beginning. It doesn’t come to mind to create objects until it is used. The writing method is as follows:

//Lazy single example writing
public class SingletonPattern
{
    private static SingletonPattern _singletonInstance;
    //Constructor privatization is the key
    private SingletonPattern()
    {
 
    }
    //Double inspection to improve performance
    public static SingletonPattern GetInstance(bool useLock = true)
        {
         if (_singletonInstance is null)
            {
                lock (singleton_lock)
                {
                    if (_singletonInstance is null)
                    {
                        _singletonInstance = new SingletonPattern();
                    }
                }
            }
            return _singletonInstance;
       }
}

Note: the lazy type should consider multi thread safety. Here, double check lock is used to ensure thread safety and improve performance

Hungry Han style is relatively simple. You only need to judge whether it has been created or not. The writing method is as follows:

//Hungry Han style single case writing
public class SingletonPattern
{
    private static SingletonPattern _singletonInstance = new SingletonPattern();
    //Constructor privatization is the key
    private SingletonPattern()
    {
 
    }
 
    public static SingletonPattern GetInstance(bool useLock = true)
        {          
            if (_singletonInstance is null)
            {
                _singletonInstance = new SingletonPattern();
            }            
            return _singletonInstance;
       }
}

Applicable scenarios of this mode:

  • A global class, which is frequently created and destroyed, such as service class, tool class, etc
  • When it is necessary to control the number of instances and save system resources

Prototype pattern

Prototype pattern is used to create duplicate objects while ensuring performance. This type of design pattern is a creation pattern, which provides the best way to create objects. This pattern implements a prototype interface that is used to create a clone of the current object.When creating objects directly, the cost is relatively highThis mode is adopted when. For example, an object needs to be created after a costly database operation. We can cache the object, return its clone on the next request, and update the database when necessary, so as to reduce database calls.

The difficulty of prototype pattern lies in the cloning of objects. If the objects are complex and there are many nested attribute objects, it will be troublesome to implement the cloning method by yourself. Fortunately, at present, mainstream languages support deep cloning of objects, such as serializable in Java, clonedeep in JavaScript, etc. deep cloning can also be realized through serialization and deserialization. Therefore, languages that support serialization and deserialization can realize deep cloning in this way.

The implementation method of the prototype is not different from that of the single instance. The key point is that the returned instance is the cloned object

public class PrototypePattern
   {
       private static PrototypePattern _protetypeInstance = new PrototypePattern();
 
       private PrototypePattern()
       {
 
       }
 
       public static PrototypePattern GetInstance()
       {
           //The focus is on returning cloned objects
           PrototypePattern clone = GetDeepCloneObj();
           return clone;
       }
   }

Applicable scenario of prototype:

  • Object creation is complex and consumes a lot of resources, and similar objects need to be created repeatedly

Factory pattern

Factory pattern is divided into factory method and abstract factory in GoF design pattern. In fact, the classification of simple factory, factory method and abstract factory is more common.

Sinple factory pattern

Simple factory mainly isolates users and products. When users need to use products, they directly request from the factory without knowing the realization of specific products. That is to say, the dependency inversion principle!

简单工厂

As can be seen from the above figure, to create a product, users do not need to know the specific implementation of the product, but only need to know the factory where the product is created. The implementation code is as follows:

public class SimpleFactoryPattern
    {
        public static IRunner CreateRunner(PatternEnum pattern)
        {
            switch (pattern)
            {
                case PatternEnum.Singleton:
                    return new SingletonRunner();
                case PatternEnum.Prototype:
                    return new PrototypeRunner();
                case PatternEnum.Factory_Method:
                    return new FactoryMethodRunner();
                case PatternEnum.Abstract_Factory:
                    return new AbstractFactoryRunner();
                default:
                    return null;
            }
        }
    }
 
//Use
class Program
  {
        static void Main(string[] args)
        {
            var pattern = PatternEnum.Abstract_Factory;
            var prototype = SimpleFactoryPattern.CreateRunner(pattern);
            prototype.Run();
 
            Console.ReadKey();
        }
  }

Simple factory is suitable for relatively simple situations, which canMask object creation details, it conforms to the opening and closing principle for the object. However, the factory does not comply with the opening and closing principle, because when an object needs to be added, the factory content needs to be modified. In addition, when too many objects need to be generated or often need to be modified, this pattern is not enough.

At this time, the factory method needs to be used

Factory method pattern

工厂方法

As shown in the figure above, the factory method is different from the simple factory in that the specific factory is also shielded. Users do not need to know what factory to use, but just call the method of the abstract factory. The implementation is as follows:

//Abstract factory
 public abstract class MounseFactoryMethod
   {
       public abstract IMouse CreateMouse();
   }
   //Concrete realization factory
   public class DellMouseFactory : MounseFactoryMethod
   {
       public override IMouse CreateMouse()
       {
           return new DellMouse();
       }
   }
   //Concrete realization factory
   public class HpMouseFactory : MounseFactoryMethod
   {
       public override IMouse CreateMouse()
       {
           return new HpMouse();
       }
   }
   //When there are too many specific implementation factories, you can combine simple factories and use simple factories to create specific factories
   //You can also use reflection to create factories
   public class MouseFactory
   {
       public static MounseFactoryMethod CreateMouseFactory(BrandEnum brand)
       {
           switch (brand)
           {
               case BrandEnum.Dell:
                   return new DellMouseFactory();
               case BrandEnum.Hp:
                   return new HpMouseFactory();
               default:
                   return null;
           }
       }
   }
 
   //Use
   class Program
 {
       static void Main(string[] args)
       {                       
           var mouseFactory = MouseFactory.CreateMouseFactory(BrandEnum.Dell);
           var mouse = mouseFactory.CreateMouse();
           mouse.Click();        
       }
 }

The factory method conforms to the opening and closing principle, when there is a new product, you only need to add a new factory without changing the existing factory code.
In the above code, we abstract a mouse production factory, mounsefactorymethod, and two specific production factories, dellmousefactory and hpmousefactory. In addition, a simple factory mousefactory is used to select the specific factory to use.

Factory method is common in practical useThere are many kinds of objects to be created and they are frequently added or deletedThe factory method is a good choice.

Abstract factory

First, understand the following product family concepts:

产品族

As shown in the figure above, a product with the same characteristics is called aProduct grade, different products on the same product platform are called oneProduct family

Abstract factories are used to handle more complex products. For example, the mounsefactorymethod in the above factory method is specially used to produce mouse, while dellmousefactory and hpmousefactory are used to produce Dell mouse and HP mouse respectively. They are all products of the same product level. When we need to produce not only a mouse but also a keyboard, a mounsefactorymethod alone can no longer meet the production needs. At this time, the factory produces not only a single product, but a product family. The class diagram is as follows:

抽象工厂

In the figure above, we define a computerfactory, which can produce more abundant products (mouse and keyboard). Dell and HP have independent factories to produce their own mouse and keyboard respectively.

Factories that produce product families like the one above are called abstract factories.

The difference between abstract factory and factory method is that factory method can only produce a single product, that is, there is only one product interface, while the products of abstract factory may come from different interfaces.
The implementation code is as follows:

public abstract class ComputerAbstractFactory
  {
      public abstract IMouse CreateMouse();
      public abstract IKeyboard CreateKeyboard();
  }
 
  public class DellAbstractFactory : ComputerAbstractFactory
  {
      public override IKeyboard CreateKeyboard()
      {
          return new DellKeyboard();
      }
 
      public override IMouse CreateMouse()
      {
          return new DellMouse();
      }
  }
 
  public class HpAbstractFactory : ComputerAbstractFactory
  {
      public override IKeyboard CreateKeyboard()
      {
          return new HpKeyboard();
      }
 
      public override IMouse CreateMouse()
      {
          return new HpMouse();
      }
  }
 
  //Use
  class Program
{
      static void Main(string[] args)
      {                                 
          Console. Writeline ("create a product using the abstract factory dellabstractfactory");
          var dellFactory = new DellAbstractFactory();
          dellFactory.CreateKeyboard().Click();
          dellFactory.CreateMouse().Click();
 
          Console. Writeline ("create a product using the abstract factory hpabstractfactory");
          var hpFactory = new HpAbstractFactory();
          hpFactory.CreateKeyboard().Click();
          hpFactory.CreateMouse().Click();            
      }
}

Plant model summary

  1. Simple factories do not comply with the opening and closing principle, and are only used in the case of few product types and infrequent modifications
  2. The factory method complies with the opening and closing principle, but only applies to the case of a single product grade
  3. The abstract factory conforms to the opening and closing principle and is applicable to the production of product families