zoukankan      html  css  js  c++  java
  • 设计模式 | 简单工厂模式及典型应用

    前言

    设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。

    本文主要介绍简单工厂模式及典型应用,内容如下:

    • 简单工厂模式的介绍
    • 简单工厂模式的典型应用及源码分析
      • Calendar 类获取日历类对象
      • JDBC 获取数据库连接
      • LoggerFactory 获取 Logger 对象

    简单工厂模式

    工厂模式是最常用的一类创建型设计模式,包括 抽象工厂模式,工厂方法模式和简单工厂模式 这三种,简单工厂模式是其中最简单的一种

    简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。

    因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式,但不属于GOF23种设计模式

    角色

    Factory(工厂角色):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑;工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product

    Product(抽象产品角色):它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。

    ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法

    在简单工厂模式中,客户端通过工厂类来创建一个产品类的实例,而无须直接使用new关键字来创建对象,它是工厂模式家族中最简单的一员

    示例

    抽象产品类 Video,定义了抽象方法 produce()

    public abstract class Video {
        public abstract void produce();
    }
    

    具体产品类 JavaVideo 和 PythonVideo,都继承了抽象产品类 Video

    public class JavaVideo extends Video {
        @Override
        public void produce() {
            System.out.println("录制Java课程视频");
        }
    }
    
    public class PythonVideo extends Video {
        @Override
        public void produce() {
            System.out.println("录制Python课程视频");
        }
    }
    

    工厂类实现的两种方法:使用if-else判断和使用反射来创建对象

    public class VideoFactory {
        /**
         * 使用if else 判断类型,type 为 Java 则返回 JavaVideo, type为Python则返回 PythonVideo
         */
        public Video getVideo(String type) {
            if ("java".equalsIgnoreCase(type)) {
                return new JavaVideo();
            } else if ("python".equalsIgnoreCase(type)) {
                return new PythonVideo();
            }
            return null;
        }
    
        /**
         * 使用反射来创建对象
         */
        public Video getVideo(Class c) {
            Video video = null;
            try {
                video = (Video) Class.forName(c.getName()).newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return video;
        }
    }
    

    使用一个客户端来调用工厂类

    public class Test {
        public static void main(String[] args) {
            VideoFactory videoFactory = new VideoFactory();
            Video video1 = videoFactory.getVideo("python");
            if (video1 == null) {
                return;
            }
            video1.produce();
    
            Video video2 = videoFactory.getVideo(JavaVideo.class);
            if (video2 == null) {
                return;
            }
            video2.produce();
        }
    }
    

    输出

    录制Python课程视频
    录制Java课程视频
    

    示例.简单工厂模式类图

    Test 类通过传递参数给 VideoFactory.getVideo() 来获取对象,创建对象的逻辑交给了工厂类 VideoFactory 来完成

    简单工厂模式总结

    简单工厂模式的主要优点如下:

    • 工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的职责,而仅仅“消费”产品,简单工厂模式实现了对象创建和使用的分离。
    • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以在一定程度减少使用者的记忆量。
    • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

    简单工厂模式的主要缺点如下:

    • 由于工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响。
    • 使用简单工厂模式势必会增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度。
    • 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护,且违背开闭原则。
    • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

    适用场景

    • 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
    • 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。

    简单工厂模式的典型应用及源码分析

    Calendar 类获取日历类对象

    Calendar 抽象类,该类的子类有 BuddhistCalendarJapaneseImperialCalendarGregorianCalendarRollingCalendar

    getInstance方法,根据参数获取一个Calendar子类对象,该方法实际将参数传给 createCalendar 方法,createCalendar 在根据参数通过 providerswitch 或者 if-else 创建相应的子类对象

    以下为 Java8 中的 Calendar 类代码,Java7 中的实现为 if-else 方式

    public static Calendar getInstance(TimeZone zone, Locale aLocale) {
        return createCalendar(zone, aLocale);
    }
    
    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) {
            }
        }
    
        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 (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;
    }
    

    Calendar的继承关系

    可以看到抽象产品角色工厂角色都由 Calendar 担任,具体产品角色Calendar 的子类担任

    JDBC 获取数据库连接

    一般JDBC获取MySQL连接的写法如下:

    //加载MySql驱动
    Class.forName("com.mysql.jdbc.Driver");
    DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "123456");
    

    首先通过反射加载驱动类 com.mysql.jdbc.Driver 类,然后再通过 DriverManager 获取连接

    看看 com.mysql.jdbc.Driver 的代码,该类主要的内容是静态代码块,其会随着类的加载一块执行

    public class Driver extends NonRegisteringDriver implements java.sql.Driver {
        public Driver() throws SQLException {
        }
        static {
            try {
                DriverManager.registerDriver(new Driver());
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        }
    }
    

    静态代码块:new 一个 Driver 类并注册到 DriverManager 驱动管理类中

    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 {
            throw new NullPointerException();
        }
        println("registerDriver: " + driver);
    }
    

    其中的 registeredDrivers 是一个 CopyOnWriteArrayList 对象

    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    

    CopyOnWriteArrayList是Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可以称这种容器为"写时复制器",Java并发包中类似的容器还有CopyOnWriteSet
    一篇CopyOnWriteArrayList的文章:https://www.cnblogs.com/chengxiao/p/6881974.html

    再通过 DriverManager.getConnection 获取连接对象的主要代码如下:通过for循环从已注册的驱动中(registeredDrivers)获取驱动,尝试连接,成功则返回连接

    private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
        // ...省略...
        println("DriverManager.getConnection("" + url + "")");
        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());
            }
        }
        // ...省略...
    }
    

    Connection 接口及子类实现关系

    工厂角色为 DriverManager 类,抽象产品角色为 Connection,具体产品角色则很多

    Logback 中的 LoggerFactory 获取 Logger 对象

    查看 LoggerFactory 类的 getLogger 方法,可看到调用了 iLoggerFactory.getLogger(),其中 iLoggerFactory 是一个接口

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

    iLoggerFactory 接口只有一个 getLogger 方法

    public interface ILoggerFactory {
        Logger getLogger(String var1);
    }
    

    查看其子类依赖关系

    iLoggerFactory接口子类的依赖关系

    再看一个子类 LoggerContext 对 ILoggerFactory 的实现

    image

    可看到这是通过 if-else 方式的简单工厂模式

    Logger 接口及子类实现关系

    工厂角色为 iLoggerFactory 接口的子类如 LoggerContext,抽象产品角色为 Logger,具体产品角色为 Logger 的子类,主要是 NOPLoggerLogger

    参考:
    刘伟:设计模式Java版
    慕课网java设计模式精讲 Debug 方式+内存分析

    后记

    欢迎评论、转发、分享,您的支持是我最大的动力

    更多内容可访问我的个人博客:http://laijianfeng.org

    关注【小旋锋】微信公众号,及时接收博文推送

    推荐阅读:设计模式系列文章

  • 相关阅读:
    ECharts之柱状图 饼状图 折线图
    Vue自定义指令(directive)
    HDU 1231 最大连续子序列
    POJ 2533 Longest Ordered Subsequence
    HDU 1163 Eddy's digital Roots
    HDU 2317 Nasty Hacks
    HDU 2571 命运
    HDU 4224 Enumeration?
    HDU 1257 最少拦截系统
    HDU 2740 Root of the Problem
  • 原文地址:https://www.cnblogs.com/whirly/p/10047650.html
Copyright © 2011-2022 走看看