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
project | Simple factory mode | Abstract factory pattern |
---|---|---|
Application scenario | When fewer types of objects are created | When the product interface category of the created object is stable, it will not change |
advantage | The outside world does not need to understand the process of object creation. The outside world only needs to output the parameters of the factory class | 1. 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 |
shortcoming | 1. 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 code | If 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.