This article is excerpted from This is How Design Patterns should Be Learned

1. Product grade structure and product family

Before we look at abstract factories, we need to understand two concepts: product hierarchy and product family, as shown in the figure below.

In the figure above, there are square, circle and diamond shapes. Those with the same color and depth represent the same product family, and those with the same shape represent the same product grade structure. Life can also come from, for example, for instance, midea production of a variety of household appliances, as above, the darkest washing machine of square represents beauty, darkest circle represents the midea air-conditioning, color the deepest water heater of diamond represents beauty, belong to the darkest row beauty brand, the product family belong to midea. Look at the diamond on the far right, the darkest color is designated to represent the beauty of the water heater, then the second row of slightly lighter color of the diamond represents hisense water heater. Similarly, there are Gree washing machines, Gree air conditioners and Gree water heaters in the same product family.

In the picture below, the small house on the far left is considered to be a specific factory, including Midea factory, Hisense factory and Gree factory. Each brand of factory produces washing machines, air conditioners and water heaters.

Through the comparison of the two pictures above, I believe you have a very vivid understanding of the abstract factory.

General notation for abstract factory patterns

The following is a common way to write the abstract factory pattern.


public class Client {
    public static void main(String[] args) {
        IFactory factory = new ConcreteFactoryA();
        factory.makeProductA();
        factory.makeProductB();

        factory = new ConcreteFactoryB();
        factory.makeProductA();
        factory.makeProductB();
    }

    // Abstract factory class
    public interface IFactory {
        IProductA makeProductA(a);

        IProductB makeProductB(a);
    }

    // Product A is abstract
    public interface IProductA {
        void doA(a);
    }

    // Product B abstraction
    public interface IProductB {
        void doB(a);
    }

    // The specific product A of product family A
    static class ConcreteProductAWithFamilyA implements IProductA{
        public void doA(a) {
            System.out.println("The ProductA be part of FamilyA"); }}// Product B of product family A
    static class ConcreteProductBWithFamilyA implements IProductB{
        public void doB(a) {
            System.out.println("The ProductB be part of FamilyA"); }}// Product A of product family B
    static class ConcreteProductAWithFamilyB implements IProductA{
        public void doA(a) {
            System.out.println("The ProductA be part of FamilyB"); }}// Product B of product family B
    static class ConcreteProductBWithFamilyB implements IProductB{
        public void doB(a) {
            System.out.println("The ProductB be part of FamilyB"); }}// Specific factory class A
    static class ConcreteFactoryA implements IFactory{
        public IProductA makeProductA(a) {
            return new ConcreteProductAWithFamilyA();
        }

        public IProductB makeProductB(a) {
            return newConcreteProductBWithFamilyA(); }}// Specific factory class B
    static class ConcreteFactoryB implements IFactory{
        public IProductA makeProductA(a) {
            return new ConcreteProductAWithFamilyB();
        }

        public IProductB makeProductB(a) {
            return newConcreteProductBWithFamilyB(); }}}Copy the code

Support product extensions with abstract factory patterns

Let’s look at a specific business scenario and implement it in code. Take online courses as an example. Generally, there are certain standards for course development. Each course should provide not only the video recording of the course, but also the teacher’s classroom notes. It is equivalent to changing the current business to the same course, which is not only a course information, but also includes video recording, classroom notes, and even source code to form a complete course. Firstly, add two products in the product level: IVideo for recording and playing videos and INote for class notes. The code of the IVideo interface is as follows.


public interface IVideo {
    void record(a);
}

Copy the code

The code for the INote interface is as follows.


public interface INote {
    void edit(a);
}

Copy the code

Then create an abstract factory CourseFactory class.


/** * Abstract factory is the user's main entry point * One of the most widely used design patterns in Spring * easily extensible * Created by Tom */
public abstract class CourseFactory {

    public void init(a){
        System.out.println("Initialize basic data");
    }

    protected abstract INote createNote(a);

    protected abstract IVideo createVideo(a);

}

Copy the code

Next, create the Java product family. The code for the JavaVideo JavaVideo class is as follows.


public class JavaVideo implements IVideo {
    public void record(a) {
        System.out.println("Record Java Video"); }}Copy the code

Extended product-level Java class notes JavaNote class.


public class JavaNote implements INote {
    public void edit(a) {
        System.out.println("Writing Java Notes"); }}Copy the code

The specific factory that creates the Java product family, JavaCourseFactory.


public class JavaCourseFactory extends CourseFactory {

    public INote createNote(a) {
        super.init();
        return new JavaNote();
    }

    public IVideo createVideo(a) {
        super.init();
        return newJavaVideo(); }}Copy the code

The Python product family is then created, with the code for the Python video PythonVideo class as follows.


public class PythonVideo implements IVideo {
    public void record(a) {
        System.out.println("Record Python video"); }}Copy the code

Extended product-level Python class notes PythonNote.


public class PythonNote implements INote {
    public void edit(a) {
        System.out.println("Writing Python Notes"); }}Copy the code

PythonCourseFactory, the concrete factory to create the Python product family.


public class PythonCourseFactory implements CourseFactory {
    public INote createNote(a) {
        return new PythonNote();
    }
    public IVideo createVideo(a) {
        return newPythonVideo(); }}Copy the code

Finally, look at the client-side calling code.


public static void main(String[] args) {
    JavaCourseFactory factory = new JavaCourseFactory();
    factory.createNote().edit();
    factory.createVideo().record();
}

Copy the code

The above code completely describes the two product families of Java courses and Python courses, as well as the two product levels of video and notes. The abstract factory perfectly and clearly describes such a complex layer of relationships. However, I do not know if you have found that if you continue to expand the product level, the Source Source is also added to the course, the code from the abstract factory to the specific factory to adjust all, which is obviously not open and closed principle.

Refactoring the database connection pool using the abstract factory schema

Again, to demonstrate the JDBC operation case from the beginning of the class, we need to recreate the database connection for each operation. In fact, each creation is very costly in performance and business call time. We use the abstract factory pattern, where the database connection is pre-created and cached in a container so that it can be pulled in as soon as the business is called. Let’s look at the code. The code for the Pool abstract class is as follows.


/** * Custom connection POOL getInstance() returns a unique instance of the POOL. The constructor POOL () calls the loadDrivers() function; * Connection pool creates the createPool() function, loadDrivers() loads the driver * createPool() creates the connection pool, getConnection() returns a connection instance, * getConnection(long time) add time limit * freeConnection(Connection con) return con Connection instance to Connection pool, Getnum () returns the number of free connections * getnumActive() returns the number of connections currently in use * *@author Tom
 *
 */

public abstract class Pool {
   public String propertiesName = "connection-INF.properties";

   private static Pool instance = null; 	// Define a unique instance

   /** * Maximum number of connections */
   protected int maxConnect = 100; 		// Maximum number of connections

   /** ** Hold the number of connections */
   protected int normalConnect = 10; 	// Keep the number of connections

   /**
    * 驱动字符串
    */
   protected String driverName = null; 	// Driver string

   /** * driver class */
   protected Driver driver = null; 		// Drive variables


   /** * private constructor, does not allow external access to */
   protected Pool(a) {
      try
      {
         init();
         loadDrivers(driverName);
      }catch(Exception e) { e.printStackTrace(); }}/** * initializes all member variables read from the configuration file */
   private void init(a) throws IOException {
      InputStream is = Pool.class.getResourceAsStream(propertiesName);
      Properties p = new Properties();
      p.load(is);
      this.driverName = p.getProperty("driverName");
      this.maxConnect = Integer.parseInt(p.getProperty("maxConnect"));
      this.normalConnect = Integer.parseInt(p.getProperty("normalConnect"));
   }

   /** * Load and register all JDBC drivers *@paramDri receives the driver string */
   protected void loadDrivers(String dri) {
      String driverClassName = dri;
      try {
         driver = (Driver) Class.forName(driverClassName).newInstance();
         DriverManager.registerDriver(driver);
         System.out.println("JDBC driver registered successfully" + driverClassName);
      } catch (Exception e) {
         System.out.println("Unable to register JDBC driver :" + driverClassName + "Wrong,"+ e); }}/** * Create a connection pool */
   public abstract void createPool(a);

   /** ** (singleton mode) Returns an instance of the database connection Pool **@paramDriverName Database driver string *@return
    * @throws IOException
    * @throws ClassNotFoundException
    * @throws IllegalAccessException
    * @throws InstantiationException
    */
   public static synchronized Pool getInstance(a) throws IOException,
         InstantiationException, IllegalAccessException,
         ClassNotFoundException {

      if (instance == null) {
         instance = (Pool) Class.forName("org.e_book.sqlhelp.Pool").newInstance();
      }
      return instance;
   }

   /** * get an available connection, if not, create a connection that is less than the maximum connection limit *@return* /
   public abstract Connection getConnection(a);

   /** * get a connection, time limit *@paramTime Sets the duration of the connection in milliseconds *@return* /
   public abstract Connection getConnection(long time);

   /** * returns the connection object to the connection pool *@paramCon gets the connection object */
   public abstract void freeConnection(Connection con);

   /** * returns the number of connections currently free *@return* /
   public abstract int getnum(a);

   /** * returns the number of connections currently working *@return* /
   public abstract int getnumActive(a);

   /** * Close all connections and cancel driver registration (this method is a singleton method) */
   protected synchronized void release(a) {
      // Undo the driver
      try {
         DriverManager.deregisterDriver(driver);
         System.out.println("Revoke the JDBC driver" + driver.getClass().getName());
      } catch (SQLException e) {
         System.out
               .println("Cannot unregister JDBC driver :"+ driver.getClass().getName()); }}}Copy the code

DBConnectionPool The code for the database connection pool is as follows.


/** * Database connection pool management class *@author Tom
 *
 */
public final class DBConnectionPool extends Pool {
   private int checkedOut; 						// The number of connections in use
   /** * holds the resulting connection object container */
   private Vector<Connection> freeConnections = new Vector<Connection>(); 
												// Store the generated connection object container

   private String passWord = null; 				/ / password

   private String url = null; 					// Connection string

   private String userName = null; 				/ / user name

   private static int num = 0;					// Number of idle connections

   private static int numActive = 0;				// The number of connections currently available

   private static DBConnectionPool pool = null;	// Connection pool instance variable

   /** * Generate data connection pool *@return* /
   public static synchronized DBConnectionPool getInstance(a)
   {
      if(pool == null)
      {
         pool = new DBConnectionPool();
      }
      return pool;
   }

   /** * get an instance of the database connection pool */
   private DBConnectionPool(a) {
      try
      {
         init();
         for (int i = 0; i < normalConnect; i++) { 	// Initially normalConn a connection
            Connection c = newConnection();
            if(c ! =null) {
               freeConnections.addElement(c); 			// Add a connection object to the container
               num++; // Record total connections}}}catch(Exception e) { e.printStackTrace(); }}/** * initialize *@throws IOException
    */
   private void init(a) throws IOException
   {
      InputStream is = DBConnectionPool.class.getResourceAsStream(propertiesName);
      Properties p = new Properties();
      p.load(is);
      this.userName = p.getProperty("userName");
      this.passWord = p.getProperty("passWord");
      this.driverName = p.getProperty("driverName");
      this.url = p.getProperty("url");
      this.driverName = p.getProperty("driverName");
      this.maxConnect = Integer.parseInt(p.getProperty("maxConnect"));
      this.normalConnect = Integer.parseInt(p.getProperty("normalConnect"));
   }
   /** * This method can be used to release a connection object to the connection pool * if it is no longer in use@param con
    */
   public synchronized void freeConnection(Connection con) {
      freeConnections.addElement(con);
      num++;
      checkedOut--;
      numActive--;
      notifyAll(); / / unlock
   }

   /** * Create a new connection@return* /
   private Connection newConnection(a) {
      Connection con = null;
      try {
         if (userName == null) { // The user and password are empty
            con = DriverManager.getConnection(url);
         } else {
            con = DriverManager.getConnection(url, userName, passWord);
         }
         System.out.println("Connection pool creates a new connection");
      } catch (SQLException e) {
         System.out.println("Could not create a connection for this URL" + url);
         return null;
      }
      return con;
   }

   /** * returns the number of connections currently free *@return* /
   public int getnum(a) {
      return num;
   }

   /** * returns the number of connections currently available *@return* /
   public int getnumActive(a) {
      return numActive;
   }


   /** * (singleton mode) get an available connection *@return* /
   public synchronized Connection getConnection(a) {
      Connection con = null;
      if (freeConnections.size() > 0) { // There are idle connections
         num--;
         con = (Connection) freeConnections.firstElement();
         freeConnections.removeElementAt(0);
         try {
            if (con.isClosed()) {
               System.out.println("Delete an invalid connection from the connection pool"); con = getConnection(); }}catch (SQLException e) {
            System.out.println("Delete an invalid connection from the connection pool");
            con = getConnection();
         }
		// There are no idle connections and the current connection is less than the maximum allowed value. If the maximum value is 0, there is no limit
      } else if (maxConnect == 0 || checkedOut < maxConnect) { 
         con = newConnection();
      }
      if(con ! =null) { // The current number of connections increases by 1
         checkedOut++;
      }
      numActive++;
      return con;
   }

   /** * get a connection with a wait time limit of milliseconds *@paramTimeout Accepts the wait time in milliseconds *@return* /
   public synchronized Connection getConnection(long timeout) {
      long startTime = new Date().getTime();
      Connection con;
      while ((con = getConnection()) == null) {
         try {
            wait(timeout); // The thread waits
         } catch (InterruptedException e) {
         }
         if ((new Date().getTime() - startTime) >= timeout) {
            return null; // If time out, return}}return con;
   }

   /** * close all connections */
   public synchronized void release(a) {
      try {
         // Assign the current connection to the enumeration
         Enumeration allConnections = freeConnections.elements();
         // Close the connection pool using a loop
         while (allConnections.hasMoreElements()) {
            Returns the next element of the enumeration if at least one more element is available
            Connection con = (Connection) allConnections.nextElement();
            try {
               con.close();
               num--;
            } catch (SQLException e) {
               System.out.println("Cannot close connection in connection pool");
            }
         }
         freeConnections.removeAllElements();
         numActive = 0;
      } finally {
         super.release(); }}/** * Set up a connection pool */
   public void createPool(a) {

      pool = new DBConnectionPool();
      if(pool ! =null) {
         System.out.println("Connection pool created successfully");
      } else {
         System.out.println("Failed to create connection pool"); }}}Copy the code

Application of abstract factory pattern in Spring source code

In Spring, all factories are subclasses of BeanFactory. With the implementation of the BeanFactory, we can access beans from Spring’s container. Call the getBean() method, depending on the policy, to get the concrete object.


public interface BeanFactory {
	String FACTORY_BEAN_PREFIX = "&";

	Object getBean(String name) throws BeansException;

	<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;

	Object getBean(String name, Object... args) throws BeansException;

	<T> T getBean(Class<T> requiredType) throws BeansException;

	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

	boolean containsBean(String name);

	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, @NullableClass<? > typeToMatch) throws NoSuchBean 	DefinitionException;

	@NullableClass<? > getType(String name)throws NoSuchBeanDefinitionException;
	String[] getAliases(String name);

}

Copy the code

A subclass of the BeanFactory mainly ClassPathXmlApplicationContext, XmlWebApplicationContext, StaticWebApplicationContext, StaticPortletApplic AtionContext, GenericApplicationContext and Static ApplicationContext. In Spring, DefaultListableBeanFactory achieved all public logic of the factory.

Tom play architecture: 30 real cases of design mode, challenge annual salary 60W is not a dream

This article is “Tom play structure” original, reproduced please indicate the source. Technology is to share, I share my happiness! If this article is helpful to you, welcome to follow and like; If you have any suggestions can also leave a comment or private letter, your support is my motivation to adhere to the creation. Pay attention to “Tom bomb architecture” for more technical dry goods!