4,简单工厂

59 阅读7分钟

1,前言

上一篇我们说了单例,按照难度来递进,这次轮到工厂了
工厂计划分三部分说:简单工厂,工厂方法,抽象工厂
三部分争取使用相似或有关联的例子进行递进式的讲解
这样一来,既可以实现三种工厂的对比,又可以说明根据需求变化工厂模式逐渐演变的过程
简单工厂实际并不属于一种设计模式,但这里我们就当成一种设计模式来说,主要是给后边两个模式做铺垫

2,场景

elm

外卖有各种饭,工厂这部分就用这些饭来做例子,将工厂模式用于制作各种饭的过程

简单工厂:

    餐厅制作鸡肉饭,烤肉饭,牛肉饭

    制作流程为:准备,烹饪,摆放,打包

3,不使用工厂的代码

简单的写一个类,根据传入的类型做饭

package com.brave.food;

import com.brave.food.simplefactory.rice.BarbecueRice;
import com.brave.food.simplefactory.rice.BeefRice;
import com.brave.food.simplefactory.rice.ChickenRice;
import com.brave.food.simplefactory.rice.Rice;

/**
 * 餐厅
 *  不使用设计模式的餐厅
 * @author Brave
 *
 */
public class Restaurant {

    public void orderRice(String type){

        Rice rice = null;

        if(type.equals("BarbecueRice")){
            rice = new BarbecueRice();
        } else if(type.equals("BeefRice")){
            rice = new BeefRice();
        } else if(type.equals("ChickenRice")){
            rice = new ChickenRice();
        }

        System.out.println("-----------"+type+"----------");

        rice.prepare();
        rice.cook();
        rice.set();
        rice.box();
    }
}

完善剩余代码:

饭的抽象类:

package com.brave.food.simplefactory.rice;

/**
 * Rice:饭抽象类
 *      各种饭继承此抽象类
 * @author Brave
 *
 */
public abstract class Rice {

    // 准备
    public void prepare(){
        System.out.println("准备");
    }

    // 烹饪
    public void cook(){
        System.out.println("烹饪");
    }

    // 摆放
    public void set(){
        System.out.println("摆放");
    }

    // 打包
    public void box(){
        System.out.println("打包");
    }
}

各种饭:继承上边饭的抽象类:

package com.brave.food.simplefactory.rice;

public class ChickenRice extends Rice {

    @Override
    public void prepare() {
        System.out.println("鸡肉饭-准备");
    }

    @Override
    public void cook() {
        System.out.println("鸡肉饭-烹饪");
    }

    @Override
    public void set() {
        System.out.println("鸡肉饭-摆放");
    }

    @Override
    public void box() {
        System.out.println("鸡肉饭-打包");
    }

}

客户端:

package com.brave.food;

/**
 * 客户端
 *  不使用设计模式的客户端
 * @author Brave
 *
 */
public class Client {

    public static void main(String[] args) {

        Restaurant restaurant = new Restaurant();

        restaurant.orderRice("BarbecueRice");
        restaurant.orderRice("BeefRice");
        restaurant.orderRice("ChickenRice");

    }

}

4,问题和分析

首先,这种写法简单明了,在实际项目中应用也很多,并没有什么问题
最大的问题不是代码本身,而是需求的变化

问题:
    Restaurant类,依赖了所有"饭"的具体类(BarbecueRice,BeefRice,ChickenRice)
    当饭的"产品种类"频繁变化时,就需要对这个类进行频繁修改,这个修改可能造成orderRice方法问题,从而影响到其他"饭"的创建

违反了几个设计原则:

    1,依赖倒置原则:
        依赖于抽象,不要依赖具体类,相比于"针对接口编程,不针对实现编程"来说,这里更强调抽象
        不能让高层组件依赖低层组件,并且,无论高层或是底层组件,两者都应依赖于抽象
        Restaurant是高层组件,各种"饭"的实现是低层组件,Restaurant依赖了所有"饭"的实现

    2,”开放-关闭”原则:
        对拓展开放,对修改关闭
        频繁维护饭的"产品种类",无法让orderRice()对修改关闭

所以我们应该找出变化的部分,将他们从不变的部分中分离出来

5,简单工厂

将频繁变化的部分创建为一个简单工厂类

package com.brave.food.simplefactory;

import com.brave.food.simplefactory.rice.BarbecueRice;
import com.brave.food.simplefactory.rice.BeefRice;
import com.brave.food.simplefactory.rice.ChickenRice;
import com.brave.food.simplefactory.rice.Rice;

/**
 * 简单工厂
 *      用于创建具体对象
 * @author Brave
 *
 */
public class SimpleRiceFactory {

    /**
     * 根据类型创建对应的"饭"对象
     * @param type
     * @return
     */
    public Rice createRice(String type){

        Rice rice = null;

        if(type.equals("BarbecueRice")){
            rice = new BarbecueRice();
        } else if(type.equals("BeefRice")){
            rice = new BeefRice();
        } else if(type.equals("ChickenRice")){
            rice = new ChickenRice();
        }

        return rice;
    }
}

修改餐厅的构造方法,使用工厂进行饭的实例化

package com.brave.food.simplefactory;

import com.brave.food.simplefactory.rice.Rice;

/**
 * 餐厅
 * @author Brave
 *
 */
public class Restaurant {

    SimpleRiceFactory factory;

    public Restaurant(SimpleRiceFactory factory){
        this.factory = factory;
    }

    public void orderRice(String type){

        Rice rice;

        rice = factory.createRice(type);

        System.out.println("-----------"+type+"----------");

        rice.prepare();
        rice.cook();
        rice.set();
        rice.box();
    }
}

客户端:

package com.brave.food.simplefactory;

/**
 * 客户端:
 *  仅仅依赖SimpleRiceFactory, Restaurant两个类
 *  将各种"饭"的制作流程和实例化过程封装起来
 *  当有新的"饭"加入时无需修改原有客户端代码,只需添加调用即可
 * @author Brave
 *
 */
public class Client {

    public static void main(String[] args) {

        // 创建简单工厂-肯定类型创建"饭"对象
        SimpleRiceFactory simpleFactory = new SimpleRiceFactory();

        // 注入工厂创建餐厅-使餐厅创建"饭"对象的过程受工厂对象实现的控制
        Restaurant restaurant = new Restaurant(simpleFactory);

        // 向指定了工厂对象的餐厅下单
        restaurant.orderRice("BarbecueRice");
        restaurant.orderRice("BeefRice");
        restaurant.orderRice("ChickenRice");

    }

}

6,简单工厂分析

简单工厂将变化的部分提取出来
Restaurant依赖Rice(抽象类),SimpleRiceFactory(简单工厂)
orderRice()仅需要消费简单工厂返回的对象即可
当饭的"产品种类"频繁变化时,Restaurant类无需修改

简单工厂的优点:
    去除了客户端与具体产品的依赖,封装了对象的创建过程

简单工厂的的不足:
    工厂类中包含了所有实例创建的逻辑,一旦工厂出问题,所有客户端都会有问题
    简单工厂的产品是基于共同的抽象类或接口创建的,所以当产品种类增加时(新的抽象类加入),工厂类就需要判断创建何种接口的产品,和创建何种种类的产品相互混淆,违背了单一职责原则,导致系统丧失灵活性和可维护性
    新增一个产品时,无法避免的,必须修改工厂类,但可以利用反射在一定程度上解决(仅限于产品类的构造及初始化相同的场景),所以很难满足严格意义上的"开放-关闭"原则

简单工厂还有一种实现方式是使用静态工厂方式,静态工厂虽然不需要实例化具体对象就可以使用,但丧失了通过继承改变行为的能力

7,利用反射弥补简单工厂拓展性

利用反射弥补简单工厂拓展性,从而在某种程度上实现"开放-关闭"原则

1,修改工厂类,通过反射创建对象

package com.brave.food.simplefactory.reflection;

import com.brave.food.simplefactory.rice.Rice;

/**
 * 简单工厂-反射实现
 *      利用反射弥补简单工厂拓展性,从而在某种程度上实现”开放-关闭”原则
 * 
 * @author Brave
 *
 */
public class SimpleRiceFactory {

    /**
     * 根据类型创建对应的"饭"对象
     * @param c
     * @return
     */
    public Rice createRice(Class c){

        Rice rice = null;

        try {
            rice = (Rice) Class.forName(c.getName()).newInstance();
        } catch (InstantiationException e) {
            System.out.println("不支持抽象类或接口");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            System.out.println("没有足够权限,即不能访问私有对象");
        } catch (ClassNotFoundException e) {
            System.out.println("类不存在");
            e.printStackTrace();
        }    

        return rice;
    }
}

修改调用:

package com.brave.food.simplefactory.reflection;

import com.brave.food.simplefactory.rice.Rice;

/**
 * 餐厅
 * @author Brave
 *
 */
public class Restaurant {

    SimpleRiceFactory factory;

    public Restaurant(SimpleRiceFactory factory){
        this.factory = factory;
    }

    public void orderRice(Class c){

        Rice rice;

        rice = factory.createRice(c);

        System.out.println("-----------"+c+"----------");

        rice.prepare();
        rice.cook();
        rice.set();
        rice.box();
    }
}
package com.brave.food.simplefactory.reflection;

import com.brave.food.simplefactory.rice.BarbecueRice;
import com.brave.food.simplefactory.rice.BeefRice;
import com.brave.food.simplefactory.rice.ChickenRice;

/**
 * 客户端:
 *  使用简单工厂-反射的方式实现对象的创建
 *  当有新的"饭"加入时,只需添加新的类,无需修改工厂类
 *  利用反射弥补简单工厂拓展性,从而在某种程度上实现”开放-关闭”原则
 * @author Brave
 *
 */
public class Client {

    public static void main(String[] args) {

        // 创建简单工厂-肯定类型创建"饭"对象
        SimpleRiceFactory simpleFactory = new SimpleRiceFactory();

        // 注入工厂创建餐厅-使餐厅创建"饭"对象的过程受工厂对象实现的控制
        Restaurant restaurant = new Restaurant(simpleFactory);

        // 向指定了工厂对象的餐厅下单
        restaurant.orderRice(BarbecueRice.class);
        restaurant.orderRice(BeefRice.class);
        restaurant.orderRice(ChickenRice.class);

    }

}

运行结果:

-----------class com.brave.food.simplefactory.rice.BarbecueRice----------
烤肉饭-准备
烤肉饭-烹饪
烤肉饭-摆放
烤肉饭-打包
-----------class com.brave.food.simplefactory.rice.BeefRice----------
牛肉饭-准备
牛肉饭-烹饪
牛肉饭-摆放
牛肉饭-打包
-----------class com.brave.food.simplefactory.rice.ChickenRice----------
鸡肉饭-准备
鸡肉饭-烹饪
鸡肉饭-摆放
鸡肉饭-打包

8,简单工厂在JDK中的应用

简单工厂在JDK中最典型的应用就是JDBC
把关系型数据库认为是抽象产品
MySQL,Oracle是具体产品
DriverManager是工厂类
应用程序通过JDBC接口使用关系型数据库,不需关心使用哪种数据库
直接使用DriverManager的静态方法去得到该数据库的Connection即可
package com.brave.simplefactory.jdk;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * 简单工厂在JDK中的使用
 * 
 * @author Brave
 *
 */
public class JDBC {

    public static void main(String[] args) {

        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/student");
            PreparedStatement ps = conn.prepareStatement("select * from mysql.test");
            ps.execute();
        } catch (SQLException ex) {
            System.out.println("Execute query failed " + ex);
        } catch (ClassNotFoundException e) {
            System.out.println("Load mysql driver failed " + e);
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                }
            }
        }

    }
}