【23种设计模式专题】二 工厂模式

你的名字 2023-07-19 04:53 109阅读 0赞

程序猿学社的GitHub,欢迎Star
github技术专题
本文已记录到github

文章目录

  • 前言
  • 小故事
  • 传统方式
  • 简单工厂(第一种)
  • 工厂方法模式(第二种)
  • 抽象工厂模式(第三种)
    • 使用工厂方法模式
    • 使用抽象工厂实现

前言

通过上一篇文章,我们已经知道程序猿是有女(男)朋友,也知道如何保证只会有一个对象(男女朋友),本文我们来看一看工厂模式。

社长:“老王,我们开始继续23中设计模式之工厂模式”
隔壁老王: “社长,工厂模式有什么用,为什么要用工厂模式?”
社长: “我先买一个小关子,先来看一看传统的写法”
2020040323024232.png

小故事

周末一大早,6点钟左右,我就被吵醒,然后一脸呆萌呆萌的看着我,原来是忘记给我家乖乖喂食物咯,看了一下存放猫粮的袋子,也弹尽粮绝咯,只能上宠物店去购买猫粮。他的名字叫汤圆

传统方式

通过代码,我们实现去宠物店购买猫粮的这样一个需求。

  1. package com.cxyxs.designmode.factory;
  2. /** * Description: * Author: 程序猿学社 * Date: 2020/4/3 23:16 * Modified By: */
  3. public interface PetShop {
  4. void buy();
  5. }
  6. class Meat implements PetShop{
  7. @Override
  8. public void buy() {
  9. System.out.println("社长给汤圆购买肉干");
  10. }
  11. }
  12. /** * 提供者 * ======================= * 调用者 */
  13. class Test{
  14. public static void main(String[] args) {
  15. PetShop ps = new Meat();
  16. ps.buy();
  17. }
  18. }

2020040323220543.png
20200404095520788.png

  • 这种写法违反了迪米特法则也就是最少知道原则,通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。
  • 结合生活实际理解,例如我这里是去购买肉干,我有必要知道这个肉是如何生产出来的吗
  • 实际上也违反勒开闭原则,对扩展开放(提供者),对修改关闭(调用者)
    例如我们把类Meat改为Meat1,我们可以发现,我们需要修改两个地方,一个是提供者,还有一个是调用者,两边都要改动,这就违反了对修改关闭这一条,也没有实现程序的一个解耦。

隔壁老王: “社长,你也说了,上面这种方式,提供者一变,调用者就得跟着变,在项目开发过程中,如何也出现这样的问题,那大家还能不能愉快的玩耍咯”
社长:“别急,我们可以通过简单工厂来实现”

简单工厂(第一种)

从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例

肉干毕竟不能当饭吃,所以,还得买猫粮,肉干只能当零食吃。所以这时候我们的需求又发生了改变。
20200404095322845.png

  • 不清楚这个图如何画的,可以百度查一下UML类图

    package com.cxyxs.designmode.factory.simple;

    /* Description:宠物店 Author: 程序猿学社 Date: 2020/4/3 23:16 Modified By: /
    public interface PetShop {

    1. void buy();

    }

    class Meat implements PetShop{

    1. @Override
    2. public void buy() {
    3. System.out.println("社长给汤圆购买肉干");
    4. }

    }

    class Foot implements PetShop{

    1. @Override
    2. public void buy() {
    3. System.out.println("社长给汤圆购买猫粮");
    4. }

    }

  1. class CatFootFacoty{
  2. public static PetShop buy(String name){
  3. switch (name){
  4. case "猫粮":
  5. return new Foot();
  6. case "肉干":
  7. return new Meat();
  8. }
  9. return null;
  10. }
  11. }
  12. /** * 提供者代码 * ======================= * 调用端代码 */
  13. class Test{
  14. public static void main(String[] args) {
  15. PetShop ps = CatFootFacoty.buy("肉干");
  16. PetShop ps1 = CatFootFacoty.buy("猫粮");
  17. ps.buy();
  18. ps1.buy();
  19. }
  20. }

20200404000403881.png
好处:

  • 在这里借助了一个工厂类CatFootFacoty,实现解耦,我们不用关心肉干和猫粮是生产生产的,只需要告诉工厂,我需要这两样东西。由工厂统一管理。
  • 产品很少的情况下,可以实现简单工厂模式

缺点:

  • 如果产品一多,工厂类就会变得很臃肿,不利于维护,同时他违反咯开闭原则,开闭原则很重要的一点,当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来实现变化。
  • 例如我现在需要新增一个购买鱼的产品需求,需要新增一个鱼类,再修改工厂类,类会变得越来越多,如果上百个产品,这就意味着上百个类,增加了程序的复杂度。

工厂方法模式(第二种)

跟简单工厂相比,工厂方法模式是简单工厂的plus版本,越来越流程化。不像简单工厂一个工厂,既生产肉干,又生产猫粮等等,工厂方法模式,就是把具体生产的工作,交给具体的工厂来负责,例如,生产肉干的是一个工厂,生产猫粮的又是一个工厂。

  • 所以工厂方法模式符合开闭原则

社长: “我们刚刚已经了解到简单工厂一些优缺点,其中很重要的一点就是不符合开闭原则,我们来看一看工厂方法模式”
社长: “既然工厂方法模式是简单工厂的plus版本,我们直接把简单工厂的代码拿过来,新创建一个包methodmodel,老王,有没有跟上我的节奏”
隔壁老王: “欧了,已经搞定咯,如何改造成工厂方法模式*
社长: “话不多说,先看看类图,再看代码 “
20200404154350772.png

  • 这个类图代码写完后,可以自动生成,在idea中,右击
    20200404154458703.png
    再把对应的类拖进来就可以自动生成,

    package com.cxyxs.designmode.factory.methodmodel;

  1. /** * Description:宠物店 * Author:程序猿学社 * Date: 2020/4/3 23:16 * Modified By: */
  2. public interface PetShop {
  3. void buy();
  4. }
  5. class Meat implements PetShop {
  6. @Override
  7. public void buy() {
  8. System.out.println("社长给汤圆购买肉干");
  9. }
  10. }
  11. class Foot implements PetShop {
  12. @Override
  13. public void buy() {
  14. System.out.println("社长给汤圆购买猫粮");
  15. }
  16. }
  17. interface Factory{
  18. PetShop food();
  19. }
  20. class MeatFactory implements Factory{
  21. @Override
  22. public PetShop food() {
  23. return new Meat();
  24. }
  25. }
  26. class FootFactory implements Factory{
  27. @Override
  28. public PetShop food() {
  29. return new Foot();
  30. }
  31. }
  32. /** * 提供者代码 * ======================= * 调用端代码 */
  33. class Test{
  34. public static void main(String[] args) {
  35. Factory factory = new MeatFactory();
  36. FootFactory footFactory = new FootFactory();
  37. PetShop food = factory.food();
  38. PetShop food1 = footFactory.food();
  39. food.buy();
  40. food1.buy();
  41. }
  42. }

20200404104505832.png
隔壁老王: “社长,你这代码,我没有看出什么好处来,只知道你这代码越来越复杂,绕的头都晕咯。”
社长: “老王,别急呀,我们之前已经知道简单工厂是不符合开闭原则的,也就是说,尽量不要在已经的代码上进行修改,应该进行扩展”
社长: “我家的汤圆现在已经不满足于肉干和猫粮咯,需要吃鱼,我们看看,我们如何在现有的代码上进行改动”

  1. /** * 扩展吃鱼的部分开头 */
  2. class Fish implements PetShop {
  3. @Override
  4. public void buy() {
  5. System.out.println("社长给汤圆购买小鱼鱼");
  6. }
  7. }
  8. class FishFactory implements Factory{
  9. @Override
  10. public PetShop food() {
  11. return new Fish() ;
  12. }
  13. }
  14. /** * 提供者代码 * ======================= * 调用端代码 */
  15. class Test{
  16. public static void main(String[] args) {
  17. Factory factory = new MeatFactory();
  18. FootFactory footFactory = new FootFactory();
  19. Factory fishFactory = new FishFactory();
  20. PetShop food = factory.food();
  21. PetShop food1 = footFactory.food();
  22. PetShop food2 = fishFactory.food();
  23. food.buy();
  24. food1.buy();
  25. food2.buy();
  26. }
  27. }
  • 扩展产品,具体工厂也跟着扩展,不需要修改以前的代码,遵守了开闭原则。

好处:

  • 提供者修改代码后,调用者是不知道的,迪米特法则,也就是最少知道原则。
  • 在简单工厂上做咯优化,扩展产品,不需要修改以前的代码,只需要扩展一个产品和一个具体工厂即可

隔壁老王: “就拿你上面main方法里面的MeatFactory举例,假设MeatFactory类变为MeatFactory123,还是需要修改提供者和调用者两边的代码”
社长 “工厂名称有一套规范,只需要提供者保证尽量不改动类名就行,不然就是一个死循环,看看mybatis工厂类,版本变动,工厂名也不会变动。mybatis开发就相当于提供者,我们使用人员,就相当于调用者,如果mybatis工厂名变动,我们开发也不知道,这体验是不是很不好。所以,这个问题,不用担心,都有规范的”

抽象工厂模式(第三种)

社长: “我们之前只是很简单的实现吃,猫还会吃、睡等行为(多个产品等级)。产品等级一多,工厂类就会变得越来越臃肿”

使用工厂方法模式

20200404185659683.png

  • 需要使用到12个类,工厂的涉及类就有6个

    package com.cxyxs.designmode.factory.abstrastinterface;

    /* Description: Author: 程序猿学社 Date: 2020/4/4 18:20 Modified By: /
    public interface Foot {

    1. void eat();

    }

    class Meat implements Foot{

    1. @Override
    2. public void eat() {
    3. System.out.println("给汤圆吃肉干");
    4. }

    }

    class Fish implements Foot{

    1. @Override
    2. public void eat() {
    3. System.out.println("给汤圆吃小鱼仔");
    4. }

    }

    interface Toy{

    1. void play();

    }

    class CatTeaser implements Toy{

    1. @Override
    2. public void play() {
    3. System.out.println("社长利用逗猫棒跟汤圆玩耍");
    4. }

    }

    class Ball implements Toy{

    1. @Override
    2. public void play() {
    3. System.out.println("汤圆一个人跟小球进行玩耍");
    4. }

    }

    /* 食物工厂代码 */
    interface FootFactory{

    1. public Foot proFoot();

    }
    class MeatFactory implements FootFactory{

    1. @Override
    2. public Foot proFoot() {
    3. return new Meat();
    4. }

    }

    class FishFactory implements FootFactory{

    1. @Override
    2. public Foot proFoot() {
    3. return new Fish();
    4. }

    }

    /* 玩具工厂 */
    interface ToyFactory{

    1. public Toy proToy();

    }
    class CatTeaserFactory implements ToyFactory{

    1. @Override
    2. public Toy proToy() {
    3. return new CatTeaser();
    4. }

    }

    class BallFactory implements ToyFactory{

    1. @Override
    2. public Toy proToy() {
    3. return new Ball();
    4. }

    }

使用抽象工厂实现

社长: “先看看类图,再根据类图实现对应的代码”
20200404191133428.png

  1. package com.cxyxs.designmode.factory.abstrastinterface.plus;
  2. /** * Description: * Author: 程序猿学社 * Date: 2020/4/4 18:20 * Modified By: */
  3. public interface Foot {
  4. void eat();
  5. }
  6. class Meat implements Foot {
  7. @Override
  8. public void eat() {
  9. System.out.println("给汤圆吃肉干");
  10. }
  11. }
  12. class Fish implements Foot {
  13. @Override
  14. public void eat() {
  15. System.out.println("给汤圆吃小鱼仔");
  16. }
  17. }
  18. interface Toy{
  19. void play();
  20. }
  21. class CatTeaser implements Toy {
  22. @Override
  23. public void play() {
  24. System.out.println("社长利用逗猫棒跟汤圆玩耍");
  25. }
  26. }
  27. class Ball implements Toy {
  28. @Override
  29. public void play() {
  30. System.out.println("汤圆一个人跟小球进行玩耍");
  31. }
  32. }
  33. /** * 食物工厂代码 */
  34. interface Factory{
  35. public Foot proFoot();
  36. public Toy proToy();
  37. }
  38. class MeatAndCatTeaserFactory implements Factory {
  39. @Override
  40. public Foot proFoot() {
  41. return new Meat();
  42. }
  43. @Override
  44. public Toy proToy() {
  45. return new CatTeaser();
  46. }
  47. }
  48. class FishAndBallFactory implements Factory{
  49. @Override
  50. public Foot proFoot() {
  51. return new Fish();
  52. }
  53. @Override
  54. public Toy proToy() {
  55. return new Ball();
  56. }
  57. }
  • 工厂类由之前的6个,变为3个。减少了工厂类的臃肿

优点:

  • 抽象工厂可以理解为简单工厂和工厂方法模式的一个汇总
  • 对产品进行了抽象,适合一些维度有关联的(也就是说,有逻辑关系),例如,我举的这个例子,在工厂方法中,会有吃的工厂和玩的工厂,而在抽象工厂中,直接把这两个具体工厂直接抽象出来,合二为一。

缺点:

  • 要求抽象的多个产品,之前有逻辑关系
  • 后续需要生产喝的东西,需要修改抽象工厂,同时各个具体工厂也需要修改

隔壁老王: “社长,在抽象工厂模式中,吃和玩都是捆绑的关系,你这是捆绑销售,如果,我只想要实现吃,应该怎么办?”
社长: “如果只想实现吃,就可以使用工厂方法模式,应该根据具体问题,具体选择,使用那个模式。”

总结:

  • 不管是简单工厂、工厂方法模式、抽象工厂模式,我们应该灵活运用,主要的目的,还是为了解耦,写出可扩展性、可读的代码。

原创不易,不要白嫖,觉得有用的社友,给我点赞,让更多的老铁看到这篇文章。
因技术能力有限,如文中有不合理的地方,希望各位大佬指出,在下方评论留言,谢谢,希望大家一起进步,一起成长。

作者:程序猿学社
原创公众号:『程序猿学社』,专注于java技术栈,分享java各个技术系列专题,以及各个技术点的面试题。
原创不易,转载请注明来源(注明:来源于公众号:程序猿学社, 作者:程序猿学社)。

发表评论

表情:
评论列表 (有 0 条评论,109人围观)

还没有评论,来说两句吧...

相关阅读