3分钟学设计模式(创建型):2、工厂方法模式

2021-10-08 15:30:35 浏览数 (1)

前言

设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。

经过汇总的23种设计模式它是总结了面向对象设计当中最有价值的经验。对之前来讲可能是对其中部分设计模式还是相对来说熟悉的但仔细琢磨还是会有些疑问,正好在目前相对来说有更多的业余时间,可以来一次重新学习设计模式!

本篇内容在工厂方法模式之前还加了一段关于简单工厂模式的介绍以及相关实现,再引入到工厂方法模式

简单工厂

在聊工厂方法模式之前,先了解简单工厂模式,它虽然不属于23种之一但它是工厂模式家族里最基础的一种编码概念

简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。——《百度百科》

就是说设计一个工厂类来生产各种类的实例,根据你选择或者传入的参数工厂提供你需要的实例,通过这样的方式来解决一个类对于使用各种实例之前需要依赖这些实例对应的类的问题。

代码语言:javascript复制
public abstract class Food{
    //烹饪或者加热
    public abstract void cooking();
} 
代码语言:javascript复制
public class Sandwich extends Food{
    @Override
    public void cooking(){
        System.out.print("做明治");
    }
} 
代码语言:javascript复制
public class Noodle extends Food{
    @Override
    public void cookie(){
        System.out.print("煮面条");
    }
} 
代码语言:javascript复制
public class Dumpling extends Food{
    @Override
    public void cooking(){
        System.out.print("煎饺子");
    }
} 

那么如上有三个具体类(三明治、面条、饺子)。这里画个图:

在不使用工厂模式的情况下我们要使用这些具体类的实例需要先创建实例也就是new出来。

如下伪代码

代码语言:javascript复制
public class Me {
    public void eat() {
        Food food = null;
        if("我想吃面"){
            food = new Noodle();
        }else if("我想吃三明治"){
            food = new Sandwich();
        }else("不知道吃啥") {
            food = new Dumpling();
        }
        food.cooking();
        System.out.println("我开吃啦" food);
    }
}

如上因为我不可能只吃一种食物,导致类里需要绑定很多具体食物的类在代码当中。

并且食物的客户并非只有我(Me.class),还有你、她、他甚至餐馆。我们虽然用Food进行接收但new关键字后面仍是具体的类

那么各个客户类里绑定着各种各样的具体类。一旦这一系列具体的类需要改变。那么客户代码都需要修改。

为了不惧怕改变,把创建实例的部分也就是写了具体类的部分代码抽取到工厂,由工厂进行提供。这样左边是抽象类或者接口接收,右边是统一的工厂供给各种具体类的实例。

代码语言:javascript复制
class FoodFactory{
    public Food createFood(String foodName){
        if("三明治".equals(foodName)){
            return new Sandwich();
        }else if("面条".equals(foodName)){
            return new Noodle();
        }else if
        ......
    }
}
代码语言:javascript复制
public class Me {
    FoodFactory factory = new FoodFactory();
    public void eat() {
        // 拿到食物
        Food food = factory.createFood("我想吃xxx");
        // 加热或按照标准烹饪
        food.cooking();
        // 吃
        System.out.println("我开吃啦" food);
    }
}

由依赖很多具体的类变成代码里只依赖FoodFactory。总而言之变成如下的关系:

客户类之前为了使用各种类的实例,导致写死了很多具体类在代码当中。而通过这样的工厂,客户类同样可以获取各种类型的实例但代码里却只有FoodFactory一个具体类了

工厂方法模式

那么到这里,可以发现简单工厂确实完成了实例创建与使用之间的解耦合,但它对于工厂类本身是不满足对修改关闭的原则,可能我要吃面一会儿吃清淡的一会儿吃麻辣的。因此一个类型的东西是有多个具体的类,我们需要使用一个类型的实例让工厂给我们,工厂给了这个类型的其中之一的具体类实例,之后有可能需要换。

喜欢吃辣去找辣的菜馆,喜欢吃清淡找清淡的菜馆呗!

如此说来确实菜馆就一个咋行呢?比如有两种菜馆(工厂),我吃辣就找HotFoodFactory吃清淡点就找MildFoodFactory

代码语言:javascript复制
public abstract class FoodFactory{
    public Food cooking(String foodName){
        Food food = createFood(foodName);
        food.cooking();
        ....
        return food;
    }
    public abstract Food createFood(String foodName);
}
代码语言:javascript复制
public class HotFoodFactory extends FoodFactory{
    @Override
    public Food createFood(String foodName){
        if("饺子".equals(foodName)){
            return new HotDumpling();
        }else if("面条".equals(foodName)){
            return new HotNoodle();
        }else if
        ......
    }
}
代码语言:javascript复制
public class MildFoodFactory extends FoodFactory{
    @Override
    public Food createFood(String foodName){
        if("饺子".equals(foodName)){
            return new MildDumpling();
        }else if("面条".equals(foodName)){
            return new MildNoodle();
        }else if
        ......
    }
}

最终需要的具体类的实例找对应的工厂进行创建

代码语言:javascript复制
public class Me {
    FoodFactory factory = new HotFoodFactory();
    public void eat() {
        // 获取食物
        Food hotNoodle = factory.cooking("面条");
        // 吃
        System.out.println("我开吃啦" hotNoodle);
    }
}
代码语言:javascript复制
public class You {
    FoodFactory 兰州拉面馆 = new 兰州拉面();
    public void eat() {
        // 获取食物
        Food 兰州拉面 = 兰州拉面馆.cooking("面条");
        // 吃
        System.out.println("我开吃啦" 兰州拉面);
    }
}

那么现在完成一个转变,比起之前的简单工厂模式所有的具体类都在一个工厂里面进行创建,责任过大且不好扩展。那么对于现在的工厂方法模式。可以分不同的工厂创建不同类型的实例。

当你所有面馆的面都吃厌了想吃新口味的面,可以由新开的面馆提供新的面类,而不至于将之前的某家面馆的产品进行推翻重新做产品毕竟这家面馆可能也被其他人喜欢。那么这样即做到了对修改关闭对扩展开放。

思考

简单工厂的写法,为啥有的会给创建方法加上static?

一般简单工厂基本上涉及的类型不会太多,也不大会发生变化。使用static修饰就可以直接类调用创建方法了。但如果考虑到该类之后要被继承可能重写创建方法那就不能用静态了

简单工厂用反射不就可以满足对修改关闭了么?

这样的实现没有太多意义。先把这种实现代码列出来看一看:

代码语言:javascript复制
public class Factory{
    public static Food createFood(String className){
        return Class.forName(className).newInstance();
    }
}
代码语言:javascript复制
public class Me{
    public void eat(){
       Food food1 = Factory.createFood("xx.xx.xx.Noodle");
       Food food2 = Factory.createFood("xx.xx.xx.Dumpling");
    }
}

咋一看好像没问题,确实解决了一堆判断的问题,简单工厂的代码不用像之前一样加一个实例就加一个判断。

代码语言:javascript复制
new Noodle(); ==> "xx.xx.xx.Noodle"

但想想工厂模式的设计到底是为了什么?不就是为了创建与使用解耦合么。为了在客户代码里使用的是抽象来换取具体实例,具体实例变动是和工厂有关。这样写又是在客户代码里具体化了。一变动需要变动的是全体客户代码。

这样写全限定名与直接new都是写到了可以被实例化的具体类,不同的是字符串我们可以从配置里去读取通过这样的方式解耦。

代码语言:javascript复制
包子=xx.xx.xx.牛肉包
代码语言:javascript复制
Food food = Factory.createFood(getProperties("包子"));

通过配置完成客户代码写抽象,最终获得具体的实例。当具体类发生变更只需要改配置当中引用的具体类即可。

但这种解耦不是工厂模式完成的。把具体丢给了客户代码。

客户代码用配置或者用其他工厂等其他方式来进行解耦了,再给这个工厂?。

那这个工厂是干啥的?

工厂方法模式的每个具体工厂就等于一个简单工厂么?

单个来看实现是差不多的,但对于工厂方法模式中的各个具体工厂他们不是独立的。他们是共同实现了一个抽象工厂,对这个工厂的抽象创建方法进行了各自不一样的实现。

总结

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到了子类。

上面定义了一个工厂(菜馆)是抽象的。真正创建具体菜品实例的是他的抽象方法。这个抽象方法分发下去给各个子类进行实现。

工厂类作为中间人,完成了客户类与很多使用的实例对应的类解耦了。客户类只对工厂有依赖关系或者耦合关系。并且通过换具体工厂来解决某些使用实例变动的问题。其实是是将很多的耦合抽取了,变成了唯一的耦合。那么这个耦合就可以让启动器根据配置或者数据库来动态获取解决。

客户类:

代码语言:javascript复制
public class Me {
    FoodFactory factory;
    public void setFactory(FoodFactory factory){
        this.factory = factory;
    }
    public void eat() {
        Food food1 = factory.cooking("面条");
        Food food2 = factory.cooking("包子");
    }
}

配置:

代码语言:javascript复制
早餐店="牛肉馆"
代码语言:javascript复制
me.setFactory(get("早餐店"));
food1 = @"牛肉面";
food2 = @"牛肉包子";

更改配置:

代码语言:javascript复制
早餐店="素食馆"
代码语言:javascript复制
me.setFactory(get("早餐店"));
food1 = @"清汤面";
food2 = @"素包子";

其优缺点基本上是相较于简单工厂而言,当客户类使用产品实例需要更改时,客户类通过工厂来获取因此其代码不变。变得是工厂创建实例使用的具体类。简单工厂模式就是在工厂代码里改变,而工厂方法模式将工厂抽象并新建另一个工厂,让客户代码去换工厂。它的缺点也是因为它的优点,当产品的类型结构越来越复杂,大类型与具体类型都越来越多。具体工厂类因此也越来愈多。

0 人点赞