• Abstract Factory pattern
  • Optimized abstract factory
  • Asynchronous factory

Before we look at the abstract factory pattern, let’s review the previous simple factory and factory method patterns. The job of a simple factory is very simple: Construct an entity type and return the instance as an abstract type; The factory method pattern further abstracts an abstract creator and an abstract product type. The actual execution process is that the concrete factory creates the concrete product type. Both the concrete factory and the concrete product type can be abstracted into the previously defined abstract creator and abstract product type. In this mode, even if facing a very large type system with complex family relationships, the client program can still obtain product instances satisfying certain abstract types based on the abstract creator during operation.

But in many scenarios, you need to create products that don’t just inherit from a single abstract type; they are themselves multiple types that have dependencies but are not homologous.

Abstract Factory pattern

The abstract factory pattern addresses this situation by producing a series of types with related dependencies. Provide an interface for creating families of related or dependent objects. — Design Patterns: Elements of Reusable Object-Oriented Software

Abstract factories can return interfaces to a series of related or interdependent objects. In addition, the abstract factory itself needs an interface that includes method definitions that return interfaces for those related objects.

The UML class diagram is as follows:

Abstract factory UML class diagram

Implementation code:

public interface IProductA { };
public interface IProductB { };

public interface IAbstractFactory
{
    IProductA CreateProductA();
    IProductB CreateProductB();
}

public class ProductA1 : IProductA { }
public class ProductA2 : IProductA { }
public class ProductB1 : IProductB { }
public class ProductB2 : IProductB { }

public class ConcreteFactory1 : IAbstractFactory
{
    public IProductA CreateProductA()
    {
        return new ProductA1();
    }

    public IProductB CreateProductB()
    {
        return new ProductB1();
    }
}

public class ConcreteFactory2 : IAbstractFactory
{
    public IProductA CreateProductA()
    {
        return new ProductA2();
    }

    public IProductB CreateProductB()
    {
        returnnew ProductB2(); }}Copy the code

Call:

[Test]
public void AbstractFactoryTest()
{
    IAbstractFactory factory = new ConcreteFactory1();
    IProductA productA = factory.CreateProductA();
    IProductB productB = factory.CreateProductB();

    Assert.AreEqual(typeof(ProductA1), productA.GetType());
    Assert.AreEqual(typeof(ProductB1), productB.GetType());
}
Copy the code

A problem can be seen from the calling side of the code. As with the previous factory method pattern, the Client is coupled to a specific factory, so dependency injection can be used here to “solve” the problem, pushing the handling of this step up to the calling side.

Optimize the abstract factory pattern

This code, based on the classic abstract factory pattern, can also be optimized:

  • First, the factory-specific code is too repetitive,
  • In addition, a specific factory is tied to a specific product, and if you need to change the way the product portfolio is combined, you have to either modify the specific factory code or create a new factory, both of which are major changes.
  • A more serious problem is that if new abstract products are to be added, changes from the abstract factory interface to every concrete implementation will need to be made, almost completely.

For the optimization solution of the first point, AbstractFactoryBase can be extracted to implement the creation method of abstract interfaces. If the Create method of a factory is special, the Create method of the base class can be overridden. But base class factory does not know how to create a specific plant product portfolio, which can be instantiated in the specific plant transfer of an abstract and concrete products mapping (can be the form of a dictionary), let the base class according to the mapping relationship to work, or can be based on a ready-made IOC container to configure the mapping. This will solve the second and third problems. The implementation code is as follows:

public interface IAbstractFactoryWithMapper
{
    T Create<T>() where T : class;
}

public abstract class AbstractFactoryBase : IAbstractFactoryWithMapper
{
    protected IDictionary<Type, Type> mapper;
    public AbstractFactoryBase(IDictionary<Type, Type> mapper)
    {
        this.mapper = mapper;
    }

    public virtual T Create<T>() where T : class
    {
        if(mapper == null || mapper.Count == 0 || ! mapper.ContainsKey(typeof(T))) { throw new ArgumentNullException(); } Type targetType = mapper[typeof(T)];return (T)Activator.CreateInstance(targetType);
    }
}

public class ConcreteFactory : AbstractFactoryBase
{
    public ConcreteFactory(IDictionary<Type, Type> mapper) : base(mapper) { }
}
Copy the code

Call:

[Test]
public void AbstractFactoryWithMapperTest()
{
    IDictionary<Type, Type> dictionary = new Dictionary<Type, Type>();
    dictionary.Add(typeof(IProductA), typeof(ProductA1));
    dictionary.Add(typeof(IProductB), typeof(ProductB1));

    IAbstractFactoryWithMapper factory = new ConcreteFactory(dictionary);
    IProductA productA = factory.Create<IProductA>();
    IProductB productB = factory.Create<IProductB>();

    Assert.AreEqual(typeof(ProductA1), productA.GetType());
    Assert.AreEqual(typeof(ProductB1), productB.GetType());
}
Copy the code

Asynchronous factory

Sometimes the process of creating product instances is complicated, or involves the access of external resources such as network and database, which takes a long time. In this case, if the factory supports asynchronous invocation, the client can simply send a request to the factory, do something else, and come back when it receives notification that the factory has been created. Asynchronous factory implementation:

public interface IProduct { }; public interface IFactory { IProduct Create(); } public interface IFactoryWithNotifier : IFactory { void Create(Action<IProduct> callBack); } public class ConcreteProduct: IProduct {} public class ConcreteFactory: IFactoryWithNotifier {public IProduct Create() {returnnew ConcreteProduct(); } public void Create(Action<IProduct> callBack) {IProduct product = Create(); callBack(product); }} public class Subscriber {private IProduct product; public void SetProduct(IProduct product) { this.product = product; } public IProductGetProduct()
    {
        returnproduct; }}Copy the code

Call:

[Test]
public void AsyncFactoryTest()
{
    IFactoryWithNotifier factoryWithNotifier = new ConcreteFactory();
    Subscriber subscribe = new Subscriber();
    Action<IProduct> callback = new Action<IProduct>(subscribe.SetProduct);

    Assert.IsNull(subscribe.GetProduct());
    factoryWithNotifier.Create(callback);
    Assert.IsNotNull(subscribe.GetProduct());
}
Copy the code

Reference book: design patterns — implementation and extension of engineering based on C# by wang xiang