美文网首页Java设计模式
设计模式——工厂模式

设计模式——工厂模式

作者: Qiansion齐木楠雄 | 来源:发表于2021-05-18 16:43 被阅读0次

工厂模式类型

1、简单工厂模式
2、工厂方法模式
3、抽象工厂模式

面向接口编程

面向接口编程.png

面向接口编程
1、每个模块负责自己的职责(单一职责),各个模块之间通过接口隔离
2、每个模块都应该"承诺"自己对外暴露的接口是不变的。当模块内部发生变化时,其他模块是不需要知道的。这便是依赖于抽象而不依赖于实现(依赖倒置原则)
3、上层模块只知道下层模块暴露出的接口即可,至于实现细节不需要也不应该知道。

举个简单的例子来简单的说明上面三个条件。
三国时期,曹操和刘备各自有自己的军队。他们分别管理蜀国和魏国(职责单一)。各国之间只能通过外交官(接口隔离)进行传话。各国的外交官这个人对外是不变的,当有的小国换了君主,其他国家是不用知道的,有什么事只用找这个外交官商量即可(依赖抽象不依赖实现)。

概念

产品:类
抽象产品:抽象类、接口
产品簇:多个内在联系的产品(比如 小米手机 小米手环)
产品等级:
作者:可以理解为开发者 如:开发mybatis的为作者 文中也以服务器端来表示
用户:可以理解为使用者 如:使用mybatis的程序员为用户 文中也以客户端来表示

简单工厂模式

看如下一段代码

//抽象产品
interface Food{
    void eat();
}
// 具体产品
class Hamburger implements Food{
    @Override
    public void eat() {
        System.out.println("吃汉堡包!");
    }
}

/**==============================================================
 */
public class AppTest {
    public static void main(String[] args) {
        Food f = new Hamburger();
        f.eat();
    }
}

虚线以上为作者开发,虚线以下为用户调用。
如果有一天作者突然做出了修改将Hamburger变成了Hamburger2,此时用户就无法使用了。

interface Food{
    void eat();
}

class Hamburger2 implements Food{
    @Override
    public void eat() {
        System.out.println("吃汉堡包!");
    }
}

/**==============================================================
 */
public class AppTest {
    public static void main(String[] args) {
        //Cannot resolve symbol 'Hamburger'
        Food f = new Hamburger();
        f.eat();
    }
}

那么用户这边也需要跟着修改,因为耦合度太高。面向接口编程本身就是解耦的,用户只需要知道接口,至于你是hamburger1还是hamburger2不是用户所关心的。作者把细节暴露给用户就是违反了迪米特法则。
这种设计相当脆弱!为什么呢?因为,只要作者修改了具体产品的类名,那么客户端代码,也要随着一起改变。这样服务器端代码和客户端代码就是耦合的!我们希望的效果是,无论服务器端代码如何修改,客户端代码都应该不知道,不用修改客户端代码。

针对以上问题修改

使用简单工厂模式

class FoodFactory{
    public static Food getFood(int n){
        Food food =null;
        switch (n){
            case 1:
                food = new Hamburger();
                break;
        }
        return food;
    }
}

/**==============================================================
 */
public class AppTest {
    public static void main(String[] args) {
        Food f = FoodFactory.getFood(1);
        f.eat();
    }
}

分析
这样做的好处是如果作者将Hamburger改成Hamburger2或者Hamburger3,用户只用知道getFood(1)中的1对应的是Hamburger,而不用去管它是Hamburger2还是Hamburger3。面向接口编程说的不是java类中的interface。而是上层向下层暴露的接口,如上面代码中的静态方法getFood。这样就隔离了变化,也是符合依赖倒置原则,调用者不用依赖细节,而应该依赖抽象。

面对变化

此时用户觉得汉堡太腻了,想要再添加一个食物——面条呢?用户先得创建一个面条类

class Noddle implements Food{
    @Override
    public void eat() {
        System.out.println("吃面条");
    }
}

然后在简单工厂中添加一个case条件分支即可

class FoodFactory{
    public static Food getFood(int n){
        Food food =null;
        switch (n){
            case 1:
                food = new Hamburger();
                break;
                //添加面条的条件分支
            case 2:
                food = new Noddle();
                break;
        }
        return food;
    }
}

很显然,简单工厂是不利于拓展的,每次新加一个新的食物都需要去修改简单工厂中的switch语句,违背了“对修改关闭,对拓展开放”的原则。

类图展示

最终类图展示如下:


类图展示.png

用户不属于类图的范畴,这里加上只是为了方便理解,对比类图和面向接口编程的图就很容易理解了。

总结

【简单工厂】
优点
1、把具体产品的类型,从客户端代码中解耦出来 。
2、服务器端,如果修改了具体产品的类型,客户端也无影响
这便符合了“面向接口编程”的思想
缺点
1、客户端不得不死记硬背那些常量与具体产品的映射关系 比如:1对应Hamburger
2、如果客户端产品特别多,则简单工厂就会变得臃肿。比如有100个具体产品,则需要在简单的switch中写100多个case!
3、最重要的是,变化来了:客户端需要拓展具体产品的时候,势必需要修改简单工厂中的代码,这样便违背了“开闭原则”

工厂方法模式

为了使简单工厂更方便的拓展,在上面简单工厂的例子中升级成为工厂方法模式。首先,我们需要从代码层面去明确简单工厂为什么不方便做拓展,答案是因为它只有一个工厂,所有的食物都必须在这一个工厂内进行实例化,那就无法避免的要使用条件分支确定食物类型。
将简单工厂方式改为工厂方法方式
1、去掉简单工厂中的FoodFactory
2、创建一个工厂接口

interface FoodFactory {
    Food getFood();
}

3、创建生产汉堡包的工厂并实现接口

class HamburgerFactory implements FoodFactory{
    @Override
    public Food getFood() {
        return new Hamburger();
    }
}

4、调用

public class AppTest {
    public static void main(String[] args) {
        FoodFactory ff =new HamburgerFactory();
        Food f=ff.getFood();
        f.eat();
    }
}

吃汉堡包!
工厂方法创建工厂,工厂内部进行实例化。这样每新加一个食物的时候,就需要添加一个与之相关的工厂。
比如:添加大米Rice

class Rice implements Food{
    @Override
    public void eat() {
        System.out.println("吃大米");
    }
}
class RiceFactory implements FoodFactory{
    @Override
    public Food getFood() {
        return new Rice();
    }
}
public class AppTest {
    public static void main(String[] args) {
        FoodFactory ff =new RiceFactory();
        Food f=ff.getFood();
        f.eat();
    }
}

吃大米

这样就把简单工厂里的修改转变为拓展了。除此之外,在客户端调用时,也不用再去记hamburger对应什么,Noddle对应什么,Rice对应什么。直接见名知意的调用即可。

类图展示

工厂方法.png

总结

【工厂方法】
优点
1、仍然具有简单工厂的优点:服务器端修改了具体产品以后,客户端无影响
2、当客户端需要拓展一个新的产品时,不需要修改作者原来的代码,只是拓展一个新的工厂而已
问题
我们一开始创建简单工厂就是了让客户端感受不到服务器端的变化(即不论Hamburger1或Hamburger2客户端都不要去了解),现在如果服务器把RiceFactory改为了RiceFactory1或RiceFactory2,客户端仍然需要做相应的修改,这不是白忙活了吗?
这里我们可以看面向接口编程中的第2条:

每个模块都应该"承诺"自己对外暴露的接口是不变的。当模块内部发生变化时,其他模块是不需要知道的。这便是依赖于抽象而不依赖于实现(依赖倒置原则)

每个模块应该"承诺"对外暴露的接口是不变的。当然,如果确实需要改变的,那就只能同步进行修改。

缺点
1、每一个实体类都需要一个工厂类与之对应,这里的产品是Food,下一次是Drink呢,过多的产品等级将会导致类爆炸。

工厂方法模式.png

抽象工厂模式

当有过多产品等级时,使用抽象工厂模式会比工厂方法模式更适用。
此处普及一下产品等级和产品簇的概念


image.png

同样颜色的为产品簇,具有一定联系,如图中,红色的电器均属于华为。产品等级为同一类产品,电冰箱为一个产品等级,电视机为另外一个产品等级。如图中有5个产品等级。

以工厂方法类图为例
先创建基本的产品

//抽象产品
interface Drink{
    void drink();
}
interface Food{
    void eat();
}
// 产品
class Rice implements Food {
    @Override
    public void eat() {
        System.out.println("吃大米");
    }
}
class Hamburger implements Food {
    @Override
    public void eat() {
        System.out.println("吃汉堡包!");
    }
}
class Cola implements Drink {
    @Override
    public void drink() {
        System.out.println("喝可乐");
    }
}
class IcePeak implements Drink {
    @Override
    public void drink() {
        System.out.println("喝冰峰");
    }
}

①工厂方法模式

// 工厂相关
interface FoodFactory {
    Food getFood();
}
interface DrinkFactory{
    Drink getDrink();
}
class HamburgerFactory implements FoodFactory{
    @Override
    public Food getFood() {
        return new Hamburger();
    }
}

class RiceFactory implements FoodFactory{
    @Override
    public Food getFood() {
        return new Rice();
    }
}

class ColaFactory implements DrinkFactory{

    @Override
    public Drink getDrink() {
        return new Cola();
    }
}

class IcePeakFactory implements DrinkFactory {

    @Override
    public Drink getDrink() {
        return new IcePeak();
    }
}

②抽象工厂模式

abstract class Factory{
    abstract Food getFood();
    abstract Drink getDrink();
}

class KFCfFactory extends Factory{
    @Override
    Food getFood() {
        return new Hamburger();
    }
    @Override
    Drink getDrink() {
        return new Cola();
    }
}

class QiansionFactory extends Factory{

    @Override
    Food getFood() {
        return new Rice();
    }

    @Override
    Drink getDrink() {
        return new IcePeak();
    }
}

从对比中就能看出在产品等级多的情况下,抽象工厂的类更少,或者说它本身就很契合于多产品等级的。在工厂方法模式中,每一个产品都有自己的工厂——大米工厂、汉堡工厂、可乐工厂、冰峰工厂。但是这些工厂都有一些特点,大米和汉堡工厂都是食物,可乐和冰峰工厂都是饮品。食物和饮品这就是两个产品等级了,那么生产食物和饮品工厂既不能叫做食物工厂也不能叫做饮品工厂,所以可以用抽象工厂来指代,这个抽象工厂技能生产食物也能生产饮品。

类图展示

抽象工厂.png
可以看出,如果一个工厂里只生产一个产品等级(如:getFood()),那就是工厂方法,如果一个工厂里生产多个产品等级(如:getFood(),getDrink())那就是抽象工厂。
拓展性(增加产品簇)
新增一类食品(HotDryNoodles,热干面)和饮品(eggnog,蛋酒)
class HotDryNoodles implements Food{
    @Override
    public void eat() {
        System.out.println("吃热干面");
    }
}
class Eggnog implements Drink{
    @Override
    public void drink() {
        System.out.println("喝蛋酒");
    }
}
class WHFactory extends Factory {
    @Override
    Food getFood() {
        return new HotDryNoodles();
    }
    @Override
    Drink getDrink() {
        return new Eggnog();
    }
}
class diet{
    public static void taste(Factory ff){
        Food f = ff.getFood();
        Drink d = ff.getDrink();
        System.out.println("品尝:");
        f.eat();
        d.drink();
    }
}
public class AppTest {
    public static void main(String[] args) {
        Factory ff = new WHFactory();
        diet.taste(ff);
    }
}

品尝:
吃热干面
喝蛋酒

总结

【抽象工厂】
优点
1、仍然有简单工厂和工厂方法的特点
2、更重要的是,抽象工厂把工厂类的数量减少了!无论多少个产品等级,工厂就一套
问题
1.为什么KFCFactory中,就必须是Humburger搭配Cola呢?为什么不能是Rice搭配Cola呢?
抽象工厂中,可以生产多个产品,但这多个产品之间,必须有内在联系。即必须为产品簇
缺点
1、当产品等级发生变化时(增加产品等级、删除产品等级),都要引起所有工厂代码的修改,这就违反了“开闭原则”。

三种工厂模式场景比较

当产品不需要扩充时,使用简单工厂
当产品等级只有一个的时候,使用工厂方法
当产品等级比较固定时,使用抽象工厂。

工厂模式应用

Calendar
Calendar是java的日历类,是简单工厂的代表。

Calendar calendar = Calendar.getInstance();

关注getInstance()方法中的createCalendar方法


image.png

这里截取createCalendar关键部分


image.png
可以说,这很简单工厂了。

LoggerFactory
log4j是我们平常经常使用的日志工具,其中的日志工厂就是用工厂方法模式来实现的。此处产品等级只有一个,就是Logger。

        Logger logger = LoggerFactory.getLogger("logger");

可以关注这个getLogger的方法中的getILoggerFactory

image.png
根据条件的不同得到不同的工厂。
image.png
BeanFactory
spring的核心Bean工厂自然是不能的缺席工厂模式了。
image.png
从图中可以看出beanFactory里存在多个返回值,可以确定它的产品等级是多个的。
image.png
而每一个接口的实现最少都有5个实现类,这5个实现类可以看成5个工厂,每个工厂都会实现里面的方法形成一系列的产品簇,是典型的抽象工厂模式。

相关文章

  • 设计模式四、抽象工厂模式

    系列传送门设计模式一、单例模式设计模式二、简单工厂模式设计模式三、工厂模式设计模式四、抽象工厂模式 抽象工厂模式 ...

  • 工厂模式

    java设计模式-工厂模式 工厂模式: 工厂模式是java设计模式里最常用的设计模式之一。 工厂模式属于创建型模式...

  • 设计模式之工厂模式

    设计模式之工厂模式 标签(空格分隔): 设计模式 工厂模式 设计模式的感念 设计模式的应用 工厂设计模式的产生 工...

  • 设计模式三、工厂模式

    系列传送门设计模式一、单例模式设计模式二、简单工厂模式设计模式三、工厂模式设计模式四、抽象工厂模式 工厂模式 在一...

  • 设计模式一、单例模式

    系列传送门设计模式一、单例模式设计模式二、简单工厂模式设计模式三、工厂模式设计模式四、抽象工厂模式 简单单例(推荐...

  • 单件设计模式

    一、定义 设计模式 设计模式就是一种更好的编写代码方案。 常见设计模式 工厂设计模式、抽象工厂设计模式、抽象工厂设...

  • iOS设计模式(三)之抽象工厂模式

    设计模式系列传送门 iOS设计模式(一)之简单工厂模式iOS设计模式(二)之工厂模式iOS设计模式(三)之抽象工厂...

  • iOS设计模式(一)之简单工厂模式

    设计模式系列传送门 iOS设计模式(一)之简单工厂模式iOS设计模式(二)之工厂模式iOS设计模式(三)之抽象工厂...

  • iOS设计模式(二)之工厂模式

    设计模式系列传送门 iOS设计模式(一)之简单工厂模式iOS设计模式(二)之工厂模式iOS设计模式(三)之抽象工厂...

  • 常用设计模式

    设计模式 工厂模式 工厂模式思路上分:简单工厂模式,工厂模式, 抽象工厂模式// 抽象工厂模式可以代替工厂模式,做...

网友评论

    本文标题:设计模式——工厂模式

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