一、简单工厂模式的由来

  所有设计模式都是为解决某类问题而产生的,那么简单工厂模式是为解决什么问题呢?我们假设有以下业务场景:

  在一个学生选课系统中,文科生用户选课时,我们要获得文科生的所有课程列表;理科生用户选课时,获得理科生的所有课程列表;体育生用户选课时,获得体育生的所有课程列表;那我们的逻辑怎么写呢?很简单,我们用伪代码实现如下:

if(userType is 文科生){

    return 文科生课程;

}else if(userType is 理科生){

    return 理科生课程;

}else if(userType is 体育生){

   return 体育生课程;

}

  这时我们发现,不光在学生选课时需要这段逻辑,在学生查询课程时,也需要这段代码,于是我们理所应当的将这段代码复制到了查询课程的方法中,如果老师查询课程的方法中也要这段代码,那继续复制到老师查看课程的方法中。。。。。。

  这就造成了重复性代码,如何让这段代码只写一遍,多处可以复用呢?我们可以将这段代码抽取成公共方法,将学生类型定义成参数。在调用的地方,通过传入不同的参数类型,返回不同的课程列表。这样就实现了一次代码,多次复用的效果。

  以上的这种优化过程,其实就是简单工厂模式的生成过程。

二、什么是简单工厂模式?

  在简单工厂模式中,程序员创建或者获得对象,不再用new Object()的方式进行,而是给工厂类传入参数,工厂类根据程序员传入参数的不同,返回不同的对象。

三、简单工厂模式的角色

  简单工厂模式中,有以下几种角色,这些角色撑起了简单工程模式的实现:

  •   产品规范:提供规范,供其它产品子类来实现这些规范;可以是接口,抽象类或者父类;
  •   产品实现:产品规范的具体实现类;实现或者继承产品规范的接口,抽象类或父类;
  •   调度类(工厂类):有了产品规范,也有了产品实现,那用哪个产品呢,由调度类来调度决定。
  •   产品使用者:使用产品的地方,传入想使用的产品参数,通过调度类给产品使用者返回产品。

  其中,产品规范可以是接口,抽象类或者父类,总之,它的作用就是定义产品的规范,想生产这个产品,必须遵循这套规范,以此达到统一的标准。产品实现类是子类,来实现接口的方法,定义具体的产品。产品实现类可以有很多个,只要实现了产品规范,都是产品的实现类。工厂类,对具体的产品实现类进行调度,通过逻辑判断,决定输出哪个产品。这里的产品使用者,就是我们敲代码的程序员,我们想要使用某一个对象或其他产品,只需往工厂类里传入相应参数,就能返回了想要的产品。

  由此可见,设计模式是针对程序员服务的,程序员只需输入参数,就能获得产品。设计模式并不是针对软件用户的东西,所以,我们在学习设计模式时,一定要把这个观念树立起来:在设计模式中,用户就是我们敲业务代码的程序员,而我们在写设计模式时,我们服务的对象,也就成了业务代码程序员,而不是普通的软件用户了。

四、简单工厂模式的实现

  下面,我们自己实现一套简单工厂模式的代码。由上面可知,实现该模式,其实就是实现产品规范、产品实现类、调度工厂类、使用者类几个角色即可。

  首先,定义产品规范,产品规范可以是接口,抽象类或父类,这里,我们用接口定义。

/**
* 产品规范接口:定义生产家具规范
*/
public interface Furniture {
/**
* 成产家具的方法
*/
public void createFurniture();
}

然后,我们定义两个产品的实现类,实现createFurniture()规范

public class Table implements Furniture{
@Override
public void createFurniture() {
System.out.println("生产桌子");
}
} public class Chair implements Furniture{
@Override
public void createFurniture() {
System.out.println("生产椅子");
}
}

  然后,我们定义调度工厂类,工厂类需要进行调度,判断返回哪种家具。

  注意:工厂类的调度方法,通常用static静态方法,方便使用者调用


/**
* 工厂类,用于调度家具,返回程序员想要的家具
*/
public class FurnitureFactory {

/**
* 调度方法,根据传入的type参数,判断返回哪种家具
* @param type
* @return
*/
public static Furniture getFurniture(String type){
Furniture f=null;
if("桌子".equals(type)){
f=new Table();
}else if("椅子".equals(type)){
f=new Chair();
}
return f;
}
}

  最后,定义调用类,来调用工厂,返回想要的家具

/**
* 产品使用者
*/
public class FurnitureUser {
public static void main(String[] args) {
//甲程序员需要桌子,传入桌子参数,获得桌子
Furniture f1=FurnitureFactory.getFurniture("桌子");
f1.createFurniture();
//乙程序员需要椅子,传入椅子参数,获得椅子
Furniture f2=FurnitureFactory.getFurniture("椅子");
f2.createFurniture(); } }

  这样,一个简单工厂模式,就实现了。由此可见,作为简单工厂模式的使用者,即业务程序员而言,我们只关注给工厂类传参,获取对象即可。而作为简单工厂模式的设计者,我们需要定义产品规范,定义调度工厂类的调度逻辑。至于产品实现类,可以由多个第三方来实现。

五、缺点

缺点一:

  我们继续上面的例子,进行进一步开发。加入现在我们对家具进行扩展,要生产沙发,我们需要怎么做呢?

  首先,产品规范无需改动。

  然后,产品的实现类,我们要进行扩展,新建沙发类,来实现沙发产品

public class Sofa implements Furniture{
@Override
public void createFurniture() {
System.out.println("生产沙发");
}
}

  接下来,工厂类里,因为我们新加了产品,所以,在调度方法中,需要把新的产品逻辑写进去

/**
* 工厂类,用于调度家具,返回程序员想要的家具
*/
public class FurnitureFactory { /**
* 调度方法,根据传入的type参数,判断返回哪种家具
* @param type
* @return
*/
public static Furniture getFurniture(String type){
Furniture f=null;
if("桌子".equals(type)){
f=new Table();
}else if("椅子".equals(type)){
f=new Chair();
}else if("沙发".equals(type)){
f=new Sofa();
}
return f;
}
}

  这样,我们在使用者中,就可以生产沙发了

/**
* 产品使用者
*/
public class FurnitureUser {
public static void main(String[] args) {
//甲程序员需要桌子,传入桌子参数,获得桌子
Furniture f1=FurnitureFactory.getFurniture("桌子");
f1.createFurniture();
//乙程序员需要椅子,传入椅子参数,获得椅子
Furniture f2=FurnitureFactory.getFurniture("椅子");
f2.createFurniture();
//丙程序员需要沙发
Furniture f3=FurnitureFactory.getFurniture("沙发");
f3.createFurniture(); } }

  由此可见,我们新加一个产品,需要新加一个产品类,这个无可厚非。但是我们还需要在工厂类中,修改调度逻辑,把新加的产品逻辑写进去。这个操作,就违反了开闭原则。

  开闭原则:对外支持扩展,对内不允许修改。

  新产品类的增加,体现了对外扩展的支持,但是修改调度类,又违背了不对内修改的原则。这就是简单工厂模式的缺点之一。

缺点二:

  在上面的角色图中我们可以看出,工厂类连接了产品和使用者,是简单工厂模式的核心。加入工厂类出现了问题,那么所有角色都处于了瘫痪状态。

缺点三:

  我们上述所讲的简单工厂模式,是标准模式,在实际应用中,很多时候,我们会对简单工厂模式进行变形,例如,产品规范我们用抽象类来定义,在产品规范中,我们有定义产品调度的方法,这时,这个抽象类,就具有了产品规范和工厂调度两个角色。还拿上面的代码举例子,我们可以这样写:

/**
* 产品规范+产品调度:既定义产品的规范,又有产品调度的方法,合二为一
*/
public abstract class Furniture { /**
* 定义规范
*/
public abstract void createFurniture(); /**
* 调度方法
*/
public static Furniture getFurniture(String type){
Furniture f=null;
if("桌子".equals(type)){
f=new Table();
}else if("椅子".equals(type)){
f=new Chair();
}else if("沙发".equals(type)){
f=new Sofa();
}
return f;
}
}

  这样,Furniture就兼具了两种角色。那么它的实现类,就变成了继承Furniture类即可。这样改造,也是一种简单工厂模式的实现。

  在这种实现中,就违反了单一责任原则。但是在某种情况下,我们可以违背其中的原则,不要求生搬硬套。这里只是举例说明,在很多框架实现中,都用了这种模式的实现方式。

六、改进

  针对违背开闭原则的缺点,我们对代码进行改进。之所以违背了开闭原则,是因为工厂类里的调度方法,在新加产品时需要修改。那么,有什么办法可以不对其进行修改呢?思路就是要动态加载其产品类,这样,无论多少个产品,动态加载进来,就无需改动代码了。

  如何进行动态加载呢?我们可以采用xml配置的方式,将产品实现类加入xml里,在工厂类里,通过反射,创建xml里配置的产品类。我们还可以使用注解,在产品实现类上定义注解,然后工厂类扫描注解,通过反射动态加载产品类。这样,我们就只需要修改xml或者在产品类中加注解就行了,无需修改工厂类的方法,这就满足了开闭原则。这种思路是不是很熟悉呢?我们在spring框架的使用中,不是就可以在xml中配置类,也可以通过注解声明类吗?没错,spring就是通过这种方式满足开闭原则的。

  配置xml文件和自定义注解的方式实现简单工厂模式的代码,大家自行写一下  (●'◡'●)

七、优点

  1.面向接口编程,体现了面向对象的思想;

  2.用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的。有利于整个软件体系结构的优化。

八、简单工厂模式在JDK中的应用

  下面,我们来演示一下,在JDK中,用到的简单工厂模式。

  首先,我们讲解Calendar类中用到的简单工厂模式:

  上面说到,简单工厂模式由产品规范、产品实现、工厂调度、产品使用几个角色组成,那我们就一一找到这几个角色。

  创建Calendar日历对象,我们是通过Calendar.getInstance()方法获得,在这里,我们就是产品使用者,调用getInstance()方法,获得产品。相应的,那Calendar就是工厂类了,因为使用者直接面对的是工厂。getInstance()方法就是工厂类的调度方法了,我们看其源码:

  可以看到,getInstance方法是个静态方法,我记得上面说到的调度类通常是静态方法吗

  在getInstance()中,调用了createCalendar()方法,我们继续看这个方法的源码:

private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
{
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
} Calendar cal = null; if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
// If no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}

  由上面的代码可以看出,真正的调度,是在createCalendar()里调度的,关键代码如下:

  createCalendar()里,通过逻辑判断,调度返回哪个产品类。那么至此,产品规范类也就有了,那就是返回的Calendar。所以,在这个简单工厂模式中,Calendar既充当了工厂类角色,又充当了产品规范角色。Calendar是一个抽象类。

  那么产品实现是谁呢,就是createCalendar()里的产品1,产品2,产品3.我们随便打开一个类,看其源码:

  可以看到,产品继承了产品规范Calendar类,实现了其规范。

  这样,简单工厂模式的几个角色,就都找出来了。

  下面,我们看JDBC是如何利用简单工厂模式的。我们先看一下原生JDBC连接数据库的代码,这里,我们主要讲设计模式,所以,只展示部分JDBC代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement; public class JDBCDemo {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mybatis";
String username = "root";
String password = "123456";
try {
Class.forName("com.mysql.jdbc.Driver");// 指定连接类型
Connection conn = DriverManager.getConnection(url, username, password);// 获取连接
PreparedStatement statement = conn.prepareStatement("");// 准备执行语句
} catch (Exception e) {
e.printStackTrace();
} }
}

  我们需要先引入mysql的驱动包,然后执行JDBC代码,才能连接mysql数据库。如果我们想连接Oracle数据库,则需要引入Oracle的驱动包,进行Oracle数据库连接。

  同样的,我们找简单工厂模式的几个关键角色。首先,产品使用者就是写JDBC代码的程序员了。工厂是谁呢,没错,工厂是产品使用者调用的,肯定是在JDBC代码里出现的一个对象。稍加分析便可以知道,是DriverManager对象。调度方法就是getConnection()方法。

  我们点进源码查看,DriverManager的getConnection()里调用了另一个私有的getConnection()方法,我们看这个私有的getConnection方法的关键源代码:

   //  Worker method called by the public getConnection() methods.
private static Connection getConnection(
......省略......
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
} } else {
println(" skipping: " + aDriver.getClass().getName());
} } ......省略...... }

  可以看出,getConnection方法循环遍历了一个registeredDrivers集合,满足一定的条件,返回Connection对象。这里就是在做调度工作,返回相应的产品。继续看源码可以得知,registeredDrivers是DriverManager维护的一个集合,DriverManager还提供了registerDriver()方法,来供产品注册驱动,注册的驱动,会加入到registeredDrivers集合中,供DriverManager工厂调度使用。

  那么,谁是产品角色呢,就是我们引入的mysql驱动包,就是产品。他们都遵循了Driver接口规范。Driver接口是jdk定义的一个数据库连接的规范接口。至此,所有的角色都已经找到,可以看出,JDBC里也应用到了简单工厂模式。

  是不是还有同学对JDBC模式的简单工厂模式很懵逼呢?那接下来,我们亲手实现一个产品类,来帮助大家理解。

  我们都知道,连接mysql数据库,引入mysql数据库的驱动,连接oracle数据库,引入oracle数据库的驱动,这些都是产品的实现类。那我们能不能引入自定义的驱动呢?答案是肯定的。下面我们就按照Driver产品规范,自己实现一个产品类。

  首先,创建产品实现类,并实现Driver规范:

import java.sql.*;
import java.util.Properties;
import java.util.logging.Logger;

public class SelfDriver implements Driver {

//这里需要注册到DriverManager里,DriverManager是工厂类,需要调度新产品,只有注册了才可以调度。
static {
try {
DriverManager.registerDriver(new SelfDriver());
} catch (SQLException e) {
e.printStackTrace();
}
}

//实现接口规范,这里我们输出自定义数据库字样,来判断是否走了我们自定义产品的方法。
@Override
public Connection connect(String url, Properties info) throws SQLException {
System.out.println("连接了自定义数据库");
return null;
}

@Override
public boolean acceptsURL(String url) throws SQLException {
return false;
}

@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
return new DriverPropertyInfo[0];
}

@Override
public int getMajorVersion() {
return 0;
}

@Override
public int getMinorVersion() {
return 0;
}

@Override
public boolean jdbcCompliant() {
return false;
}

@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}

  产品创建好了,也注册到了工厂类中,那接下来,我们在JDBC中使用我们的产品 即可。如下代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement; public class JDBCDemo {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mybatis";
String username = "root";
String password = "123456";
try {
//这里注册我们自己的产品驱动
Class.forName("demo.SelfDriver");// 指定连接类型
Connection conn = DriverManager.getConnection(url, username, password);// 获取连接
PreparedStatement statement = conn.prepareStatement("");// 准备执行语句
} catch (Exception e) {
e.printStackTrace();
} }
}

  运行JDBC,可以看到输出结果:

  说明我们自己写的驱动运行了。

  接下来,请大家思考一下,JDBC的简单工厂模式,是怎么实现开闭原则的呢?

  首先,我们定义新的产品类,实现Driver接口规范,这是对外扩展必须的操作,没有问题。

  然后,我们需要在产品类里,调用DriverManager的registerDriver方法,将我们的产品注册到DriverManager中,在registerDriver方法中,就把新的产品,加入到了我们上面提到的registeredDrivers集合中,这样,就无需修改工厂类代码,直接用新产品了。

  所以,JDBC是通过在新产品中注册的方式,实现了开闭原则,这种方式,值得大家借鉴。

  好了,简单工厂模式就为大家分享到这里

设计模式之简单工厂模式(Simple Factory Pattern)的更多相关文章

  1. 【设计模式】简单工厂模式 Simple Factory Pattern

    简单工厂模式Simple Factory Pattern[Simple Factory Pattern]是设计模式里最简单的一个模式,又叫静态工厂模式[Static Factory Pattern], ...

  2. 设计模式之—简单工厂模式<Simple Factory Pattern >

    简单工厂模式结构图: 简单工厂模式以简单的加减乘除运算为例: 运算符类(Operation): namespace CalcTest.Simple_Factory_patterns { class O ...

  3. 【UE4 设计模式】简单工厂模式 Simple Factory Pattern

    概述 描述 又称为静态工厂方法 一般使用静态方法,根据参数的不同创建不同类的实例 套路 创建抽象产品类 : 创建具体产品类,继承抽象产品类: 创建工厂类,通过静态方法根据传入不同参数从而创建不同具体产 ...

  4. Golang设计模式—简单工厂模式(Simple Factory Pattern)

    Golang设计模式--简单工厂模式 背景 假设我们在做一款小型翻译软件,软件可以将德语.英语.日语都翻译成目标中文,并显示在前端. 思路 我们会有三个具体的语言翻译结构体,或许以后还有更多,但现在分 ...

  5. 大白话简单工厂模式 (Simple Factory Pattern)

    大白话简单工厂模式 (Simple Factory Pattern) 从买车经历说起 毕业两年,码农张小两口无法忍受挤公交,凌晨起床抢火车票的痛苦,遂计划买车.逛了多家4S店,最终定下日产某车型的轿车 ...

  6. 设计模式之简单工厂模式Simple Factory(四创建型)

    工厂模式简介. 工厂模式专门负责将大量有共同接口的类实例化 工厂模式可以动态决定将哪一个类实例化,不必事先知道每次要实例化哪一个类. 工厂模式有三种形态: 1.简单工厂模式Simple Factory ...

  7. Net设计模式实例之简单工厂模式(Simple Factory Pattern)

    一.简单工厂模式简介(Bref Introduction) 简单工厂模式(Simple Factory Pattern)的优点是,工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类, ...

  8. 简单工厂模式(Simple Factory Pattern)

    简单工厂模式概述 定义:定义一个工厂类,他可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类 在简单工厂模式中用于被创建实例的方法通常为静态(static)方法,因此简单工厂模式又被 ...

  9. C#设计模式-1简单工厂模式Simple Factory)

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 简单的工 ...

随机推荐

  1. Codeforces Round #550 (Div. 3) F. Graph Without Long Directed Paths (二分图染色)

    题意:有\(n\)个点和\(m\)条无向边,现在让你给你这\(m\)条边赋方向,但是要满足任意一条边的路径都不能大于\(1\),问是否有满足条件的构造方向,如果有,输出一个二进制串,表示所给的边的方向 ...

  2. Codeforces ECR 83 C. Adding Powers (位运算)

    题意:给你n个数和一个底数k,每个数每次能减去k^i(i=0,1,2,....),每个k^i只能用一次,问是否能够将每个数变为0. 题解:我们将每个数转化为k进制,因为每个k^i只能用一次,所以我们统 ...

  3. hdu5317 RGCDQ

    Problem Description Mr. Hdu is interested in Greatest Common Divisor (GCD). He wants to find more an ...

  4. Educational Codeforces Round 96 (Rated for Div. 2) D. String Deletion (思维)

    题意:有一个\(01\)串,每次操作要先删除一个位置上的元素,然后删除相同前缀和,直到字符串被删完,问最多能操作多少次. 题解: 对于一个长度大于\(1\)的相同前缀,我们最多只能对它操作一次,然后就 ...

  5. Link/Cut Tree CodeForces - 614A 暴力+爆 long long 处理

    题意: 给你一个区间[l,r],让你从小到大输出k^x,设y=k^x,要保证y在区间[l,r]中 题解: 就算k是最小的2也不需要枚举多少次就到long long的极限了,所以暴力没商量,根本不会TL ...

  6. Git使用出现Automatic merge failed; fix conflicts and then commit the result.解决方法

    产生原因 首先这个问题产生的原因是因为你git pull 的时候会分为两步,第一步先从远程服务器上拉下代码,第二步进行merge,但是merge时候失败了就会产生上述问题. 解决方法: 丢弃本地提交, ...

  7. codeforces 11B Jumping Jack

    Jack is working on his jumping skills recently. Currently he's located at point zero of the number l ...

  8. Linux shell script All In One

    Linux shell script All In One refs xgqfrms 2012-2020 www.cnblogs.com 发布文章使用:只允许注册用户才可以访问!

  9. Apple iPhone 12 Pro 数据迁移方式 All In One

    Apple iPhone 12 Pro 数据迁移方式 All In One iPhone 12 Pro https://mp.weixin.qq.com/s/US1Z_69zVQIhV-cNW1E6A ...

  10. Web Components All In One

    Web Components All In One Web Components https://www.webcomponents.org/ HTML Template Custom Element ...