【代码重构设计模式之运用】之工厂方法模式的几种实现方式

作为一名程序猿,我们每天或多或少写不少代码,但是写出来的代码是不是扩展性良好呢?是不是便于读者阅读呢?是不是突然有一天,某位同事修改你的代码,不是背后骂你“哪个家伙写的代码!鄙视他!!!”而是惊叹曰“这是哪位施主写的代码!!咋就写的这般优美,让老衲好端品味!!!”

1.前言

菜鸟最近出去面试,发现有的面试官就问设计模式(就比如我就会经常问到,O(∩_∩)O哈哈~),菜鸟呢就听过几个常用的设计模式(什么单例啦,工厂啦)。这时,菜鸟就找到大神请教一番。

“大神,为啥现在有的面试官经常问到设计模式啊?”

“菜鸟,那可不是,设计模式在编程中也是十足重要啊!”

“为啥呢?”

“设计模式都是前辈积累的经验,对于写出扩展性的代码起到良好作用。有了设计模式,我们才能写出优雅的代码,便于后期的扩展。另外如果代码设计比较良好,对于需求的扩展性,从研发的角度来讲,你的代码扩展不会扩展影响面,有利于规避风险。”

“但是我看到大多数同事的代码,也没见到用到设计模式啊!

“那只能说,就像你们使用SpringMVC,虽然日常就是写各种controller,service,mapper,entity,大家只需要定义和实现。但是,大多数的人在service依然是基于函数(一堆堆方法)写出一大坨的代码,使得service臃肿,不便于后期扩展维护,阅读性也比较差”。

“那可不是,大家都是如此啊!”

“那为啥不能好好的设计一下呢,这样我们对于后面的同事去维护你的代码,就能轻松的实现需求的扩展”。

“这么说来,设计模式很重要了,那你给我讲讲工厂模式吧”。

“好,那我就把毕生所学传授给吧!!”

“太好了!!”

2.工厂方法模式

所谓正统工厂方法,也就是我们经常常用的工厂方法模式,它具有什么样特征呢?
1、产品类继承抽象类或者实现共同的接口(比如下面我们实现了Car接口)。
2、工厂类实现接口,接口方法是产品子类共同的抽象类或接口(比如我们的CarFactory,其Car就是我们产品锁共同实现的的接口)。
3、工厂类通过枚举、字符串等获得对应产品类型,以返回对应的对象实例。

/**
 * @author Seig Heil
 * @date 2019/5/30 21:30
 * @desc 车接口
 */
public interface Car {
    /**
     * 获取名称
     * @return
     */
    String getName();
}

/**
 * @author Seig Heil
 * @date 2019/5/30 21:31
 * @desc 奥迪车
 */
public class AudiCar implements Car {
    @Override
    public String getName() {
        return "我是一辆奥迪车";
    }
}

/**
 * @author Seig Heil
 * @date 2019/5/30 21:32
 * @desc 宝马车
 */
public class BwmCar implements Car {
    @Override
    public String getName() {
        return "我是一辆宝马车";
    }
}

/**
 * @author Seig Heil
 * @date 2019/5/30 21:34
 * @desc 车工厂接口
 */
public interface Factory {
    /**
     * 创建车
     * @return
     */
    Car create(Type type);

    /**
     * 车类型
     */
    enum Type{
        Audi,Bwm
    }
}

/**
 * @author Seig Heil
 * @date 2019/5/30 21:33
 * @desc 车工厂
 */
public class CarFactory implements Factory{
    @Override
    public Car create(Type type) {
        switch (type){
            case Audi:
                return new AudiCar();
            case Bwm:
                return new BwmCar();
        }
        return null;
    }
}

客户端测试类

/**
 * @author Seig Heil
 * @date 2019/5/30 21:40
 * @desc
 */
public class Client {
    public static void main(String[] args) {
        CarFactory factory = new CarFactory();
        Car audi = factory.create(Factory.Type.Audi);
        System.out.println(audi.getName());
        Car bwm = factory.create(Factory.Type.Bwm);
        System.out.println(bwm.getName());
    }
}

控制台输出

我是一辆奥迪车
我是一辆宝马车

总结,这种工厂方法模式,也是最常见的一种;但是它有个弊端,就是如果增加一种产品类,那么咱们的工厂类的方法就需要修改,这就不符“开闭原则”(对修改关闭,对扩展开放)。

3.抽象工厂模式

特征归纳:(1)、产品类独享各自的工厂类,工厂接口或抽象类;(2)、工厂接口或抽象类返回参数是产品类的基类或接口。

先定义一个车辆工厂接口,返回对象是车接口。

/**
 * @author Seig Heil
 * @date 2019/5/30 21:34
 * @desc 车工厂接口
 */
public interface CarFactory {
    /**
     * 创建车
     * @return
     */
    Car create();
}

奥迪车和宝马车各自实现各自工厂

/**
 * @description: 奥迪车工厂类
 * @Date : 2019/8/6 下午4:37
 * @Author : 石冬冬-Seig Heil
 */
public class AudiCarFactory implements CarFactory {
    @Override
    public Car create() {
        return new AudiCar();
    }
}

/**
 * @description: 宝马车工厂类
 * @Date : 2019/8/6 下午4:37
 * @Author : 石冬冬-Seig Heil
 */
public class BwmCarFactory implements CarFactory {
    @Override
    public Car create() {
        return new BwmCar();
    }
}

写个测试类

/**
 * @description: 测试类
 * @Date : 2019/8/6 下午5:06
 * @Author : 石冬冬-Seig Heil
 */
public class Client {
    public static void main(String[] args) {
        CarFactory audiCarFactory = new AudiCarFactory();
        System.out.println(audiCarFactory.create().getName());
        CarFactory bwmCarFactory = new BwmCarFactory();
        System.out.println(bwmCarFactory.create().getName());
    }
}

归纳:从上述例子我们可以看出,对于不同种类的产品有各自的工厂,同种产品可以派生子类即可;不同种类的工厂只需有最上层接口或抽象类即可,这样对于扩展一个新产品类,只需要增加一个工厂,以及对应工厂产品类即可。这就符合“开闭原则“,但是呢会产生更多的类,但是往往我们实际场景中很少有这种需求。

4.静态工厂模式

对于工厂方法模式,我们可以对工厂类进行简化,就是省略工厂接口。

/**
 * @author Seig Heil
 * @date 2019/5/30 21:46
 * @desc 静态工厂实现
 * 区别:
 * (1)、工厂类无需实现接口
 * (2)、提供静态方法调用
 */
public final class SimpleCarFactory {
    /**
     * 创建产品
     * @param type
     * @return
     */
    public final static Car create(Type type){
        switch (type){
            case Audi:
                return new AudiCar();
            case Bwm:
                return new BwmCar();
        }
        return null;
    }
    public static enum Type{
        Audi,Bwm
    }
    private SimpleCarFactory(){}
}

我们对比工厂方法模式,其实发现,就是不再实现工厂接口,而是提供一个静态方法,似乎清爽了许多。

客户端测试类

/**
 * @author Seig Heil
 * @date 2019/5/30 21:40
 * @desc
 */
public class Client {
    public static void main(String[] args) {
        Car audi = SimpleCarFactory.create(SimpleCarFactory.Type.Audi);
        System.out.println(audi.getName());
        Car bwm = SimpleCarFactory.create(SimpleCarFactory.Type.Bwm);
        System.out.println(bwm.getName());
    }
}

5.静态工厂+反射实现

综合上述两种实现方式,无论工厂方法模式还是静态工厂模式,我们对于增加产品,对应的生产产品的方法都需要修改(比如哪两种,就需要新增枚举);这时,我们可以采用放射方式。

/**
 * @author Seig Heil
 * @date 2019/5/30 21:46
 * @desc 静态工厂实现
 * 区别:
 * (1)、工厂类无需实现接口
 * (2)、提供静态方法调用
 * (3)、通过反射实现,用户端只需要提供子类.class即可。
 */
public final class SimpleCarFactory {
    /**
     * 创建产品
     * @param clazz
     * @return
     */
    public final static Car create(Class<? extends Car> clazz){
        try {
            return (Car)Class.forName(clazz.getName()).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    private SimpleCarFactory(){}
}

我们可以看到方法参数采用界定 Class<? extend Car>,这意味着传入的参数一定是实现Car接口,所以不会让客户端调用随便传入。

比如如下一个类HondaCar。

/**
 * @author Seig Heil
 * @date 2019/5/30 21:56
 * @desc 这辆车没有实现 Car接口
 */
public class HondaCar {
    public String getName(){
        return "我是一辆丰田车";
    }
}

客户端测试类

/**
 * @author Seig Heil
 * @date 2019/5/30 21:40
 * @desc
 */
public class Client {
    public static void main(String[] args) {
        Car audi = SimpleCarFactory.create(AudiCar.class);
        System.out.println(audi.getName());
        Car bwm = SimpleCarFactory.create(BwmCar.class);
        System.out.println(bwm.getName());
        Car honda = SimpleCarFactory.create(HondaCar.class);//这里一行代码其实是无法编译通过的
        System.out.println(honda.getName());
    }
}

根据如上,我们可以看到简单工厂+反射方式,从扩展性来讲,只要产品类实现Car接口即可。从调用方来讲,静态方法规约界定了一定要实现Car接口的子类,从而严格控制参数。

6.枚举方式

我们除了上述三种,还有一种,那就是枚举。
特征归纳:
1、枚举定义一个抽象方法,方法返回类型是子类共同要实现的Car接口。
2、枚举成员要实现抽象方法,并返回对象实例。
3、从扩展性来讲,新增子类,只需要新增成员,并实现抽象方法即可。
4、遵循“迪米特原则”,对于客户端调用利于读懂。

/**
 * @author Seig Heil
 * @date 2019/5/30 22:05
 * @desc 枚举方式实现
 * (1)、定义一个抽象方法
 * (2)、成员实现该抽象方法
 * 从扩展性来讲:
 * 1、增加一个产品类,需要增加一个枚举成员,相对静态工厂+反射方式,需要增加代码。
 */
public enum CarFactoryEnum {
    Audi{
        @Override
        Car create() {
            return new AudiCar();
        }
    },
    Bwm{
        @Override
        Car create() {
            return new BwmCar();
        }
    };

    abstract Car create();
}

客户端测试类


/**
 * @author Seig Heil
 * @date 2019/5/30 21:40
 * @desc
 */
public class Client {
    public static void main(String[] args) {
        Car audi = CarFactoryEnum.Audi.create();
        System.out.println(audi.getName());
        Car bwm = CarFactoryEnum.Bwm.create();
        System.out.println(bwm.getName());
    }
}

7.总结

我们回顾上述四种实现方法,大家记住了哪种呢?如果还没有印象,那就自己手敲一下代码。作为一名程序员,只有代码敲的多,我们才能加深自己的理解。就像蒸馒头一样,光看人家做的不错,自己跃跃欲试。那就动手来一把吧!
工厂模式也是最常见的一种,不过我们使用Spring框架,就可以把Bean对象交由Bean容器管理。
尽管如此,如果我们掌握上述几种模式,是不是可以在同事间吹个niubility吧。坦白来讲,掌握了,你就可以运用到项目实践之中,这样的代码就上升一个层次,不再是普通大众,当你不断提高了,日积月累,你也会成为大神。
就像,孔乙己说“我会‘回’字的几种写法,你们晓得吗?”

发布了46 篇原创文章 · 获赞 27 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/shichen2010/article/details/90707481