一起学设计模式-02 工厂模式

写在前面

如果你还没了解六大软件设计原则的话,建议先谷歌下,再来学习设计模式。这对理解设计模式和记忆设计模式的经典写法都会有很大帮助。学习设计模式,光是记写法没有意义,要学习的是设计思想,为什么要这么做,这么做的好处是什么。怀着这两个问题来学习设计模式,你会发现六大软件设计原则在设计模式中的应用,相应的也会加强你对六大软件设计原则的记忆。

工厂模式的经典疑问

很多人,包括我自己在看工厂模式的时候都会问一个问题,工厂模式的好处到底是什么?有人说抽象?我觉得那是依赖倒置的好处,工厂模式遵循了依赖倒置原则,自然有抽象的特点,但这并不是根本好处,但凡遵循这一原则的都会具备这个特点。所以在我们在学习某个知识点的时候,要多看几篇文章,反复对比斟酌,不要希望说“XXX看这一篇就够了”(很多博客的标题都会这么起),包括这篇文章,也仅仅是作为读者的一个参考。那么工厂模式的好处到底是啥,我个人认为工厂模式最根本的好处是:

工厂模式最直接的好处就是屏蔽了对象的创建细节,将使用者和对象的创建过程隔离开来。特别是对那些创建过程非常复杂的对象,使用者不再需要关心这些对象是如何创建的,这么做简化了使用者的操作,降低了调用难度。另外,由于创建的过程被工厂封装,也避免了创建对象的代码,散落在各个模块的情况,同时也减少了代码开发工作量。当然工厂模式也具备多态的特性,这是因为工厂模式遵循依赖倒置原则,只是一个附带的特性。最后,多了这层封装也为实例创建的集中管理提供了便捷。

假如我需要一个ThinkPad 的Computer电脑实例来办公,这个ThinkPad电脑实例是由A Screen显示屏,B KeyBoard键盘,C CPU 处理器, D RAM内存条构成,且有一个非常复杂的组装过程。好了,作为一个使用者,我可能需要这么写:

Screen aScreen = new AScreen(a);
KeyBoard bKeyborad = new BKeyBoard(b);
CPU ccpu = new CCPU(c);
RAM dRam = new DRAM(d);
Computer computer = new ThinkPad(aScreen, bKeyborad, cCpu, dRam);

我作为电脑的使用者,我并不关心电脑是由哪些部分构成的,是怎么组装的。这些内容对使用者来说,显然是多余的。而且假如我在多处需要使用这个ThinkPad的实例,这段构造ThinkPad的代码会出现在多个地方。某天ThinkPad的构成变了,使用的是fRAM,那我还要把散落在各个模块的构造代码挑出来,把参数dRam改为fRam。

有的人可能会说,那我提供一个无参构造函数,把创建对象的细节都放里面,这样不也可以避免当入参发生变化时,不需要到处修改代码,而且也屏蔽了创建细节。这样确实是可以,但是实际上有多少个类的是可以无参构造的呢,这种情况就非常极端了。设计模式只是一个解决实际问题的参考,有没有用要不要用都是结合实际情况决定,当然因为其广泛的适用性,所以被总结为一个模式,类构成非常简单且产品单一,当然可以不用工厂模式,没必要钻牛角尖。

下面我们从简单工厂模式开始,一步步深入的学习简单工厂模式,工厂方法模式,抽象工厂模式,看看工厂模式是怎么样一步步演变的。

简单工厂模式

简单工厂模式通常由三部分构成,经典写法就是switch语句来决定返回的对象。

  • 抽象的产品接口(遵循依赖倒置原则,所有产品都实现了产品抽象接口)
  • 具象的产品类
  • 具象的工厂类
//抽象接口
public interface Computer {
    public void run();
}

//具象类
public class Mac implements  Computer {
    @Override
    public void run() {
        System.out.println("Mac Run");
    }
}

public class ThinkPad implements Computer {
    @Override
    public void run() {
        System.out.println("ThinkPad Run");
    }
}

//简单工厂-告诉工厂想要什么类型的电脑,工厂就给你什么类型的电脑,新款电脑需要修改工厂类增加case分支
public class SimpleFactory {
    public static Computer createComputer(String Type){
        Computer computer = null;
        switch (Type){
            case "MAC": return new Mac();
            case "ThinPad":return new ThinkPad();
            default: throw new IllegalArgumentException("不存在的Computer类型");
        }
    }
}

//客户端代码
public class TestComputer {
    public static void main(String[] args)  {
        SimpleFactory.createComputer("ThinPad").run();
    }
}

在产品不多的情况下,我们可以接受使用简单工厂模式。但是在产品类型比较多的时候,简单工厂模式就不再适用。想象下,每增加一个产品,我们都需要修改简单工厂(违背开闭原则),而且我们不停的往这个简单工厂里面添加代码,越来越多,非常臃肿,可读性和可维护性会变差。而且,我们需要不停的修改简单工厂的代码,违背了开闭原则(只扩展不修改原代码)。这个时候,工厂方法模式就来了。

注1:顺便提一下使用反射来创建实例可以实现不违反开闭原则的简单工厂

    public static Computer createComputerByReflect(String type) 
            throws IllegalAccessException, 
                    InstantiationException, 
                   ClassNotFoundException {
        Class computerClass = Class.forName(type);
        return (Computer)computerClass.newInstance();
    }

    //客户端代码
    public class TestComputer {
    public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
        SimpleFactory.createComputerByReflect("test.open.Mac").run();
    }
}

注2:简单工厂模式又称静态工厂模式,使用静态方法,无需实例化简单工厂。至于工厂方法模式和抽象工厂模式为啥就不用静态方法,个人理解是因为这两个模式对工厂进行了抽象(定了工厂接口),如果用了静态方法,这些方法无法被重写。

工厂方法模式

工厂方法模式,可以理解为简单工厂模式的加强版,对具象的工厂类做了一层抽象(构成多了一层工厂接口),不同产品的创建不再集中在一个工厂内,而是由各自专门的工厂类负责。新增一个产品,无需改动原有工厂类,只需要新增一个新的工厂类即可,在继承了简单工厂模式的优点之余,也克服了简单工厂模式违背开闭原则的问题。当然缺点就是类变多了,开发量增加了。

  • 抽象的产品接口
  • 抽象的工厂接口(为了遵循开闭原则而做的抽象)
  • 具象的产品类
  • 具象的工厂类
//抽象的产品接口
public interface Computer {
    public void run();
}

//具象的产品类
public class Mac implements  Computer {

    @Override
    public void run() {
        System.out.println("Mac Run");
    }
}

public class ThinkPad implements Computer {

    @Override
    public void run() {
        System.out.println("ThinkPad Run");
    }
}

//抽象的工厂接口
public interface AbMethodFactory {
    Computer createComputer();
}

//具象的工厂类
public class MacFactory implements AbMethodFactory{
    @Override
    public Computer createComputer() {
        return new Mac();
    }
}

//具象的工厂类
public class ThinkPadFactory implements AbMethodFactory {

    @Override
    public Computer createComputer() {
        return new ThinkPad();
    }
}

//客户端代码
public class TestComputer {
    public static void main(String[] args){
        AbMethodFactory abMethodFactory = new MacFactory();
        Computer mac = abMethodFactory.createComputer();
        mac.run();
    }
}

抽象工厂模式

前面两种工厂,生产的都是单一产品。实际上,有的时候我们会有生成一系列产品的需求,比如我要生产的是一个电脑套装(一个产品系列),包含电脑,鼠标,键盘。当我们面临多个产品组合协作的场景时,使用抽象工厂,我们可以保证客户端使用的产品,都是如我们所希望的出自同一系列(比如 Mac电脑要接Type-C接口的鼠标),我只要切换工厂,就能实现配套措施的全部切换,改变整个系统的行为。缺点是如果我要在产品族里面再加一个产品,那么我需要修改工厂接口,增加一个获取新产品的方法抽象方法定义,所有具象工厂也要增加对应实现,违背了开闭原则。

抽象工厂的构成和工厂方法模式一致,但是其工厂内会提供多个方法用于获取同一个系列的不同产品。

  • 抽象的产品接口
  • 抽象的工厂接口(定义多个方法生产同系列不同产品)
  • 具象的产品类
  • 具象的工厂类(提供多个方法生产同系列不同产品)

代码只贴工厂接口和其中一个工厂吧,不然篇幅太冗长了。

public interface AbMethodFactory {
    //生产电脑
    Computer createComputer();
    //生产鼠标
    Mouse createMouse();
}

public class MacFactory implements AbMethodFactory{
    @Override
    public Computer createComputer() {
        return new Mac();
    }

    @Override
    public Mouse createMouse() {
        return new TypeCMouse();
    }

}

参考文章:

https://blog.csdn.net/fmyzc/article/details/79614944

0

说点什么

avatar
  Subscribe  
提醒