乐趣区

关于java:万字长文加实战案例让抽象工厂不再抽象

本文节选自《设计模式就该这样学》

1 对于产品等级构造和产品族

在解说形象工厂之前,咱们要理解两个概念:产品等级构造和产品族,如下图所示。

上图中有正方形、圆形和菱形 3 种图形,雷同色彩、雷同深浅的代表同一个产品族,雷同形态的代表同一个产品等级构造。同样能够从生存中来举例,比方,美的电器生产多种家用电器,那么上图中,色彩最深的正方形就代表美的洗衣机,色彩最深的圆形代表美的空调,色彩最深的菱形代表美的热水器,色彩最深的一排都属于美的品牌,都属于美的电器这个产品族。再看最右侧的菱形,色彩最深的被指定了代表美的热水器,那么第二排色彩略微浅一点的菱形代表海信热水器。同理,同一产品族下还有格力洗衣机、格力空调、格力热水器。

再看下图,最左侧的小房子被认为是具体的工厂,有美的工厂、海信工厂、格力工厂。每个品牌的工厂都生产洗衣机、空调和热水器。

通过下面两张图的比照了解,置信大家对形象工厂有了十分形象的了解。

2 形象工厂模式的通用写法

以下是形象工厂模式的通用写法。


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

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

    // 形象工厂类
    public interface IFactory {IProductA makeProductA();

        IProductB makeProductB();}

    // 产品 A 形象
    public interface IProductA {void doA();
    }

    // 产品 B 形象
    public interface IProductB {void doB();
    }

    // 产品族 A 的具体产品 A
    static class ConcreteProductAWithFamilyA implements IProductA{public void doA() {System.out.println("The ProductA be part of FamilyA");
        }
    }

    // 产品族 A 的具体产品 B
    static class ConcreteProductBWithFamilyA implements IProductB{public void doB() {System.out.println("The ProductB be part of FamilyA");
        }
    }

    // 产品族 B 的具体产品 A
    static class ConcreteProductAWithFamilyB implements IProductA{public void doA() {System.out.println("The ProductA be part of FamilyB");
        }
    }

    // 产品族 B 的具体产品 B
    static class ConcreteProductBWithFamilyB implements IProductB{public void doB() {System.out.println("The ProductB be part of FamilyB");
        }
    }

    // 具体工厂类 A
    static class ConcreteFactoryA implements IFactory{public IProductA makeProductA() {return new ConcreteProductAWithFamilyA();
        }

        public IProductB makeProductB() {return new ConcreteProductBWithFamilyA();
        }
    }

    // 具体工厂类 B
    static class ConcreteFactoryB implements IFactory{public IProductA makeProductA() {return new ConcreteProductAWithFamilyB();
        }

        public IProductB makeProductB() {return new ConcreteProductBWithFamilyB();
        }
    }
}

3 应用形象工厂模式反对产品扩大

咱们来看一个具体的业务场景,并且用代码来实现。还是以网络课程为例,个别课程研发会有肯定的规范,每个课程不仅要提供课程的录播视频,还要提供老师的课堂笔记。相当于当初的业务变更为同一个课程不单纯是一个课程信息,要同时蕴含录播视频、课堂笔记,甚至要提供源码能力形成一个残缺的课程。首先在产品等级中减少两个产品:录播视频 IVideo 和课堂笔记 INote。
IVideo 接口的代码如下。


public interface IVideo {void record();
}

INote 接口的代码如下。


public interface INote {void edit();
}

而后创立一个形象工厂 CourseFactory 类。


/**
 * 形象工厂是用户的主入口
 * 在 Spring 中利用得最为宽泛的一种设计模式
 * 易于扩大
 * Created by Tom
 */
public abstract class CourseFactory {public void init(){System.out.println("初始化根底数据");
    }

    protected abstract INote createNote();

    protected abstract IVideo createVideo();}

接下来创立 Java 产品族,Java 视频 JavaVideo 类的代码如下。


public class JavaVideo implements IVideo {public void record() {System.out.println("录制 Java 视频");
    }
}

扩大产品等级 Java 课堂笔记 JavaNote 类。


public class JavaNote implements INote {public void edit() {System.out.println("编写 Java 笔记");
    }
}

创立 Java 产品族的具体工厂 JavaCourseFactory。


public class JavaCourseFactory extends CourseFactory {public INote createNote() {super.init();
        return new JavaNote();}

    public IVideo createVideo() {super.init();
        return new JavaVideo();}
}

随后创立 Python 产品族,Python 视频 PythonVideo 类的代码如下。


public class PythonVideo implements IVideo {public void record() {System.out.println("录制 Python 视频");
    }
}

扩大产品等级 Python 课堂笔记 PythonNote 类。


public class PythonNote implements INote {public void edit() {System.out.println("编写 Python 笔记");
    }
}

创立 Python 产品族的具体工厂 PythonCourseFactory。


public class PythonCourseFactory implements CourseFactory {public INote createNote() {return new PythonNote();
    }
    public IVideo createVideo() {return new PythonVideo();
    }
}

最初来看客户端调用代码。


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

下面代码残缺地形容了 Java 课程和 Python 课程两个产品族,也形容了视频和笔记两个产品等级。形象工厂十分完满、清晰地形容了这样一层简单的关系。然而,不晓得大家有没有发现,如果再持续扩大产品等级,将源码 Source 也退出课程中,则代码从形象工厂到具体工厂要全副调整,这显然不合乎开闭准则。

4 应用形象工厂模式重构数据库连接池

还是演示课堂开始的 JDBC 操作案例,咱们每次操作都须要从新创立数据库连贯。其实每次创立都十分消耗性能,耗费业务调用工夫。咱们应用形象工厂模式,将数据库连贯事后创立好,放到容器中缓存着,当业务调用时就只需现取现用。咱们来看代码。
Pool 抽象类的代码如下。


/**
 * 自定义连接池 getInstance() 返回 POOL 惟一实例,第一次调用时将执行构造函数
 * 构造函数 Pool() 调用驱动装载 loadDrivers() 函数;* 连接池创立 createPool() 函数,loadDrivers() 装载驱动
 * createPool() 创立连接池,getConnection() 返回一个连贯实例,* getConnection(long time) 增加工夫限度
 * freeConnection(Connection con) 将 con 连贯实例返回连接池,getnum() 返回闲暇连接数
 * getnumActive() 返回以后应用的连接数
 *
 * @author Tom
 *
 */

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

   private static Pool instance = null;     // 定义惟一实例

   /**
    * 最大连接数
    */
   protected int maxConnect = 100;         // 最大连接数

   /**
    * 放弃连接数
    */
   protected int normalConnect = 10;     // 放弃连接数

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

   /**
    * 驱动类
    */
   protected Driver driver = null;         // 驱动变量


   /**
    * 公有构造函数,不容许外界拜访
    */
   protected Pool() {
      try
      {init();
         loadDrivers(driverName);
      }catch(Exception e)
      {e.printStackTrace();
      }
   }

   /**
    * 初始化所有从配置文件中读取的成员变量
    */
   private void init() 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"));
   }

   /**
    * 装载和注册所有 JDBC 驱动程序
    * @param dri  接管驱动字符串
    */
   protected void loadDrivers(String dri) {
      String driverClassName = dri;
      try {driver = (Driver) Class.forName(driverClassName).newInstance();
         DriverManager.registerDriver(driver);
         System.out.println("胜利注册 JDBC 驱动程序" + driverClassName);
      } catch (Exception e) {System.out.println("无奈注册 JDBC 驱动程序:" + driverClassName + ", 谬误:" + e);
      }
   }

   /**
    * 创立连接池
    */
   public abstract void createPool();

   /**
    *
    *(单例模式)返回数据库连接池 Pool 的实例
    *
    * @param driverName 数据库驱动字符串
    * @return
    * @throws IOException
    * @throws ClassNotFoundException
    * @throws IllegalAccessException
    * @throws InstantiationException
    */
   public static synchronized Pool getInstance() throws IOException,
         InstantiationException, IllegalAccessException,
         ClassNotFoundException {if (instance == null) {instance = (Pool) Class.forName("org.e_book.sqlhelp.Pool").newInstance();}
      return instance;
   }

   /**
    * 取得一个可用的连贯,如果没有,则创立一个连贯,并且小于最大连贯限度
    * @return
    */
   public abstract Connection getConnection();

   /**
    * 取得一个连贯,有工夫限度
    * @param time 设置该连贯的持续时间(以毫秒为单位)* @return
    */
   public abstract Connection getConnection(long time);

   /**
    * 将连贯对象返回连接池
    * @param con 取得连贯对象
    */
   public abstract void freeConnection(Connection con);

   /**
    * 返回以后闲暇的连接数
    * @return
    */
   public abstract int getnum();

   /**
    * 返回当前工作的连接数
    * @return
    */
   public abstract int getnumActive();

   /**
    * 敞开所有连贯,撤销驱动注册(此办法为单例办法)*/
   protected synchronized void release() {
      // 撤销驱动
      try {DriverManager.deregisterDriver(driver);
         System.out.println("撤销 JDBC 驱动程序" + driver.getClass().getName());
      } catch (SQLException e) {
         System.out
               .println("无奈撤销 JDBC 驱动程序的注册:" + driver.getClass().getName());
      }
   }
}

DBConnectionPool 数据库连接池的代码如下。


/**
 * 数据库连接池治理类
 * @author Tom
 *
 */
public final class DBConnectionPool extends Pool {
   private int checkedOut;                         // 正在应用的连接数
   /**    
    * 寄存产生的连贯对象容器
    */
   private Vector<Connection> freeConnections = new Vector<Connection>(); 
                                                // 寄存产生的连贯对象容器

   private String passWord = null;                 // 明码

   private String url = null;                     // 连贯字符串

   private String userName = null;                 // 用户名

   private static int num = 0;                    // 闲暇连接数

   private static int numActive = 0;                // 以后可用的连接数

   private static DBConnectionPool pool = null;    // 连接池实例变量

   /**
    * 产生数据连接池
    * @return
    */
   public static synchronized DBConnectionPool getInstance()
   {if(pool == null)
      {pool = new DBConnectionPool();
      }
      return pool;
   }

   /**
    * 取得一个数据库连接池的实例
    */
   private DBConnectionPool() {
      try
      {init();
         for (int i = 0; i < normalConnect; i++) {     // 初始 normalConn 个连贯
            Connection c = newConnection();
            if (c != null) {freeConnections.addElement(c);             // 往容器中增加一个连贯对象
               num++; // 记录总连接数
            }
         }
      }catch(Exception e)
      {e.printStackTrace();
      }
   }
   /**
    * 初始化
    * @throws IOException
    */
   private void init() 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"));
   }
   /**
    * 如果不再应用某个连贯对象,则可调此办法将该对象开释到连接池
    * @param con
    */
   public synchronized void freeConnection(Connection con) {freeConnections.addElement(con);
      num++;
      checkedOut--;
      numActive--;
      notifyAll(); // 解锁}

   /**
    * 创立一个新连贯
    * @return
    */
   private Connection newConnection() {
      Connection con = null;
      try {if (userName == null) { // 用户、明码都为空
            con = DriverManager.getConnection(url);
         } else {con = DriverManager.getConnection(url, userName, passWord);
         }
         System.out.println("连接池创立一个新的连贯");
      } catch (SQLException e) {System.out.println("无奈创立这个 URL 的连贯" + url);
         return null;
      }
      return con;
   }

   /**
    * 返回以后闲暇的连接数
    * @return
    */
   public int getnum() {return num;}

   /**
    * 返回以后可用的连接数
    * @return
    */
   public int getnumActive() {return numActive;}


   /**
    *(单例模式)获取一个可用连贯
    * @return
    */
   public synchronized Connection getConnection() {
      Connection con = null;
      if (freeConnections.size() > 0) { // 还有闲暇的连贯
         num--;
         con = (Connection) freeConnections.firstElement();
         freeConnections.removeElementAt(0);
         try {if (con.isClosed()) {System.out.println("从连接池删除一个有效连贯");
               con = getConnection();}
         } catch (SQLException e) {System.out.println("从连接池删除一个有效连贯");
            con = getConnection();}
        // 没有闲暇连贯且以后连贯小于最大允许值,若最大值为 0,则不限度
      } else if (maxConnect == 0 || checkedOut < maxConnect) {con = newConnection();
      }
      if (con != null) { // 以后连接数加 1
         checkedOut++;
      }
      numActive++;
      return con;
   }

   /**
    * 获取一个连贯,并加上等待时间限度,工夫为毫秒
    * @param timeout  承受等待时间(以毫秒为单位)* @return
    */
   public synchronized Connection getConnection(long timeout) {long startTime = new Date().getTime();
      Connection con;
      while ((con = getConnection()) == null) {
         try {wait(timeout); // 线程期待
         } catch (InterruptedException e) { }
         if ((new Date().getTime() - startTime) >= timeout) {return null; // 如果超时,则返回}
      }
      return con;
   }

   /**
    * 敞开所有连贯
    */
   public synchronized void release() {
      try {
         // 将以后连贯赋值到枚举中
         Enumeration allConnections = freeConnections.elements();
         // 应用循环敞开连接池中的所用连贯
         while (allConnections.hasMoreElements()) {
            // 如果此枚举对象至多还有一个可提供的元素,则返回此枚举的下一个元素
            Connection con = (Connection) allConnections.nextElement();
            try {con.close();
               num--;
            } catch (SQLException e) {System.out.println("无奈敞开连接池中的连贯");
            }
         }
         freeConnections.removeAllElements();
         numActive = 0;
      } finally {super.release();
      }
   }

   /**
    * 建设连接池
    */
   public void createPool() {pool = new DBConnectionPool();
      if (pool != null) {System.out.println("创立连接池胜利");
      } else {System.out.println("创立连接池失败");
      }
   }
}

5 形象工厂模式在 Spring 源码中的利用

在 Spring 中,所有工厂都是 BeanFactory 的子类。通过对 BeanFactory 的实现,咱们能够从 Spring 的容器拜访 Bean。依据不同的策略调用 getBean() 办法,从而取得具体对象。


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, @Nullable Class<?> typeToMatch) throws NoSuchBean     DefinitionException;

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

}

BeanFactory 的子类次要有 ClassPathXmlApplicationContext、XmlWebApplicationContext、StaticWebApplicationContext、StaticPortletApplicationContext、GenericApplicationContext 和 Static ApplicationContext。在 Spring 中,DefaultListableBeanFactory 实现了所有工厂的公共逻辑。

【举荐】Tom 弹架构:珍藏本文,相当于珍藏一本“设计模式”的书

本文为“Tom 弹架构”原创,转载请注明出处。技术在于分享,我分享我高兴!
如果本文对您有帮忙,欢送关注和点赞;如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源。关注微信公众号『Tom 弹架构』可获取更多技术干货!

退出移动版