【菜鸟学院】精通Spring框架——03工厂模式

源码:https://github.com/2020GetGoodOffer/springStudy


Spring是一个将设计模式用的淋漓尽致的经典框架,从类名就能看出,下面是一些列举:

设计模式名称 举例
工厂模式 BeanFactory
装饰者模式 BeanWrapper
代理模式 AopProxy
委派模式 DispatcherServlet
策略模式 HandlerMapping
适配器模式 HandlerAdapter
模板模式 JdbcTemplate
观察者模式 ContextLoaderListener

首先我们来学习工厂模式

简单工厂模式

简单工厂模式是指由一个工厂对象决定创建哪一种产品类的实例,但它不属于23种设计模式。简单工厂模式适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象不需要关心。

我们来看代码,还是以课程为例,定义一个课程标准ICourse接口:

public interface ICourse {
    //录制课程
    void record();
}

创建一个Java课程的实现类:

public class JavaCourse implements ICourse {
    
    public void record() {
        System.out.println("录制Java课程");    
    }
}

在客户端调用代码:

	public static void main(String[] args) {
        ICourse course=new JavaCourse();
        course.record();
    }

在上面的代码中,ICourse指向子类JavaCourse的引用,应用层代码需要依赖JavaCourse,如果业务继续扩展,那么客户端的以来会变得越来越臃肿。因此我们要想办法把这种依赖减弱,把创建细节隐藏起来。
使用简单工厂模式对代码进行优化,先增加课程类:

public class MySQLCourse implements ICourse {
    public void record() {
        System.out.println("录制MySQL课程");
    }
}

创建工厂类CourseFactory:

public class CourseFactory {
    
    public ICourse create(String name){
        if("java".equals(name))
            return new JavaCourse();
        else if ("mysql".equals(name))
            return new MySQLCourse();
        else 
            return null;
    }
}

修改客户端调用代码如下:

public static void main(String[] args) {
        CourseFactory factory = new CourseFactory();
        ICourse course = factory.create("java");
        course.record();
    }

客户端调用变简单了,但如果我们的业务继续扩展,要增加其他课程,那么工厂中的create方法就要每次根据产品的增加修改代码逻辑,不符合开闭原则。因此我们可以根据反射技术继续优化简单工厂模式:

public class CourseFactory {

    public ICourse create(String className){
        try{
            if(!(className==null||className.equals(""))){
                return (ICourse) Class.forName(className).newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

修改客户端调用代码:
这里方便明确学习内容使用了中文包命名,实际开发请使用英文

public static void main(String[] args) {
        CourseFactory factory = new CourseFactory();
        ICourse course = factory.create("工厂模式.简单工厂模式.JavaCourse");
        course.record();
    }

优化之后,产品不断丰富的过程中不需要修改CourseFactory中的代码,但还有个问题是方法参数是字符串,可控性有待提升而且需要强制类型转换,再修改一下代码:

public class CourseFactory {

    public ICourse create(Class<? extends ICourse> clazz){
        try{
            if(clazz!=null){
                return clazz.getConstructor().newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

优化客户端代码:

public static void main(String[] args) {
        CourseFactory factory = new CourseFactory();
        ICourse course = factory.create(JavaCourse.class);
        course.record();
    }

简单工厂模式的JDK实例

Calendar中的getInstance方法:
在这里插入图片描述

	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 (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;
    }

还有在logback中也使用到了简单工厂模式。
简单工厂模式的缺点就是工厂类的职责相对过重,不易于扩展复杂的产品结构。


工厂方法模式

工厂方法模式是指定义一个创建对象的接口,但让这个接口的实现类来决定实例化哪个类,工厂方法模式让类的实例化推迟到子类中进行。
在工厂方法模式中用户只需要关心所需产品对应的工厂,无需关心创建细节,而且加入新的产品时符合开闭原则。
工厂方法模式主要解决产品的扩展问题,在简单工厂模式中随着产品链的丰富,如果每个课程的创建逻辑有区别,则工厂的职责会变得越来越多,不利于维护。
根据单一职责原则我们将职能继续拆分,Java课程由Java工厂创建,MySql课程由MySQL工厂创建,对工厂本身也做一个抽象。
先创建一个ICourseFactory接口:

public interface ICourseFactory {
    ICourse create();
}

再分别创建子工厂:

public class JavaCourseFactory implements ICourseFactory {
    @Override
    public ICourse create() {
        return new JavaCourse();
    }
}
public class MySQLCourseFactory implements ICourseFactory{
    @Override
    public ICourse create() {
        return new MySQLCourse();
    }
}

测试代码如下:

public static void main(String[] args) {
        ICourseFactory factory=new JavaCourseFactory();
        ICourse course = factory.create();
        course.record();
        
        factory=new MySQLCourseFactory();
        course = factory.create();
        course.record();
    }

适合场景

工厂方法模式适合:
(1)创建对象需要大量重复代码
(2)客户端(应用层)不依赖于产品实例如何被创建、如何被实现等细节
(3)一个类通过其子类来指定创建哪个对象
缺点:
(1)类的个数容易过多,增加复杂度
(2)增加了系统的抽象性和理解难度


抽象工厂模式

抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无需指定它们的具体类。
客户端(应用层)不依赖于产品类实例如何被创建如何被实现等细节,强调的是一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复代码。需要提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖具体实现。

讲解抽象工厂模式之前,我们先学习两个概念:产品等级结构和产品族。产品族就相当于移动、联通、电信之间的关系,产品等级就相当于移动品牌中的手机、路由器、笔记本之间的关系。

继续以课程举例,假设除了课堂视频外还需要提供课堂笔记,在产品等级中增加两个产品IVideo和INote:

public interface IVideo {
    void record();
}

public interface INote {
    
    void edit();
}

然后创建一个抽象工厂接口CourseFactory:

//抽象工厂是用户的主入口,是Spring中应用的最广泛的设计模,易于扩展
public interface CourseFactory {
    INote createNote();
    IVideo createVideo();
}

接下来,创建Java产品族的Java视频类和Java笔记类:

public class JavaVideo implements IVideo{
    @Override
    public void record() {
        System.out.println("录制Java视频");
    }
}
public class JavaNote implements INote {
    @Override
    public void edit() {
        System.out.println("编写Java笔记");
    }
}

创建Java产品族的具体工厂JavaCourseFactory:

public class JavaCourseFactory implements CourseFactory {
    
    @Override
    public INote createNote() {
        return new JavaNote();
    }

    @Override
    public IVideo createVideo() {
        return new JavaVideo();
    }
}

然后创建MySQL产品的视频类和笔记类:

public class MySQLVideo implements IVideo {
    @Override
    public void record() {
        System.out.println("录制MySQL课程");
    }
}

public class MySQLNote implements INote {
    @Override
    public void edit() {
        System.out.println("编写MySQL笔记");
    }
}

创建MySQL产品族的具体工厂MySQLCourseFactory:

public class MySQLCourseFactory implements CourseFactory {
    @Override
    public INote createNote() {
        return new MySQLNote();
    }

    @Override
    public IVideo createVideo() {
        return new MySQLVideo();
    }
}

客户端调用代码:

public class Test {

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

结构图:
在这里插入图片描述
上面的代码完整描述了两个产品族Java和MySQL,也描述了两个产品等级视频和笔记。抽象工厂模式完美清晰地描述了这样一层复杂的关系。

但是如果我们继续要扩展产品等级,将源码Source也加入课程,那么我们的代码要从抽象工厂全部调整,很显然不符合开闭原则。由此可知抽象工厂模式也是有缺点的:①规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂接口。②增加了系统抽象性和理解难度。

但在实际开发中,我们我们可以根据情况不遵循开闭原则,代码每半年或一年升级一次未尝不可。


利用工厂模式重构

通过一个JDBC的案例来演示,我们每次操作都需要重新创建数据库连接,这样十分消耗性能。
我们利用工厂模式将数据库连接先创建好,放到容器中缓存,在业务调用时就只需要现取现用:
数据库连接池的抽象类:

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;
    
    //私有构造方法 不允许外部访问
    private Pool(){
        try{
            init();
            loadDrivers(driverName);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //初始化配置
    private void init() throws IOException {
        InputStream is = Pool.class.getResourceAsStream(propertiesName);
        Properties properties = new Properties();
        properties.load(is);
        this.driverName=properties.getProperty("driverName");
        this.maxConnect=Integer.parseInt(properties.getProperty("maxConnect"));
        this.normalConnect=Integer.parseInt(properties.getProperty("normalConnect"));
    }
    
    //装载和注册所有JDBC驱动程序
    private void loadDrivers(String driverName){
        try{
            driver= (Driver) Class.forName(driverName).getConstructor().newInstance();
            DriverManager.registerDriver(driver);
            System.out.println("成功注册JDBC驱动程序"+ driverName);
        }catch (Exception e){
            System.out.println("无法注册JDBC驱动程序"+ driverName +",错误:"+e);
        }
    }
    
    //创建连接池
    public abstract void createPool();
    
    //单例模式返回数据库连接池实例
    public static synchronized Pool getInstance() throws Exception{
        if(instance!=null)
            instance= (Pool) Class.forName("org.jdbc.sqlhelper.Pool").getConstructor().newInstance();
        return instance;
    }
    
    //获得一个可用连接,如果没有则创建,且小于最大连接限制
    public abstract Connection getConnection();
    
    //获得一个连接,有时间限制
    public abstract Connection getConnection(long time);
    
    //将连接对象返回给连接池
    public abstract void freeConnection(Connection connection);
    
    //返回当前空闲连接数
    public abstract int getNum();
    
    //返回当前工作连接数
    public abstract int getNumActive();
    
    //撤销驱动
    protected synchronized void release(){
        try{
            DriverManager.deregisterDriver(driver);
            System.out.println("撤销JDBC驱动"+driver.getClass().getName());
        }catch (Exception e){
            System.out.println("无法撤销JDBC驱动"+driver.getClass().getName());
        }
    }
}

DBConnectionPool数据库连接池:

public class DBConnectionPool extends Pool {
    
    //正在使用的连接数
    private int checkOut;
    //存放产生的连接对象容器
    private Vector<Connection> freeConnections=new Vector<>();
    //数据库连接信息
    private String url;
    private String userName;
    private String passWord;
    //空闲连接数
    private static int num;
    //当前可用连接数
    private static int numActive=0;
    //连接池实例
    private static DBConnectionPool pool=null;

    //产生数据库连接池
    private DBConnectionPool(){
        try {
            init();
            for(int i=0;i<normalConnect;i++){
                Connection connection=newConnection();
                if(connection!=null){
                    freeConnections.addElement(connection);//往容器中添加连接对象
                    num++;//记录总连接数
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    
    //获得一个数据库连接池实例
    public static synchronized DBConnectionPool getInstance(){
        if(pool==null)
            pool=new DBConnectionPool();
        return pool;
    }
    
    //初始化
    private void init() throws IOException{
        InputStream is = Pool.class.getResourceAsStream(propertiesName);
        Properties properties = new Properties();
        properties.load(is);
        this.userName=properties.getProperty("userName");
        this.passWord=properties.getProperty("passWord");
        this.driverName=properties.getProperty("driverName");
        this.url=properties.getProperty("url");
        this.maxConnect=Integer.parseInt(properties.getProperty("maxConnect"));
        this.normalConnect=Integer.parseInt(properties.getProperty("normalConnect"));
    }
    
    //如果不再使用某个连接对象,释放其到连接池
    public synchronized void freeConnection(Connection connection){
        freeConnections.addElement(connection);
        num++;
        checkOut--;
        numActive--;
        notifyAll();
    }
    
    //创建一个新连接
    private Connection newConnection(){
        Connection connection=null;
        try{
            if(userName==null)
                connection= DriverManager.getConnection(url);
            else 
                connection=DriverManager.getConnection(url,userName,passWord);
            System.out.println("连接池创建一个新的连接");
        }catch (Exception e){
            System.out.println("无法创建这个url的连接"+url);
            return null;
        }
        return connection;
    }
    
    //返回空连接
    public int getNum(){
        return num;
    }
    
    //返回当前连接数
    public int getNumActive(){
        return numActive;
    }
    
    //单例模式获取可用连接
    public synchronized Connection getConnection(){
        Connection connection=null;
        if(freeConnections.size()>0){
            num--;
            connection=freeConnections.firstElement();
            freeConnections.remove(0);
            try{
                if(connection.isClosed()){
                    System.out.println("从连接池中删除一个无效连接");
                    connection=getConnection();
                }
            }catch (Exception e){
                System.out.println("从连接池中删除一个无效连接");
                connection=getConnection();
            }
        }else if(maxConnect==0||checkOut<maxConnect) {//没有空闲连接且当前连接小于最大允许值 最大值为0则不限制
            connection = newConnection();
        }
        if(connection!=null)
            checkOut++;
        numActive++;
        return connection;
    }
    
    //获取一个连接,加上等待时间限制,单位ms
    public synchronized Connection getConnection(long timeout){
        long startTime=new Date().getTime();
        Connection connection;
        while ((connection=getConnection())==null){
            try{
                wait(timeout);
            }catch (Exception ignored){}
            if(new Date().getTime()-startTime>=timeout)
                return null;
        }
        return connection;
    }
    
    //关闭所有连接
    public synchronized void release(){
        try{
            Enumeration<Connection> allConnections = freeConnections.elements();
            while (allConnections.hasMoreElements()){
                Connection connection=allConnections.nextElement();
                try{
                    connection.close();
                    num--;
                }catch (SQLException e){
                    System.out.println("无法关闭连接");
                }
            }
        }finally {
            super.release();
        }
    }
    
    //建立连接池
    public void createPool(){
        pool=new DBConnectionPool();
        if(pool!=null)
            System.out.println("连接池创建成功");
        else
            System.out.println("连接池创建失败");
    }
}

发布了92 篇原创文章 · 获赞 426 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qq_41112238/article/details/105468816