您的位置:首页 > 其它

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

2020-04-05 07:16 405 查看

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

文章目录

前言

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

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

小故事

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

传统方式

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

package com.cxyxs.designmode.factory;
/**
* Description:
* Author: 程序猿学社
* Date:  2020/4/3 23:16
* Modified By:
*/
public interface PetShop {
void buy();
}

class  Meat implements  PetShop{

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

/**
* 提供者
* =======================
* 调用者
*/
class  Test{
public static void main(String[] args) {
PetShop ps = new Meat();
ps.buy();
}
}


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

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

简单工厂(第一种)

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

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

  • 不清楚这个图如何画的,可以百度查一下UML类图
package com.cxyxs.designmode.factory.simple;

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

class  Meat implements  PetShop{

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

class  Foot implements  PetShop{

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

class  CatFootFacoty{
public  static  PetShop buy(String name){
switch (name){
case "猫粮":
return  new Foot();
case "肉干":
return  new Meat();
}
return  null;
}
}

/**
* 提供者代码
* =======================
* 调用端代码
*/
class  Test{
public static void main(String[] args) {
PetShop ps = CatFootFacoty.buy("肉干");
PetShop ps1 = CatFootFacoty.buy("猫粮");
ps.buy();
ps1.buy();
}
}


好处:

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

缺点:

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

工厂方法模式(第二种)

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

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

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

  • 这个类图代码写完后,可以自动生成,在idea中,右击

    再把对应的类拖进来就可以自动生成,
package com.cxyxs.designmode.factory.methodmodel;

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

class  Meat implements PetShop {

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

class  Foot implements PetShop {

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

interface  Factory{
PetShop food();
}

class   MeatFactory implements  Factory{

@Override
public PetShop food() {
return new Meat();
}
}

class  FootFactory implements  Factory{

@Override
public PetShop food() {
return new Foot();
}
}

/**
* 提供者代码
* =======================
* 调用端代码
*/
class  Test{
public static void main(String[] args) {
Factory factory = new MeatFactory();
FootFactory footFactory = new FootFactory();

PetShop food = factory.food();
PetShop food1 = footFactory.food();

food.buy();
food1.buy();
}
}


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

/**
* 扩展吃鱼的部分开头
*/
class  Fish implements PetShop {

@Override
public void buy() {
System.out.println("社长给汤圆购买小鱼鱼");
}
}
class FishFactory implements  Factory{

@Override
public PetShop food() {
return new Fish() ;
}
}

/**
* 提供者代码
* =======================
* 调用端代码
*/
class  Test{
public static void main(String[] args) {
Factory factory = new MeatFactory();
FootFactory footFactory = new FootFactory();

Factory fishFactory = new FishFactory();

PetShop food = factory.food();
PetShop food1 = footFactory.food();
PetShop food2 = fishFactory.food();

food.buy();
food1.buy();
food2.buy();
}
}
  • 扩展产品,具体工厂也跟着扩展,不需要修改以前的代码,遵守了开闭原则。

好处:

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

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

抽象工厂模式(第三种)

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

使用工厂方法模式

  • 需要使用到12个类,工厂的涉及类就有6个
package com.cxyxs.designmode.factory.abstrastinterface;

/**
* Description:
* Author: 程序猿学社
* Date:  2020/4/4 18:20
* Modified By:
*/
public interface Foot {
void eat();
}

class  Meat implements  Foot{

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

class  Fish  implements  Foot{

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

interface Toy{
void play();
}

class  CatTeaser implements Toy{

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

class Ball implements  Toy{

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

/**
* 食物工厂代码
*/
interface FootFactory{
public Foot proFoot();
}
class MeatFactory implements  FootFactory{

@Override
public Foot proFoot() {
return new Meat();
}
}

class FishFactory implements  FootFactory{

@Override
public Foot proFoot() {
return new Fish();
}
}

/**
* 玩具工厂
*/
interface ToyFactory{
public Toy proToy();
}
class  CatTeaserFactory implements  ToyFactory{

@Override
public Toy proToy() {
return new CatTeaser();
}
}

class  BallFactory implements  ToyFactory{

@Override
public Toy proToy() {
return new Ball();
}
}

使用抽象工厂实现

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

package com.cxyxs.designmode.factory.abstrastinterface.plus;

/**
* Description:
* Author: 程序猿学社
* Date:  2020/4/4 18:20
* Modified By:
*/
public interface Foot {
void eat();
}

class  Meat implements Foot {

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

class  Fish  implements Foot {

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

interface Toy{
void play();
}

class  CatTeaser implements Toy {

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

class Ball implements Toy {

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

/**
* 食物工厂代码
*/
interface Factory{
public Foot proFoot();
public Toy proToy();
}

class MeatAndCatTeaserFactory implements Factory {

@Override
public Foot proFoot() {
return new Meat();
}

@Override
public Toy proToy() {
return new CatTeaser();
}
}

class FishAndBallFactory implements  Factory{

@Override
public Foot proFoot() {
return new Fish();
}

@Override
public Toy proToy() {
return new Ball();
}
}
  • 工厂类由之前的6个,变为3个。减少了工厂类的臃肿

优点:

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

缺点:

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

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

总结:

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

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

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

  • 点赞 9
  • 收藏
  • 分享
  • 文章举报
程序猿学社 博客专家 发布了290 篇原创文章 · 获赞 1250 · 访问量 38万+ 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: