The of Java design pattern -- factory pattern

Posted by TecBrat on Fri, 19 Nov 2021 13:35:28 +0100

1. What is the factory model

Define an interface for creating an object,but let subclasses decide which class toinstantiate.Factory Method lets a class defer instantiation to subclasses.

Define an interface for creating objects, and let its subclasses decide which factory class to instantiate. The factory pattern delays the creation process to the subclasses.

Speaking: it provides an interface for creating objects and shields the process of creating objects, so as to achieve the purpose of flexibility.

2. Plant pattern classification

Generally, the factory mode is divided into three categories:

① . Simple Factory mode

② Factory Method

③ . Abstract Factory pattern

These three patterns are abstracted from top to bottom and are more general.

It should be noted that in the book design patterns, GOF divides factory patterns into two categories: Factory Method Pattern and Abstract Factory pattern, and regards Simple Factory pattern as a special case of factory method pattern, which are classified into one category.

Let's introduce these three factory modes respectively.

2.1 Simple Factory

For example, there is a requirement:

Select different parsers for parsing according to different imported files (docx,xlsx,pptx).

The simple factory has three core objects:

1. Factory: the core of the simple factory pattern, which is responsible for implementing the internal logic of creating all instances. The method of creating product class of factory class can be directly called by the outside world to create the required product object.

2. Abstract product: the parent class of all objects created by the simple factory pattern, which is responsible for describing the common interface shared by all instances.

3. Specific product: it is the creation target of the simple factory pattern. All created objects are instances of a specific class playing this role.

① , abstract parser

public interface IOfficeParser {
    void parse();
}

② . specific parser (docx,xlsx,pptx)

public class WordParser implements IOfficeParser{
    private String filePath;
    public WordParser(String filePath){
        this.filePath = filePath;
    }
    @Override
    public void parse() {
        System.out.println("analysis docx file");
    }
}
public class ExcelParser implements IOfficeParser{
    private String filePath;
    public ExcelParser(String filePath){
        this.filePath = filePath;
    }
    @Override
    public void parse() {
        System.out.println("analysis xlsx file");
    }
}
public class PptParser implements IOfficeParser {
    private String filePath;
    public PptParser(String filePath){
        this.filePath = filePath;
    }
    @Override
    public void parse() {
        System.out.println("analysis pptx file");
    }
}

③ Factory for constructing parser

public class OfficeParserFactory {

    public static IOfficeParser getParser(String filePath) throws Exception {
        String fileExtension = getFileExtension(filePath);
        IOfficeParser parser = null;
        if("docx".equalsIgnoreCase(fileExtension)){
            parser = new WordParser(filePath);
        }else if("xlsx".equalsIgnoreCase(fileExtension)){
            parser = new ExcelParser(filePath);
        }else if("pptx".equalsIgnoreCase(fileExtension)){
            parser = new PptParser(filePath);
        }else{
            throw new Exception("file is not supported:"+fileExtension);
        }
        return parser;
    }

    private static String getFileExtension(String filePath){
        // Resolve the file name to obtain the file extension, such as document. Docx, and return docx
        String fileExtension = filePath.substring(filePath.lastIndexOf(".")+1);
        return fileExtension;
    }
}

④ . test class

public class SimpleFactoryTest {

    public static void main(String[] args) throws Exception {
        String filePath = "file.docx";
        IOfficeParser parser = OfficeParserFactory.getParser(filePath);
        parser.parse();

        String filePath1 = "form.xlsx";
        IOfficeParser parser1 = OfficeParserFactory.getParser(filePath1);
        parser1.parse();
    }
}

⑤ . summary

This is a simple factory. The client avoids the responsibility of directly creating the parser. It only needs to call the factory class to parse.

The simple factory mode can be analyzed from the opening and closing principle (open to extension and closed to modification): when adding a file parsing, such as the doc format of the old version. At this time, you only need to add a parser class, and the client (understood as the test class and the caller) does not need to be changed. Then, you can add an else if branch in the factory class OfficeParserFactory.

At this time, some students may ask that if the OfficeParserFactory class is modified, doesn't it violate the opening and closing principle? However, as long as new parsers are not added frequently and the OfficeParserFactory class is modified occasionally, it is slightly inconsistent with the opening and closing principle and is acceptable.

It looks perfect. Careful students may ask that all parsing class objects are created in the OfficeParserFactory class. Suppose that a parsing class, such as doc, creates a parser object, which is not a simple new, but also includes some other operations. At this time, do you write all these codes into OfficeParserFactory? Is there a more elegant way to write it?

Yes, the factory mode to be introduced below.

2.2 Factory Method

To solve the above problem, we can create a factory for the factory class, that is, the factory of the factory, to create the factory class object.

① Create a factory for each specific parser

public class ExcelParserFactory implements IOfficeParserFactory {

    @Override
    public IOfficeParser createParser() {
        // TODO performs some operations to create objects
        return new ExcelParser();
    }
}

② , create parser factory

public class OfficeParserFactory {

    public static IOfficeParser getParser(String filePath) throws Exception {
        String fileExtension = getFileExtension(filePath);
        IOfficeParserFactory parserFactory = OfficeParserFactoryMap.getOfficeParseFactory(fileExtension);
        if(parserFactory == null){
            throw new Exception("file is not supported:"+fileExtension);
        }
        IOfficeParser parser = parserFactory.createParser();
        return parser;
    }

    private static String getFileExtension(String filePath){
        // Resolve the file name to obtain the file extension, such as document. Docx, and return docx
        String fileExtension = filePath.substring(filePath.lastIndexOf(".")+1);
        return fileExtension;
    }
}

③ Create factory class of parser factory

public class OfficeParserFactoryMap {
    private static final Map<String, IOfficeParserFactory> parserFactoryCached = new HashMap<>();
    static {
        parserFactoryCached.put("docx",new WordParserFactory());
        parserFactoryCached.put("xlxs",new ExcelParserFactory());
        parserFactoryCached.put("pptx",new PptParserFactory());
    }
    public static IOfficeParserFactory getOfficeParseFactory(String type){
        if(type == null || type.isEmpty()){
            return null;
        }
        return parserFactoryCached.get(type.toLowerCase());
    }


}

④ . test class

public class FactoryTest {
    public static void main(String[] args) throws Exception {
        String filePath = "file.docx";
        IOfficeParser parser = OfficeParserFactory.getParser(filePath);
        parser.parse();
    }
}

⑤ . summary

In the factory mode, if we want to add new file parsing, such as mdb format (office access suite), we only need to create a new parser class and parserFactory class, and add the new parserFactory class to the map in the OfficeParserFactoryMap class. The code changes are very few and basically comply with the opening and closing principle.

However, we can see that many factory classes have been added to the factory mode, which will increase the reproducibility of the code. If each factory class only performs a simple new operation, there is no need to use this mode. Just use the simple factory mode directly.

2.3 Abstract Factory

This mode is quite special and there are not many usage scenarios. Just have a brief understanding.

We know that doc and docx are suffixes of office word documents. Similarly, xls and xlsx are suffixes of office Excel tables, as well as PPT and pptx. doc/xlx/ppt are the file suffixes of the old version of office, which are binary and have something in common when parsing, while docx/xlsx/pptx is the file suffix of the new version of office, which is composed of ooxml structure. One group is the old office and the other is the new office.

If we still use the factory mode to implement, we should write a factory class for each type. Too many classes will be difficult to maintain. How to solve it?

The abstract factory pattern is born for this special scenario. We can let a factory copy and create multiple objects of different types, instead of just creating a parser object.

The specific code is as follows:

public interface IOfficeParserFactory {

    IOfficeParser createParser();

    IOldOfficeParser createOldParser();
}
public class ExcelParserFactory implements IOfficeParserFactory {

    @Override
    public IOfficeParser createParser() {
        return new ExcelParser();
    }

    @Override
    public IOldOfficeParser createOldParser() {
        return new DocParser();
    }
}

3. Difference between simple factory and factory method

Simple factory: put the logic of creating different objects in one factory class.

Factory method: put the logic of creating different objects in different factory classes. First, use the factory class of a factory class to get a factory, and create objects in a factory.

In this way, the difference is obvious. If the logic of creating objects is complex and various initialization operations need to be performed, the factory method can be used to split the complex creation logic into multiple factory classes; The logic of creating objects is very simple. There is no need to create multiple factory classes. You can use simple factories directly.

4. Role of factory model

Encapsulation change: the creation logic may change. After encapsulation into a factory class, the change of creation logic is transparent to the caller.

Code reuse: create code that can be reused after being extracted into an independent factory class.

Isolate complexity: encapsulates complex creation logic, and callers do not need to know how to create objects.

Control complexity: separate the creation code to make the original function or class responsibilities more single and the code more concise.

After reading, you know why it's okay. Don't use the factory mode, because it's so easy to use, you'll love it.