简单工厂模式在Logback源码以及JDK源码中的应用

创建型模式

目录

一、简单工厂模式在Logback源码以及JDK源码中的应用

1、简单工厂模式

1.1 简单工厂模式的UML类图

1.2 日常生活中看简单工厂模式

1.3 具体例子

2、简单工厂模式在Logback源码中的应用

3、简单工厂模式在JDK源码中的应用

3.1 Calendar 类为例简单工厂模式

3.2 JDBC简单工厂模式

4、简单工厂模式的优缺点

4.1 简单工厂模式的优点

4.2 简单工厂模式的缺点

4.3 简单工厂模式的适用环境


一、简单工厂模式在Logback源码以及JDK源码中的应用

1、简单工厂模式

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于创建型模式。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承或实现一个父类或接口)的实例。

1.1 简单工厂模式的UML类图

1.2 日常生活中看简单工厂模式

就拿巴扎黑理发这件事来说,服务员就充当了工厂类,她根据巴扎黑想要剪的价位来安排一位合适的理发师。理发师这个称谓就相当于抽象产品,它描述了所有理发师都具备的能力——理发。服务员安排的这位总监就相当于具体产品。
简单工厂模式中,对于某个具体业务而言,client 无需知道由谁来处理这个业务,怎么处理,它只需告诉工厂类这个业务的类型,并调用工厂类提供的静态方法拿到一个具体的产品,然后调用产品的业务方法来完成业务。就像巴扎黑去餐厅吃饭,他只要告诉服务员他想吃什么,然后自然会有一位师傅帮他做好,自己根本就不需要关心这个师傅是谁以及怎么做出这道美味菜肴。这样看来,吃饭这件事也不麻烦。
但是设想一下,巴扎黑很早就起床了,他想自己做菜来犒劳自己,他有充足的时间来准备。首先他得考虑想要吃什么,于是有了下面的思考:

// 伪代码
prepareToEat(food) {
    if (food == "鱼丸粗面") {
        去买鱼丸和粗面;
        放在锅里烧; 
        ...   
    } else if (food == "大盘鸡") {
        去菜场挑只鸡让大妈杀了,剁好装袋;
        买洋葱和其他配料;
        把鸡肉浸水洗净;
        洋葱切好;
        ...
    } else if (food == "红烧肉") {
        去菜场挑一块五花肉; // 一定要买带肥肉的,不然烧出来很难吃
        切块洗净放料酒浸泡;
        锅里放油煸炒肉块;
        ...
    }
    ...
}

要想吃点好的,脑袋里还得装一本厚厚的菜谱,想要尝尝新菜,还得不断丰富大脑里的菜谱,着实麻烦。有时候脑袋不够使,还会把菜谱记错,倒不如直接去餐厅点菜吃来的省事儿。

1.3 具体例子

1.3.1 场景

实现一个简单的翻译功能,要求:能够将一段中文文本翻译成不同的语言版本

1.3.2 代码

要实现翻译,我们得有一个翻译器。我们先定义一个抽象的翻译器(AbstractTranslator),用于描述所有翻译器都具备的功能——翻译(translate)

/**
 * 翻译器抽象类
 * 抽象产品,定义产品必须实现的方法
 *
 * @author piaolingluo
 * @date 2017-11-08
 */
public abstract class AbstractTranslator {

    @Autowired
    protected BaiduConfig baiduConfig;

    /**
     * 翻译
     *
     * @param content 待翻译的内容
     * @return 翻译的得到的内容
     */
    public abstract String translate(String content);
}

再定义一个具体的翻译器——英语翻译器(EnglishTranslator),继承AbstractTranslator,它能将一段中文翻译成英文

/**
 * 英语翻译器
 * 具体产品
 *
 * @author piaolingluo
 * @date 2017-11-08
 */
@Service
public class EnglishTranslator extends AbstractTranslator {

    @Autowired
    private Gson gson;

    @Override
    public String translate(String content) {
        TransApi api = new TransApi(baiduConfig.getAppId(), baiduConfig.getSecurityKey());
        String response = api.getTransResult(content, "auto", "en");
        TransResult result = gson.fromJson(response, TransResult.class);
        return result.getTransResult().get(0).getDst();
    }
}

再定义一个具体的翻译器——日语翻译器(JapaneseTranslator),继承AbstractTranslator,它能将一段中文翻译成日语

/**
 * 日语翻译器
 * 具体产品
 *
 * @author piaolingluo
 * @date 2017-11-08
 */
@Service
public class JapaneseTranslator extends AbstractTranslator {

    @Autowired
    private Gson gson;

    @Override
    public String translate(String content) {
        TransApi api = new TransApi(baiduConfig.getAppId(), baiduConfig.getSecurityKey());
        String response = api.getTransResult(content, "auto", "jp");
        TransResult result = gson.fromJson(response, TransResult.class);
        return result.getTransResult().get(0).getDst();
    }
}

接着,我们定义系统能能翻译的语言有哪些,如果后续支持新的语言,可以追加

/**
 * 语言枚举
 * 定义系统能翻译的语言
 *
 * @author piaolingluo
 * @date 2017-11-08
 */
@Getter
public enum LanguageEnum {

    CHINESE("chinese", "中文"),

    ENGLISH("english", "英语"),

    JAPANESE("japanese", "日语");

    private String code;

    private String name;

    LanguageEnum(String code, String name) {
        this.code = code;
        this.name = name;
    }

    public static LanguageEnum valueOfLanguage(String code) {
        return Stream.of(values())
                .filter(languageEnum -> languageEnum.getCode().equals(code))
                .findFirst()
                .orElse(null);
    }
}

接着,比较重要的一点是为不同的语言指定各自的实例,后续如果支持新的语言,可以在此扩展

/**
 * 语言类型与翻译器实例的映射
 *
 * @author piaolingluo
 * @date 2017-11-08
 */
@Getter
public enum TranslatorEnum {

    ENGLISH_TRANSLATOR(LanguageEnum.ENGLISH, "englishTranslator"),

    JAPANESE_TRANSLATOR(LanguageEnum.JAPANESE, "japaneseTranslator");

    /**
     * 语言
     */
    private LanguageEnum language;

    /**
     * 具体翻译器处理bean的名字
     */
    private String translatorName;

    TranslatorEnum(LanguageEnum language, String translatorName) {
        this.language = language;
        this.translatorName = translatorName;
    }

    public static TranslatorEnum valueOfTranslator(String translatorName) {
        return Stream.of(values())
                .filter(translatorEnum -> translatorEnum.getTranslatorName().equals(translatorName))
                .findFirst()
                .orElse(null);
    }

    public static LanguageEnum languageOfTranslator(String translatorName) {
        Optional<TranslatorEnum> optional = Optional.ofNullable(valueOfTranslator(translatorName));
        return optional.isPresent() ? optional.get().getLanguage() : null;
    }
}

最最重要的就是编写这个工厂类(Factory),初始化的时候,将所有的翻译器按各自能处理的语言类型存放在 TRANSLATOR_MAP 里,并提供一个静态方法,能够根据不同的语言拿到具体的翻译器

/**
 * 工厂
 *
 * @author piaolingluo
 * @date 2017-11-08
 */
@Component
public class Factory {

    private static final Map<LanguageEnum, AbstractTranslator> TRANSLATOR_MAP = new HashMap<>();

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        applicationContext.getBeansOfType(AbstractTranslator.class)
                .entrySet().stream()
                .filter(entry -> null != TranslatorEnum.valueOfTranslator(entry.getKey()))
                .forEach(entry -> TRANSLATOR_MAP.put(
                        TranslatorEnum.languageOfTranslator(entry.getKey()),
                        entry.getValue()));
    }

    /**
     * 根据语言枚举拿到指定语言的翻译器
     *
     * @param languageEnum 语言枚举
     * @return 指定语言的翻译器
     * @throws Exception 当拿不到翻译器时,抛出此异常
     */
    public static AbstractTranslator getTranslator(LanguageEnum languageEnum) throws Exception {
        AbstractTranslator translator = TRANSLATOR_MAP.get(languageEnum);
        if (null == translator) {
            throw new Exception("无法翻译成这种语言");
        }
        return translator;
    }

    /**
     * 根据语言编码拿到指定语言的翻译器
     *
     * @param languageCode 语言编码
     * @return 指定语言的翻译器
     * @throws Exception 当拿不到翻译器时,抛出此异常
     */
    public static AbstractTranslator getTranslator(String languageCode) throws Exception {
        return getTranslator(LanguageEnum.valueOfLanguage(languageCode));
    }
}

然后我们写个 TranslateController ,接收指定的语言编码和待翻译的文本,返回翻译后的文本

/**
 * 翻译服务
 *
 * @author piaolingluo
 * @date 2017-11-08
 */
@RestController
@RequestMapping("translate")
public class TranslateController {

    @GetMapping("{code}")
    public ResponseEntity<String> translate(@PathVariable("code") String code,
                                    @RequestParam(value = "content", required = false) String content) {
        try {
            // 调用工厂类的静态方法,传入语言编码,拿到具体的翻译器实例进行翻译
            return ResponseEntity.ok(Factory.getTranslator(code).translate(content));
        } catch (Exception e) {
            return ResponseEntity.ok(e.getMessage());
        }
    }
}

最后我们测试一下。

demo完整代码请戳这里

2、简单工厂模式在Logback源码中的应用

在大家经常使用的 Logback 中,也可以看到 LoggerFactory 中有多个重载的方法 getLogger()。

  public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
  }

  public static Logger getLogger(Class clazz) {
    return getLogger(clazz.getName());
  }

3、简单工厂模式在JDK源码中的应用

3.1 Calendar 类为例简单工厂模式

可以说简单工厂模式在 JDK 源码中无处不在,下面以 Calendar 类为例讲解简单工厂模式在 JDK 源码中的应用。Calendar 类的 getInstance() 方法源码如下。

public static Calendar getInstance() {
    Calendar cal = createCalendar(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT));
    cal.sharedZone = true;
    return cal;
}

进入 createCalendar() 方法中,源码如下:

private static Calendar createCalendar(TimeZone zone, Locale aLocale) {
    Calendar cal = null;

    String caltype = aLocale.getUnicodeLocaleType("ca");
    if (caltype == null) {
        // Calendar type is not specified.
        // If the specified locale is a Thai locale,
        // returns a BuddhistCalendar instance.
        if ("th".equals(aLocale.getLanguage())
                && ("TH".equals(aLocale.getCountry()))) {
            cal = new BuddhistCalendar(zone, aLocale);
        } else {
            cal = new GregorianCalendar(zone, aLocale);
        }
    } else if (caltype.equals("japanese")) {
        cal = new JapaneseImperialCalendar(zone, aLocale);
    } else if (caltype.equals("buddhist")) {
        cal = new BuddhistCalendar(zone, aLocale);
    } else {
        // Unsupported calendar type.
        // Use Gregorian calendar as a fallback.
        cal = new GregorianCalendar(zone, aLocale);
    }

    return cal;
}

Calendar 的 UML 类图如下:

3.2 JDBC简单工厂模式

当我们需要MySQL数据库的驱动时,我们就传MySQL的参数,用Oracle的就传相应的参数。在写JDBC的时候,JDK来实现的时候,

Class.forName("com.mysql.jdbc.Driver");

通过Class.forName把mysql的驱动加载进来,那如果写ORACLE的驱动呢,这里就变成对应的ORACLE的JDBC的jar包,ORACLE的driver类,然后调用DriverManager的getConnection方法,

@CallerSensitive
      public static Connection getConnection(String url)
          throws SQLException {
          java.util.Properties info = new java.util.Properties();
          return (getConnection(url, info,Reflection.getCallerClass()));         
     }

获取对应的数据库连接,JDBC的过程也是非常简单的,

      //  Worker method called by the public getConnection() methods.
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
 
        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }
 
        println("DriverManager.getConnection(\"" + url + "\")");
 
        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;
 
        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());
            }
 
        }
 
        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }
 
        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }

通过Class.forName这种方式,直接通过反射拿到对应的Video,只不过MSYQL这里面还是需要通过注册的,

        // Walk through the loaded registeredDrivers attempting to locate someone
        // who understands the given URL.
        for (DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerClass)) {
                try {
                    if(aDriver.driver.acceptsURL(url)) {
                        // Success!
                        println("getDriver returning " + aDriver.driver.getClass().getName());
                    return (aDriver.driver);
                    }

                } catch(SQLException sqe) {
                    // Drop through and try the next driver.
                }
            } else {
                println("    skipping: " + aDriver.driver.getClass().getName());
            }

        }

 因为这个可以看出来它是一个for循环,在遍历注册的一个驱动,
 
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
 
并且它是CopyOnWriteArrayList,里面是DriverInfo,初始化的时候他是一个空的,具体是什么时候完成注册的呢,

static {  
    try {  
        java.sql.DriverManager.registerDriver(new Driver());  
    } catch (SQLException E) {  
        throw new RuntimeException("Can't register driver!");  
    }  
}

这个时候就会在registerDriver(new Driver())这个方法里面直接注册这个Driver,那里面的Driver自然就是MySQL的Driver,

public static synchronized void registerDriver(java.sql.Driver driver,

            DriverAction da)

        throws SQLException {

 

        /* Register the driver if it has not already been added to our list */

        if(driver != null) {

            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));

        } else {

            // This is for compatibility with the original DriverManager

            throw new NullPointerException();

        }

 

        println("registerDriver: " + driver);

}

如果不存在就往里放

if(driver != null) {

registeredDrivers.addIfAbsent(new DriverInfo(driver, da));

}

4、简单工厂模式的优缺点

4.1 简单工厂模式的优点

工厂类是整个模式的关键,包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象。通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的。明确了各自的职责和权利,有利于整个软件体系结构的优化。

4.2 简单工厂模式的缺点

由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中。它所能创建的类只能是事先考虑到的,如果需要添加新的类,就需要改变工厂类了。

4.3 简单工厂模式的适用环境

(1) 工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂;
(2) 客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。



 


参考文章:
https://www.jianshu.com/p/72567cc1d63f

http://c.biancheng.net/view/8387.html

https://www.cnblogs.com/thiaoqueen/p/11169924.html

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值