zl程序教程

您现在的位置是:首页 >  工具

当前栏目

设计模式学习总结(二)——工厂模式

学习模式设计模式 总结 工厂
2023-09-14 09:07:50 时间

一、概述

工厂是一种创建型的设计模式,常用于封装变化,一般遵循那里有变化就封装那里的原则。这里我们以一个快餐店为示例讲解,FastFood表示快餐,KFC表示肯德基,Mac表示麦当劳。

FastFood.java

package DP02.demo21;

/**快餐*/
public abstract class FastFood {
    /**品牌*/
    public String brand;
    /**展示*/
    public abstract void show();
}

KFC.java

package DP02.demo21;

public class KFC extends FastFood {
    @Override
    public void show() {
        this.brand="肯德基";
        System.out.println("欢迎来到"+this.brand);
    }
}

Mac.java

package DP02.demo21;

public class Mac extends FastFood {
    @Override
    public void show() {
        this.brand="麦当劳";
        System.out.println("欢迎来到"+this.brand);
    }
}

Student.java

package DP02.demo21;

/**
 * 学生
 */
public class Student {
    public static void main(String[] args) {
        KFC kfc = new KFC();
        kfc.show();
    }
}

运行结果:

从Student类中可以看到学生只允许吃KFC,如果他想吃别的东西就不允许了,违背DIP(依赖倒转原则),解决:

package DP02.demo21;

/**
 * 学生
 */
public class Student {
    public static void main(String[] args) {
        //这里可以指定任意FastFood子类,LSP
        //如果这里是稳定的,不会经常变化,则代码是没有问题的
        //如果这里要不断的变化,new KFC(),new Mac,new Kongfu()...
        FastFood kfc = new KFC();
        kfc.show();
    }
}

new KFC()就是一个变化点,封装。

二、简单工厂(Simple Factory)

简单工厂并不是GOF所著的书中提出的一种设计模式,但这种模式是学习设计模式一个很好的入口点,让我们能更容易理解设计模式是如何达到设计原则的要求的。
简单工厂设计模式是一种创建型的模式,主要是用来解决对象的创建问题。

上面的类图反映的就是将一组相似对象(继承自同一个类)的实例的创建放到另外一个对象中完成,即通过 ProductFactory 这个类来按需创建对象

在第一节中我们提出了“封装变化”的概念,这里使用简单工厂解决问题,代码如下:

FastFoodFactory.java

package DP02.demo21;

/**
 * 快餐工厂
 */
public class FastFoodFactory {
    /***
     * 用于实现不同类型快餐品牌的创建
     * @param brand 类型
     * @return 快餐品牌
     */
    public static FastFood GetFastFood(String brand) {
        switch (brand) {
            case "KFC":
                return new KFC();
            case "Mac":
                return new Mac();
            default:
                throw new IllegalArgumentException("没有该品牌");
        }
    }
}

Student.java

package DP02.demo21;

import java.util.Scanner;

/**
 * 学生
 */
public class Student {
    public static void main(String[] args) {
        //这里可以指定任意FastFood子类,LSP
        //如果这里是稳定的,不会经常变化,则代码是没有问题的
        //如果这里要不断的变化,new KFC(),new Mac,new Kongfu()...
        Scanner input=new Scanner(System.in);
        System.out.print("您想吃什么:");
        String brand=input.next();

        FastFood kfc = FastFoodFactory.GetFastFood(brand);
        kfc.show();
    }
}

运行结果:

简单工厂的优点:

简单工厂的工厂类中包含了必要的逻辑判断,这样就可以根据客户端的选择条件来动态的实例化相关的类,对于客户端来说,其去除了与具体产品之间的依赖

简单工厂的缺点:

违背了开-闭原则

三、工厂方法(Factory Method)

工厂方法模式,定义了一个用于创建对象的接口,让子类来决定要实例化哪一个类,工厂方法让类把实例化延迟到其子类。

在工厂方法模式中主要有以下几个组成部分:

抽象工厂:是工厂方法模式的核心,任何在模式中创建的具体工厂必须实现这个接口。

具体工厂:这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。

抽象产品:方法方法模式所要创建的对象的父类型,也就是产品对象的共同父类或共同拥有的接口。

具体产品:这是实现了抽象产品所定义的接口的具体产品类的实例。这是客户端最终需要的东西。

示例:

KFC肯德基,Mac麦当劳,Chips薯条,Ham汉堡

Chips.java

package DP02.demo22;

/**抽象产品,薯条*/
public abstract class Chips {
    /**展示薯条信息*/
    public abstract void info();
}

KFCChips.java

package DP02.demo22;

/**实体产品,肯德基薯条*/
public class KFCChips extends Chips {
    @Override
    public void info() {
        System.out.println("肯德基薯条");
    }
}

MacChips.java

package DP02.demo22;

/**实体产品,麦当劳薯条*/
public class MacChips extends Chips {
    @Override
    public void info() {
        System.out.println("麦当劳薯条");
    }
}

Ham.java

package DP02.demo22;

/**抽象产品,汉堡*/
public abstract class Ham {
    /**显示汉堡信息*/
    public abstract void Show();
}

KFCHam.java

package DP02.demo22;

/**实体产品 肯德基汉堡*/
public class KFCHam extends Ham {
    @Override
    public void Show() {
        System.out.println("肯德基汉堡");
    }
}

MacHam.java

package DP02.demo22;

/**
 * 实体产品 麦当劳汉堡
 */
public class MacHam extends Ham {
    @Override
    public void Show() {
        System.out.println("麦当劳汉堡");
    }
}

FastFoodFactory.java

package DP02.demo22;

/**
 * 抽象工厂 快餐工厂
 */
public abstract class FastFoodFactory {
    /**
     * 生产汉堡
     */
    public abstract Ham CreateHam();

    /**
     * 生产薯条
     */
    public abstract Chips CreateChips();
}

KFCFactory.java

package DP02.demo22;

/**实体工厂 肯德基*/
public class KFCFactory extends FastFoodFactory {
    @Override
    public Ham CreateHam() {
        return new KFCHam();
    }

    @Override
    public Chips CreateChips() {
        return new KFCChips();
    }
}

MacFactory.java

package DP02.demo22;

/**
 * 实体工厂 麦当劳
 */
public class MacFactory extends FastFoodFactory {
    @Override
    public Ham CreateHam() {
        return new MacHam();
    }

    @Override
    public Chips CreateChips() {
        return new MacChips();
    }
}

Client.java

package DP02.demo22;

/**测试类*/
public class Client {
    public static void main(String[] args) {
        /**创建食品工厂*/
        FastFoodFactory factory=new KFCFactory();
        Chips chips=factory.CreateChips();
        chips.info();
        factory.CreateHam().Show();

        factory=new MacFactory();
        factory.CreateChips().info();
        factory.CreateHam().Show();
    }
}

运行结果:

但是当产品种类非常多时,就会出现大量的与之对应的工厂类,所以我建议在这种情况下使用简单工厂模式与工厂方法模式相结合的方式来减少工厂类:即对于产品树上类似的种类(一般是树的叶子中互为兄弟的)使用简单工厂模式来实现。

当然特殊的情况,就要特殊对待了:对于系统中存在不同的产品树,而且产品树上存在产品族,那么这种情况下就可能可以使用抽象工厂模式了。

四、抽象工厂(Abstract Factory)

1、提供一系列相互依赖对象的创建工作

2、封装对象常规的创建方法(new)

3、提供统一调用数据访问方法的方式

4、避免调用数据访问方法和具体对象创建工作的紧耦合

1、用抽象工厂生产抽象产品

2、用实体工厂生产实体产品

3、用抽象产品提供实体产品访问接口

4、用实体产品实现自己的功能

在工厂方法的基础上修改FastFoodFactory.java,代码如下:

package DP02.demo23;

/**
 * 抽象工厂 快餐工厂
 */
public abstract class FastFoodFactory {
    /**
     * 生产汉堡
     */
    public abstract Ham CreateHam();

    /**
     * 生产薯条
     */
    public abstract Chips CreateChips();

    /***
     *根据brand类型返回不同的工厂
     * @param brand 工厂类型 品牌
     * @return 实体工厂
     */
    public static FastFoodFactory GetFactory(String brand){
        switch(brand){
            case "KFC":
                return new KFCFactory();
            case "Mac":
                return new MacFactory();
            default:
                throw new IllegalArgumentException("不存在的工厂类型");
        }
    }
}

 

测试Client.java

package DP02.demo23;

import java.util.Scanner;

/**测试类*/
public class Client {
    public static void main(String[] args) {
        /**创建食品工厂*/
        Scanner input=new Scanner(System.in);
        System.out.print("您想吃什么:");
        String brand=input.next();

        /**根据品牌生成实体工厂*/
        FastFoodFactory factory=FastFoodFactory.GetFactory(brand);
        factory.CreateChips().info();
        factory.CreateHam().Show();
    }
}

运行:

 

抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象。而且使用抽象工厂模式还要满足一下条件:

1.系统中有多个产品族,而系统一次只可能消费其中一族产品

2.同属于同一个产品族的产品一起使用时。

五、万能工厂

先来看简单工厂中选择不同类型的产品时的代码:

package DP02.demo21;

/**
 * 快餐工厂
 */
public class FastFoodFactory {
    /***
     * 用于实现不同类型快餐品牌的创建
     * @param brand 类型
     * @return 快餐品牌
     */
    public static FastFood GetFastFood(String brand) {
        switch (brand) {
            case "KFC":
                return new KFC();
            case "Mac":
                return new Mac();
            default:
                throw new IllegalArgumentException("没有该品牌");
        }
    }
}

当添加新的品牌时switch必须要修改,否则不会被选择。用反射可以削除switch,将字符串直接转换成一个对象。

修改简单工厂中的选择品牌的代码:

package DP02.demo21;

/**
 * 快餐工厂
 */
public class FastFoodFactory {
    /***
     * 用于实现不同类型快餐品牌的创建
     * @param brand 类型
     * @return 快餐品牌
     */
    public static FastFood GetFastFood(String brand) {
        switch (brand) {
            case "KFC":
                return new KFC();
            case "Mac":
                return new Mac();
            default:
                throw new IllegalArgumentException("没有该品牌");
        }
    }

    /***
     * 用于实现不同类型快餐品牌的创建
     * @param brand 类型
     * @return 快餐品牌
     */
    public static FastFood GetFastFoodPro(String brand) {
        try {
            return (FastFood) Class.forName("DP02.demo21." + brand).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

增加一个新的品牌:

package DP02.demo21;

public class HLS extends FastFood {
    @Override
    public void show() {
        System.out.println("欢迎光临华菜士!");
    }
}

客户端调用:

package DP02.demo21;

import java.util.Scanner;

/**
 * 学生
 */
public class Student {
    public static void main(String[] args) {
        //这里可以指定任意FastFood子类,LSP
        //如果这里是稳定的,不会经常变化,则代码是没有问题的
        //如果这里要不断的变化,new KFC(),new Mac,new Kongfu()...
        Scanner input=new Scanner(System.in);
        System.out.print("您想吃什么:");
        String brand=input.next();

        //FastFood food1 = FastFoodFactory.GetFastFood(brand);
        //food1.show();

        FastFood food2 = FastFoodFactory.GetFastFoodPro(brand);
        food2.show();
    }
}

运行结果:

新增了一HSL的产品品牌,但不需要修改任何代码,符合OCP原则,程序正常运行。修改抽象工厂方法基本一样。

反射机制指的是程序在运行时能够动态获取语言本身的信息,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接

1、反射提高了程序的灵活性和扩展性。

2、降低耦合性,提高自适应能力。

3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。

六、示例

示例:https://coding.net/u/zhangguo5/p/DP01/git

七、视频

视频:https://www.bilibili.com/video/av15867320/

八、作业与资料

8.1、作业

a)、请使用简单工厂完成一个水果工厂:

b)、请将简单工厂修改成万能工厂,添加新的水果类型时不需要修改switch

c)、请完成一个抽象工厂的示例,根据不同的国家产生不同的水果族,如:

中国            美国            德国

Apple          Apple          Apple

Orange      Orange        Orange

d)、请使用抽象工厂完成一个学员管理功能,实现展示学员与新增学员,要求在配置文件中动态切换数据库,也就是说当我在db.properties中设置DataBase=MySQL时系统立即使用MySQL作为当前运行的数据库,如果我设置为SQL Server则当前系统的数据库为SQL Server;也可以根据自己的选择使用任意数据库。