Glide source code taught me the usage of abstract factory pattern

Key points of text objectives

In daily development work, the most important understanding of the Interface for new children's shoes in the workplace is that it can be used for asynchronous communication and data callback. Today, I want to introduce today's topic the usage of abstract factory pattern in combination with the Interface usage found in Glide source code reading. Before introducing the abstract factory pattern, first introduce the common usage of the simple factory pattern. With the usage of the abstract factory pattern, analyze its advantages and disadvantages, and finally strengthen the application of this design pattern in combination with Glide source code.

Simple factory mode

Definition: a factory object determines which product class instance to create. It is suitable for creating a group of related classes, such as the following structural example, a base class for producing shapes. After being inherited by subclasses, you can produce circles, rectangles and triangles.

Product base class

//Draw shape
class Shape{
	void drawStroke(){
	   ...
	}
	void drawSolid(){
	   ...
	}
}

After subclass inheritance, it produces circle, rectangle and triangle respectively. Examples are as follows

//Draw a circle
class OvalShape extends Shape{
	void drawStroke(){
	   ...
	}
	void drawSolid(){
	   ...
	}
}

Production rectangle

//Draw rectangle
class RectShape extends Shape{
	void drawStroke(){
	   ...
	}
	void drawSolid(){
	   ...
	}
}

Production triangle

//Draw a triangle
class TriangleShape extends Shape{
	void drawStroke(){
	   ...
	}
	void drawSolid(){
	   ...
	}
}

In order to avoid paying attention to the creation process of drawing shape product objects, the following management classes are generally created, in which the product object to be created is determined by identifying the parameters passed into the factory mode.

Managed factory model
class class ShapeFactory{
    public static Shape createShape(String type){
		Shape shape = null;
		switch(type){
			case "oval":
				shape = new OvalShape();
				break;
			case "rect":
				shape = new RectShape();
				break;
			case "trianglle":
				shape = new TriangleShape();
				break;
		}
		return shape;
	}
}

Abstract factory pattern

Definition: only create an interface or abstract class for creating product objects, let subclasses implement the interface, and create corresponding subclasses according to actual use. The basic form of its structure is as follows

The interfaces for creating product objects are as follows

//Draw shape
interface Shape{
	void drawStroke();
	void drawSolid();
}

Implement the interface to form the object of creating production circle / production Rectangle / production triangle.

//Draw a circle
class OvalShape implements Shape{
	void drawStroke(){
	   ...
	}
	void drawSolid(){
	   ...
	}
}

//Draw rectangle
class RectShape implements Shape{
	void drawStroke(){
	   ...
	}
	void drawSolid(){
	   ...
	}
}

//Draw a triangle
class TriangleShape implements Shape{
	void drawStroke(){
	   ...
	}
	void drawSolid(){
	   ...
	}
}

As described below, the application of abstract factory does not need to list the creation process of all product objects

class ShapeFactory{
	private static Shape shape = null;
	//Create product object
    public static void createShape(Shape shape){
		this.shape = shape;
	}
	//Execution is not concerned
	public static void setDrawStroke(){
	    shape.drawStroke();
	}
}

//Creating circular product objects and execution methods
ShapeFactory.createShape(new OvalShape());
ShapeFactory.setDrawStroke();

//Create rectangular product object and execution method
ShapeFactory.createShape(new RectShape());
ShapeFactory.setDrawStroke();

Advantages and disadvantages of the two modes

projectSimple factory modeAbstract factory pattern
Application scenarioWhen fewer types of objects are createdWhen the product interface category of the created object is stable, it will not change
advantageThe outside world does not need to understand the process of object creation. The outside world only needs to output the parameters of the factory class1. Define the common product interface of subclasses without setting a factory class to manage the creation process of all subclasses. 2. Only the defined interface is exposed to the outside, and the internal implementation logic is not exposed, realizing code decoupling
shortcoming1. Poor expansibility. Adding subclasses also requires modifying the factory method and adding branch conditions, which violates the open and closed principle; 2. After the object type is increased, it is difficult to maintain the codeIf the production product interface changes, all subclasses must be adjusted

Application of abstract factory pattern in Glide source code

In the Glide source code, there is a piece of code in the DecodeJob class. The purpose is to determine whether to use the converted resources from the hard disk / read the converted resources from the hard disk / network request resources according to the task type. It adopts this abstract factory mode
The usage of the call is as follows

private volatile DataFetcherGenerator currentGenerator;
//2. Create task executor
currentGenerator = getNextGenerator();

//2. Create task executor
private DataFetcherGenerator getNextGenerator() {
  switch (stage) {
    case RESOURCE_CACHE://Read converted data from hard disk cache
      return new ResourceCacheGenerator(decodeHelper, this);
    case DATA_CACHE://Read unconverted raw data from hard disk cache
      return new DataCacheGenerator(decodeHelper, this);
    case SOURCE://Request to load data
      return new SourceGenerator(decodeHelper, this);
    case FINISHED:
      return null;
    default:
      throw new IllegalStateException("Unrecognized stage: " + stage);
  }
}

The abstract interfaces defined are as follows

interface DataFetcherGenerator {

  interface FetcherReadyCallback {

    void reschedule();

    void onDataFetcherReady(
        Key sourceKey,
        @Nullable Object data,
        DataFetcher<?> fetcher,
        DataSource dataSource,
        Key attemptedKey);

    void onDataFetcherFailed(
        Key attemptedKey, Exception e, DataFetcher<?> fetcher, DataSource dataSource);
  }
  boolean startNext();

  void cancel();
}

Take a look at the product class ResourceCacheGenerator that reads the resources converted from the hard disk. The implementation process is as follows

class ResourceCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> {

  ...

  ResourceCacheGenerator(DecodeHelper<?> helper, FetcherReadyCallback cb) {
    this.helper = helper;
    this.cb = cb;
  }

  // See TODO below.
  @SuppressWarnings("PMD.CollapsibleIfStatements")
  @Override
  public boolean startNext() {
      ...
      return started;
    } finally {
      GlideTrace.endSection();
    }
  }

  private boolean hasNextModelLoader() {
    return modelLoaderIndex < modelLoaders.size();
  }

  @Override
  public void cancel() {
    LoadData<?> local = loadData;
    if (local != null) {
      local.fetcher.cancel();
    }
  }

  @Override
  public void onDataReady(Object data) {
    cb.onDataFetcherReady(
        sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, currentKey);
  }

  @Override
  public void onLoadFailed(@NonNull Exception e) {
    cb.onDataFetcherFailed(currentKey, e, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE);
  }
}

The other two product categories are the same, so they are not listed here.

Learning experience

The abstract factory pattern is suitable for the situation that the product interface category is stable. Based on this pattern, the decoupling between logic and call can be realized, and only the interface is exposed to the caller. This mode does not need to set a factory class to be responsible for the creation process of all subclass product objects, and has good expansibility. But no way is perfect, as long as it is suitable for the needs of the project.

Keywords: Android

Added by IOAF on Mon, 13 Sep 2021 02:20:21 +0300