编程爱好者联盟 2016-10-26
首先是例行的国际惯例,本文写于本人学习设计模式的路上,适合同样学习设计模式的朋友交流使用,大神误入请留下您宝贵的意见,先行谢过;
前面我们已经学习过简单工厂模式,今天来学习第二个工厂模式方法工厂模式。
百度百科
工厂方法模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中,核心工作类不再负责对象的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引入新的产品。
个人拙见
前面我们已经学习了简单工厂模式或者说是静态工厂模式,在简单工厂模式中,每添加一个产品子类的同时还需要去修改核心的工厂类,添加对应的判断语句,这是对扩展开放的同时也对修改开放,违反了设计原则中的开闭原则,但是今天的工厂方法原则则正好弥补了这一缺陷,将核心工厂抽象为一个抽象接口,核心工厂不再负责具体产品的创建,仅仅负责具体工厂子类必须实现的接口,此外给每一类产品都设置一个工厂类,通过工厂类生成对应的产品,以此来达到遵循开闭原则的目的,其次,产品和工厂之间使用抽象的方式进行关联,同样遵循了依赖倒置原则,另外,单一职责原则自不必说,工厂方法还很好的遵循了迪米特法则,也就是最少知道法则。
当然,方法工厂模式肯定是针对复杂对象的创建时所使用的,如果一个类仅仅通过new就可以创建,当然就没必要使用工厂方法模式了,不然就会增加不必要的工厂类,因为系统臃肿不利于后期维护。
上图中,左半部分是产品接口以及具体的产品类,右半部分是工厂接口以及对应的工厂类,每一个工厂对应一种产品,这种方式就避过了简单工厂模式中的判断语句,如果要添加新的产品,那么只需要让产品类实现产品接口,对应的工厂类实现核心工厂接口即可,不必去修改原来的代码,满足开闭原则。
下面就用JAVA代码简单的实现以下上述的类图,首先需要一个产品接口:
public interface IProduct { public void productMethod(); }
然后是两种具体的产品:
public class ProductA implements IProduct{ @Override public void productMethod() { System.out.println("产品A方法"); } } public class ProductB implements IProduct{ @Override public void productMethod() { System.out.println("产品B方法"); } }
然后是抽象工厂接口:
public interface IFactory { public IProduct createProduct(); }
产品A对应的工厂:
public class ProductFactoryA implements IFactory{ @Override public IProduct createProduct() { return new ProductA(); } }
产品B对应的工厂:
public class ProductFacctoryB implements IFactory{ @Override public IProduct createProduct() { return new ProductB(); } }
测试类:
public class Test { public static void main(String[] args) { IFactory factory = new ProductFactoryA(); IProduct productA = factory.createProduct(); productA.productMethod();//产品A方法 factory = new ProductFacctoryB(); IProduct productB = factory.createProduct(); productB.productMethod();//产品B方法 } }
通过上述的测试代码,我们可以看到,在客户端我们可以随意的切换具体的产品工厂和具体产品,并且不需要修改任何代码,如果需要添加新的产品,也只是需要添加具体的产品类和对应的工厂类即可,然后就可以直接在客户端调用,并不需要修改任何原来的代码,这也正好满足了对扩展开放对修改关闭的开闭原则,同时也去除掉了简单工厂中的ifelse的多重判断语句。
该部分内容转自开发小组左潇龙博客
应用场景一:JDBC连接
使用JDBC连接数据库时,如果数据库是mysql,那么就需要使用mysql的驱动,如果是Oracle数据库,那么就需要使用Oracle的驱动,但是我们在写JDBC程序的时候大部分是一样的,不一样的只是一样那就是驱动包不同(前提是SQL是标准的SQL),虽然我们提供给JDBC程序的驱动包不同,但是返回的Connection却是相同的,这是怎么做到的呢?其实这里使用的就是工厂方法模式,具体的流程如下:
首先,JDBC API提供了整个数据库操作的框架,具体的数据库的操作细节由各个数据库提供商自己实现,而我们要操作的仅仅是JDBC API,而不是具体的数据库细节,这就用到了java.sql包中的Driver和Connection接口,这两个接口的部分代码如下所示:
package java.sql; import java.sql.DriverPropertyInfo; import java.sql.SQLException; /** * The interface that every driver class must implement. */ public interface Driver { Connection connect(String url, java.util.Properties info) throws SQLException; boolean acceptsURL(String url) throws SQLException; } package java.sql; import java.sql.PreparedStatement; import java.sql.SQLException; /** * <P>A connection (session) with a specific * database. SQL statements are executed and results are returned * within the context of a connection. * <P> */ public interface Connection extends Wrapper { Statement createStatement() throws SQLException; PreparedStatement prepareStatement(String sql) throws SQLException; }
在Driver接口中,注释中给出了所有的驱动类都必须实现这个接口,也就是说所有的驱动类都必须实现Driver接口,否则没法使用,然后在Driver接口中还有一个connect()方法,返回一个Connnection用以操作数据库,但是Connection也是一个接口,只给出了约束,因此各个数据库提供商在提供驱动类的同时还需要给出Connection的实现类,用来具体的操作数据库的crud等操作,因此不同的数据库就需要提供不同的驱动类和连接实现类,如mysql要提供com.jdbc.mysql.Driver以及mysql的ConnectionImpl,Oracle要提供oracle.jdbc.driver.OracleDriver以及Oracle的ConnectionImpl类。
说到这里有人就奇怪了,说我平时写JDBC的时候也没看到Driver这个接口啊,我就用到了DriverManager类,这是因为在加载数据库驱动的时候,在Driver类中自动的将驱动类注册给了DriverManager,这时候就可以使用DriverManager来获取到数据库连接了,因此可能会看不到Driver接口的存在,但是它确确实实是存在的,下面可以看一下mysql的Driverr类的代码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver());//注册驱动 } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } }
上述几个类的类图如下(请原谅我又盗用了左大神的类图):
在使用JDBC连接的时候,我们不需要知道mysql或者oracle是怎么样提供的数据库操作的,我们只需要根据JDBC API中的Connection和DriverManager拿到数据库连接,处理具体的数据库业务即可,这个场景也是工厂方法模式的一个优点:屏蔽具体的产品类,也就是说具体的产品类如何变化调用者并不关心,客户端只需要关心产品的接口,只要产品的接口不发生变化,那么不管下层模块如何变化都不会影响上层模块的调用,这是遵循依赖倒置原则的具体表现。
应用场景二:
使用工厂方法模式并不是说只能使用面向接口,有时候也需要关心具体的产品实现类,因为每一类产品可能都有不同的地方,例如HashSet和ArrayList,这两个类的具体实现也使用到了工厂方法模式,具体的类图如下(我保证这是最后一次盗图了,以后再也不敢了):
这时候一看,我擦,上面不是说要面向接口编程嘛,那我以后就使用Iterable和Iterator这俩了,这个,如果可以的话,类库的设计者何必要设计什么ArrayList、TreeSet这么多集合类呢,这里的每个具体的产品类都有其特性,因此这里就不能单单的使用接口而是要使用其对应的产品类。
上述的两个场景一个是面向接口的,一个是面向具体的产品的,这也是工厂方法模式提供的两种使用情况:如果各个产品提供的功能类似,如mysql或者Oracle都是提供数据库连接,那么就可以使用面向接口的方式来处理,但是如果是针对于具体的产品,此时调用者清楚的知道应该使用哪个具体工厂服务,实例化该具体工厂,生产出具体的产品来,这就需要考虑倒具体的产品了,不能单单的是面向接口了。
首先,工厂方法模式是简单工厂模式的扩展,给每一个产品都添加一个对应的工厂,使用抽象工厂作为核心工厂,核心工厂不再关注具体产品的创建而是交由它的子类来创建,但是在简单工厂模式中仅仅只存在一个工厂,并且是核心工厂;
其次,工厂方法模式中避免了在核心工厂中使用多重判断语句的诟病,并且满足对于修改关闭对于扩展开放的开闭原则;
第三,如果一个模块中仅仅存在一个工厂就可以满足条件,那么完全没有必要为每个产品都产生对应的工厂,虽然简单工厂模式不满足开闭原则,但是不能否认简单工厂模式也是很强大的;
工厂方法模式是一个创建型的设计模式,遵循了单一职责、依赖倒置以及开闭原则,有两种使用场景,一种是客户端不需要知道具体的产品是如何实现的,只需要面向接口编程即可,另一种是客户端完全清楚每种的产品的实现和用途,客户端需要使用具体的产品了。