设计模式必知必会:三种工厂方法之对比

在开发中,有没有试过使用工厂方法呢,不同的工厂方法之间又有什么不同呢,今天就来好好讲一讲。本文假设读者都已经了解了三种工厂方法,所以对三种工厂方法的细节不再赘述。
首先我们总共有三种工厂:

  1. 简单工厂模式
  2. 工厂方法模式
  3. 静态工厂模式

不同之处

有什么不同?看代码:

简单工厂模式

class Father{
}

class ChildA extends Father{
}

class ChildB extends Father{
}

class ChildFactory {
    public Father IWantChild(char type){
        switch(type){
            case 'A':return new ChildA();
            case 'B':return new ChildB();
        }
        return null;
    }
}

是不是很清晰,我们只要调用工厂的IWantChild方法,就可以实现创造出不同的子类,使用起来非常方便。

工厂方法模式

在来看看工厂方法模式:

class Father{
}

class ChildA extends Father{
}

class ChildB extends Father{
}

interface IFactory {
    public Father IWantChild();
}

class AFactory implements IFactory{
    @Override
    public Father IWantChild() {
        return new ChildA();
    }
}

class BFactory implements IFactory{
    @Override
    public Father IWantChild() {
        return new ChildB();
    }
}

简单工厂模式 对比 工厂方法模式

看上去两种工厂方法没有什么不同呀,为什么要这么使用呢?让我们来细细思考,从定义上看似乎没有什么问题,现在是只有ChildA和ChildB两个,假如现在我们需要ChildC了,该怎么改呢?
对于简单工厂模式,我们需要做什么,没错,就是去对工厂ChildFactory的IWantChild方法进行修改,添加一个新的分支,同时新建一个新的ChildC类并且这个类继承于Father类,不难对吧,但是如果我们是写库给别人用,且不希望别人修改我们的代码,那么别人要如何去生成新的ChildC类呢,难道还是使用new ChildC吗,这就非常不好了。
对于工厂方法模式,我们需要做什么呢,简单,继承IFactory实现ChildC的工厂,同时继承Father,实现新的ChildC类,使用上非常方便,且不需要去修改原有的代码。很好。

静态工厂方法

代码如下:

class Father{
}

class ChildA extends Father{
}

class ChildB extends Father{
}

class ChildFactory{
    public static Father IWantChildA(){
        return new ChildA();
    }

    public static Father IWantChildB(){
        return new ChildB();
    }
}

看起来感觉上是不是很多余,相对于工厂方法模式,似乎还少了一些可扩展性,但是别忘了,静态工厂的使用场景与上面两种工厂方法的使用场景是不完全相同的,在特定的使用场景下,相信你也会更倾向于使用静态工厂的。
不管这些,从上面的代码中,静态工厂有什么优势?第一、我们可以返回任何子类型的对象;第二、我们在创建参数化的实例时,代码更加简洁,例如上面的,只需要调用静态工厂方法,而不需要去填写参数;另外还有呢?对的,有名字,这个特点比起工厂方法似乎没什么优势,这是因为上面这种写法不够直观,看看下面这种写法:

class Child {
    private char ch;

    private Child(char ch){
        this.ch = ch;
    }

    public static Child ChildA(){
        return new Child('A');
    }

    public static Child ChildB(){
        return new Child('B');
    }
}

看,这种写法可以有效的按照要求生成类,同时我们可以隐藏我们的构造器,让外界的调用只能基于静态工厂,而不能调用到我们的构造器,安全性更好,且这种写法不需要每次都创建新的对象,让对象的创建都掌握在我们的手中:

class Child {
    private char ch;
    private static Child child = null;

    private Child(){
    }

    public static Child ChildA(){
        if(child == null){
            child = new Child();
        }
        child.ch = 'A';
        return child;
    }

    public static Child ChildB(){
        if(child == null){
            child = new Child();
        }
        child.ch = 'B';
        return child;
    }
}

但是静态工厂方法有没有什么局限呢?看上面这段代码就明白了,如果构造器不是公有的或者可继承的,那么我们就无法创建子类。
另外还有一个极其严重的实例,假如用户在新建这个类实例的时候,需要传递30个参数,那么我们该怎么办?当然30个是有点多了,也正好可以说明问题,传递30个参数,很容易就导致参数的传递错误,非常不好的体验,尤其是几个参数类型什么的都一样,仅仅是功能不同,那么就会导致程序无法运行出正确的结果。
有没有解决办法?

Builder模式
class Child {
    private char ch;
    private Child(char ch){
        this.ch = ch;
    }

    public static class Builder{
        private char ch;

        public Builder setCh(char ch){
            this.ch = ch;
            return this;
        }

        public Child build(){
            return new Child(ch);
        }
    }
}

如上所示,我们只向外界暴露我们的建造器Builder,Builder通过set方法设置参数,这样我们就可以通过set的名字来相应的设置不同的参数,最后在Builder中统一调用构造函数进行返回,就不容易出错了。同时,我们也可以在set的时候进行判断和提示,在build中根据参数的不同返回子类,使用上非常方便。有没有问题?有的,问题还是有的,在我们的代码中,就相当于需要两个ch,如果参数很多很长,且参数占用的空间比较大,那么就容易导致浪费。不过这种模式还是很常用的。

后记

本文比较了三种工厂方法,对不同的工厂方法的使用做了一定的说明,还是那句话,根据自己的应用场景来进行选择,适合的才是最好的。

你可能感兴趣的:(java)