Java 设计模式 -- 工厂方法模式

作者: ghwaphon | 来源:发表于2016-09-11 18:12 被阅读696次

    简单工厂模式(Simple Factory Pattern)

    定义一个工厂类,可以根据参数的不同返回不同类的实例,被创建的实例通常拥有共同的父类,因为简单工厂模式中用于创建实例的方法是静态方法,因此简单工厂模式又被称为静态工厂方法。
    假如有一家汽车厂,它可以生产很多类型的汽车,那么这个类我们可以设计为以下格式。

    public class CarFactory {
    
        public static final int AUDI = 0;
        public static final int BMW = 1;
        private int type;
    
        public CarFactory(int type) {
            this.type = type;
        }
    
        public void createCar() {
            if (type == AUDI) {
    
                // create AUDI
            } else if (type == BMW) {
    
                // create BMW
            }
        }
    }
    

    这个时候可以分析一下,这个类有以下缺点

    1.代码过于冗长,阅读难度,维护难度,测试难度增大。

    2.CarFactory 类的职责太重,因为它不仅包含 createAudi 而且还有 createBMW。

    3.包含了太多的 if...else 判断语句,一旦要类的需求发生变动,需要改变CarFactory 源代码,违反了开闭原则。

    4.客户端通过 new 操作创建和实例化CarFactory,也就是说二者耦合度过高,因为对象的创建和使用无法分离。


    那么使用简单工厂模式能做些什么呢?我们可以将 对象的创建和使用分离,也就是在客户端与生产之间搭建一个中间类,这个类通过控制产品类的创建返回给客户端一个期望的值。

    首先,我们需要为所有产品创建一个接口或者抽象类,那么什么时候使用抽象类什么时候使用接口呢?当所有的产品类中有共同的方法时,应该使用abstract类,否则使用接口。举例来说,如果所有汽车都需要盖上厂家自己的商标,那么所有类型的车都应该盖同一个标志,这就是一个共同的方法。

    public abstract class AbstractCar {
    
        public void setIcon(){
    
        }
    
        public abstract void show();
    }
    

    如果没有共同方法,使用interface 就足够了

      public interface InterfaceCar {
    
            public void show();
    }
    

    下面使用简单工厂模式再次实现上面那个汽车厂的功能。

      public class Audi implements InterfaceCar{
    
        @Override
        public void show() {
    
            // Create Audi
    
        }
    
    }
    
    public class BMW implements InterfaceCar{
    
        @Override
        public void show() {
            // Create BMW
        }
    
    }
    
    public class CarFactory {
    
        public static final int AUDI = 0;
        public static final int BMW = 1;
    
        public static InterfaceCar createCar(int type){
    
            if (type == AUDI) {
    
                return new Audi();
    
            } else if (type == BMW) {
    
                return new BMW();
    
            } else {
    
                return null;
            }
        }
    }
    

    可以看出简单工厂模式有以下优点:

    1.客户端可以免除创建一个工厂类的实例,并且直接通过工厂类的静态方法获得自己想要的产品,这样就降低了客户端与工厂类的耦合度,实现了对象的创建和使用分离。

    2.CarFactory 只通过调用产品实现类的方法去获得一个实例,并没有自己来实现它,也就是说现在这个类只有一个功能,那就是管理创建满足客户端的需求,符合单一职责原则。

    当然,这个时候我们需要创建具体的Car 对象时,还是需要到客户端中去修改我们需要的类型,每次修改客户端都需要重新编译,而且每次都是去修改主函数中的代码,这严重违背了开闭原则,有没有一种办法能够让我们既不修改客户端代码,又可以适应我们变化的需求呢?当然是可以的,但是需要借助于 xml。

    添加一个 cartype.xml 文件,内容如下

    <cartype>
        <type>Audi</type>
    </cartype>
    

    这个type 就是我们需要的那个车型,以后如果需要变化,只需要修改这个值即可,不再需要去修改主函数中的代码。当然,首先我们需要再定义一个函数去解析这个type 的值。这个函数的实现如下图所示

     public class XMLUtil {
    
    
        public static int getType(){    
            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                Document document = builder.parse(new File("cartype.xml"));
    
                NodeList list = document.getElementsByTagName("type");
                Node node = list.item(0).getFirstChild();
    
                String carType = node.getNodeValue().trim();
    
                if(carType.equals("Audi")){
                    return CarFactory.AUDI;
                }
                if(carType.equals("BMW")){
                    return CarFactory.BMW;
                }
    
            } catch (ParserConfigurationException e) {
                e.printStackTrace();
    
            } catch (SAXException e) {
                e.printStackTrace();
    
            } catch (IOException e) {
                e.printStackTrace();
    
            }
            return -1;
        }
    }
    

    注意,如果你使用的编译器是Eclipse,而且恰巧的是你在指定的包下创建了这个xml文件,那么这个时候会出现查找不到指定xml文件的错误,因为Eclipse中创建的xml文件是和我们 的源代码在同一目录的,解决的方法是复制一份xml文件到项目的根目录下。

    好了,这个时候简单工厂方法已经介绍完毕了,但是有一个突出的问题摆在了面前,工厂类中为什么还是存在if...else 判断语句,如果要添加车型,不还是要进工厂类添加if...else 语句吗?这不还是要违背开闭原则吗?解决这个问题最好的办法就是摒弃简单工厂模式,使用工厂方法模式。那么简单工厂模式存在的意义是什么?实际上简单工厂模式在只需要创建很少的对象而且基本上不会出现变动的情况下是比较合理的选择,因为这个时候if...else 语句比较少,逻辑还是比较清楚的,由于很少会出现变动,这个时候使用简单工厂模式比工厂方法模式要高效的多,原因在下面进行解释。

    工厂方法模式(Factory Method Pattern)

    在简单工厂模式中,通过一个InterfaceCar接口,将各类Car 的创建从 CarFactory类中分离出来,从一定程度上减轻了CarFactory 的负担,但是这么多类型的Car 都由CarFactory 负责创建,任务终究还是比较重的,所以我们可以用类似的方法,定义一个InterfaceFactory类,让一个特定的 Car 由 一个特定的 Factory 去创建,这样设计的话不仅符合单一职责原则,而且还有一个相当清晰的逻辑关系。

    附注:不要问我工厂方法模式的定义是什么,因为我也不知道。个人认为只要了解这个思想就可以,不必去硬记定义,毫无意义。

    将上述的CarFactory类换成接下来三个类

     public interface InterfaceFactory {    
        public InterfaceCar createCar();
    }
    
    public class AudiFactory implements InterfaceFactory{
    
        @Override
        public InterfaceCar createCar() {
            return new Audi();
        }
    
    }
    
    public class BMWFactory implements InterfaceFactory{
    
        @Override
        public InterfaceCar createCar() {
            return new BMW();
        }
    
    }
    
    public class Main {
    
        public static void main(String[] args) {
    
            InterfaceFactory factory = new AudiFactory();
    
            InterfaceCar car;
            car = factory.createCar();
            car.show();
        }
    }
    

    这样我们就可以通过在客户端指定工厂去生产指定车型的汽车了,不过和简单工厂方法模式有一个类似的问题,我们能不能每次不修改主函数中的代码,就能变更我们的需求,比如说,我现在想要一辆 BMW,主函数中是创建一个Audi,该怎么做才能既不修改源代码,又能满足我的需求,用类似的方法。

    首先介绍一个概念,Java 反射机制,也就是指在程序运行时获取已知名称的类或者已有对象的相关信息的一种机制。

    接下来看一下 xml文件中的内容

    <?xml version="1.0" encoding="UTF-8"?>
    
    <factory>
        <type>AudiFactory</type>
    </factory>
    

    这个时候的XML解析类如下面代码所示

     public class XMLUtil {
    
    
        @SuppressWarnings("rawtypes")
        public static Object getType(){    
            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                Document document = builder.parse(new File("factory.xml"));
    
                NodeList list = document.getElementsByTagName("type");
                Node node = list.item(0).getFirstChild();
    
                String mFactory = "com.testfactory."+node.getNodeValue();
    
    
                Class mClass = Class.forName(mFactory);
                Object mObject = mClass.newInstance();
                return mObject;    
    
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    

    附注:一定要注意,使用 Class.forname(name:String) 方法时,这个地方传进来的类名一定要是完整的路径,也就是包名+类名,否则就会抛出ClassNotFoundException异常。

    下面只需要在主函数中这样定义,就可以一劳永逸了,以后改变需求只需要到xml文件中去修改即可

     public class Main {
    
        public static void main(String[] args) {
    
            InterfaceFactory factory;
            InterfaceCar car;
    
            factory = (InterfaceFactory)XMLUtil.getType();
    
            car = factory.createCar();
            car.show();
        }
    }
    

    工厂方法模式的优点很显然,指定工厂生产指定产品,符合单一职责原则,增加产品类型的时候也只需要增添相应的工厂和产品实现类,无需对已有的代码进行改动,符合开闭原则。既然工厂方法模式这么好,为什么还存在上述的简单工厂模式呢?因为工厂方法模式有一个致命的缺点,就是每增添一个新的产品,就要增加两个类,即工厂类和产品类,也就是说,会有太多的类参与编译运行,会给系统带来不乐观的开销。

    相关文章

      网友评论

      • 椎椎隹木:大佬好,请问可不可以引用这篇文章?我会注明出处的
        椎椎隹木:@ghwaphon 谢谢
        ghwaphon:@椎椎隹木 ok
      • 0c9aed689d2f:首先,我们需要为所有产品创建一个接口或者抽象类,那么什么时候使用抽象类什么时候使用接口呢?当所有的产品类中有共同的方法时,应该使用abstract类,否则使用接口。举例来说,如果所有汽车都需要盖上厂家自己的商标,那么所有类型的车都应该盖同一个标志,这就是一个共同的方法。

        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        这一段不赞同,setIcon方法放在interface里面也没有什么区别。
        我的理解是:抽像类更侧重于属性的继承,接口更侧重于实现相同的方法,因为java的单继承问题,所以尽量多使用接口,这更利于设计中的组合原则。
        ghwaphon:@庞子元 你说的有道理,我说的这个意思就是如果包含重复的方法,而且方法中的代码相同,那么我们就会选择抽象类,因为这样的话我们只需要实现一次,而如果将其放在接口中,那么我们在每个实现类中都要实现。

      本文标题:Java 设计模式 -- 工厂方法模式

      本文链接:https://www.haomeiwen.com/subject/rpiuettx.html