Abstract Factory

The Abstract Factory Pattern usually defines the interfaces of a collection of factory methods, without specifying concrete products. This allows an entire factory to be replaceable, in order to produce different products following the same production outline:

Abstract Factory

The details of the products (components) are omitted from the diagram, but do notice that these products belong to two parallel families: ExperimentalRocket and FreightRocket.

Different from the Factory Method Pattern, the Abstract Factory Pattern extracts another part called client that take cares of shaping the outline of the building process. This makes the factory part focused more on producing each component.

Participants

The participants of a typical Abstract Factory Pattern implementation include the following:

  • Abstract factory: RocketFactory

Defines the industrial standards of a factory which provide interfaces for manufacturing components or complex products.

  • Concrete factory: ExperimentalRocketFactory, FreightRocketFactory

Implements the interfaces defined by the abstract factory and builds concrete products.

  • Abstract products: Rocket, Payload, Stage[]

Define the interfaces of the products the factories are going to build.

  • Concrete products: ExperimentalRocket/FreightRocket, ExperimentalPayload/Satellite, and so on.

Presents actual products that are manufactured by a concrete factory.

  • Client:

Arranges the production process across factories (only if these factories conform to industrial standards).

Pattern scope

Abstract Factory Pattern makes the abstraction on top of different concrete factories. At the scope of a single factory or a single branch of factories, it just works like the Factory Method Pattern. However, the highlight of this pattern is to make a whole family of products interchangeable. A good example could be components of themes for a UI implementation.

Implementation

In the Abstract Factory Pattern, it is the client interacting with a concrete factory for building integral products. However, the concrete class of products is decoupled from the client during design time, while the client cares only about what a factory and its products look like instead of what exactly they are.

Let's start by simplifying related classes to interfaces:

interface Payload { 
  weight: number; 
} 
 
interface Stage { 
  engines: Engine[]; 
} 
 
interface Rocket { 
  payload: Payload; 
  stages: Stage[]; 
} 

And of course the abstract factory itself is:

interface RocketFactory { 
  createRocket(): Rocket; 
  createPayload(): Payload; 
  createStages(): Stage[]; 
} 

The building steps are abstracted from the factory and put into the client, but we still need to implement it anyway:

class Client { 
  buildRocket(factory: RocketFactory): Rocket { 
    let rocket = factory.createRocket(); 
     
    rocket.payload = factory.createPayload(); 
    rocket.stages = factory.createStages(); 
     
    return rocket; 
  } 
} 

Now we have the same issue we previously had when we implemented the Factory Method Pattern. As different concrete factories build different rockets, the class of the product changes. However, now we have generics to the rescue.

First, we need a RocketFactory  interface with a generic type parameter that describes a concrete rocket class:

interface RocketFactory<T extends Rocket> { 
  createRocket(): T; 
  createPayload(): Payload; 
  createStages(): Stage[]; 
} 

And second, update the buildRocket method of the client to support generic factories:

  buildRocket<T extends Rocket>( 
    factory: RocketFactory<T> 
  ): T { } 

Thus, with the help of the type system, we will have rocket type inferred based on the type of a concrete factory, starting with ExperimentalRocket and ExperimentalRocketFactory:

class ExperimentalRocket implements Rocket { } 
 
class ExperimentalRocketFactory 
implements RocketFactory<ExperimentalRocket> { } 

If we call the buildRocket method of a client with an instance of ExperimentalRocketFactory, the return type will automatically be ExperimentalRocket:

let client = new Client(); 
let factory = new ExperimentalRocketFactory(); 
let rocket = client.buildRocket(factory); 

Before we can complete the implementation of the ExperimentalRocketFactory object, we need to define concrete classes for the products of the family:

class ExperimentalPayload implements Payload { 
  weight: number; 
} 
 
class ExperimentalRocketStage implements Stage { 
  engines: Engine[]; 
} 
 
class ExperimentalRocket implements Rocket { 
  payload: ExperimentalPayload; 
  stages: [ExperimentalRocketStage]; 
} 

Note

Trivial initializations of payload and stage are omitted for more compact content. The same kinds of omission may be applied if they are not necessary for this book.

And now we may define the factory methods of this concrete factory class:

class ExperimentalRocketFactory 
implements RocketFactory<ExperimentalRocket> { 
  createRocket(): ExperimentalRocket { 
    return new ExperimentalRocket(); 
  } 
   
  createPayload(): ExperimentalPayload { 
    return new ExperimentalPayload(); 
  } 
   
  createStages(): [ExperimentalRocketStage] { 
    return [new ExperimentalRocketStage()]; 
  } 
} 

Let's move on to another concrete factory that builds a freight rocket and products of its family, starting with the rocket components:

class Satellite implements Payload { 
  constructor( 
    public id: number, 
    public weight: number 
  ) { } 
} 
 
class FreightRocketFirstStage implements Stage { 
  engines: Engine[]; 
} 
 
class FreightRocketSecondStage implements Stage { 
  engines: Engine[]; 
} 
 
type FreightRocketStages = 
  [FreightRocketFirstStage, FreightRocketSecondStage]; 

Continue with the rocket itself:

class FreightRocket implements Rocket { 
  payload: Satellite; 
  stages: FreightRocketStages; 
} 

With the structures or classes of the freight rocket family defined, we are ready to implement its factory:

class FreightRocketFactory 
implements RocketFactory<FreightRocket> { 
  nextSatelliteId = 0; 
   
  createRocket(): FreightRocket { 
    return new FreightRocket(); 
  } 
   
  createPayload(): Satellite { 
    return new Satellite(this.nextSatelliteId++, 100); 
  } 
   
  createStages(): FreightRocketStages { 
    return [ 
      new FreightRocketFirstStage(), 
      new FreightRocketSecondStage() 
    ]; 
  } 
} 

Now we once again have two families of rockets and their factories, and we can use the same client to build different rockets by passing different factories:

let client = new Client(); 
 
let experimentalRocketFactory = new ExperimentalRocketFactory(); 
let freightRocketFactory = new FreightRocketFactory(); 
 
let experimentalRocket = 
  client.buildRocket(experimentalRocketFactory); 
 
let freightRocket = client.buildRocket(freightRocketFactory); 

Consequences

The Abstract Factory Pattern makes it easy and smooth to change the entire family of products. This is the direct benefit brought by the factory level abstraction. As a consequence, it also brings other benefits, as well as some disadvantages at the same time.

On the one hand, it provides better compatibility within the products in a specific family. As the products built by a single factory are usually meant to work together, we can assume that they tend to cooperate more easily.

But on the other hand, it relies on a common outline of the building process, although for a well-abstracted building process, this won't always be an issue. We can also parameterize factory methods on both concrete factories and the client to make the process more flexible.

Of course, an abstract factory does not have to be a pure interface or an abstract class with no methods implemented. An implementation in practice should be decided based on detailed context.

Although the Abstract Factory Pattern and Factory Method Pattern have abstractions of different levels, what they encapsulate are similar. For building a product with multiple components, the factories split the products into components to gain flexibility. However, a fixed family of products and their internal components may not always satisfy the requirements, and thus we may consider the Builder Pattern as another option.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.39.60