五、工厂模式—旅行的钱怎么来 #和设计模式一起旅行#

君子爱财,取之有道!—— 出自《增广贤文》### 故事背景上一篇我和MM相约好了,去旅行了,但是旅行是需要Money的啊,作为有个搬砖的码农,没钱啊,怎么呢!不能穷游啊,真是愁人啊 !哎 ,办法总归困难多,这一篇就是写写如何通过工厂拿到钱,然后开始我们的旅行,为一路上能胡吃海喝打下基础!下面开始我们的造钱之旅!“` public class Client{publi…

大家好,又见面了,我是全栈君。

君子爱财,取之有道!—— 出自《增广贤文》

故事背景

上一篇我和MM相约好了,去旅行了,但是旅行是需要Money的啊,作为有个搬砖的码农,没钱啊,怎么呢!不能穷游啊,真是愁人啊 !哎 ,办法总归困难多,这一篇就是写写如何通过工厂拿到钱,然后开始我们的旅行,为一路上能胡吃海喝打下基础!

下面开始我们的造钱之旅!

 public class Client{

    public static void main(String agrs[]){
        PatternMM mm = new PatternMM();
        Me me = new Me();
        String money = me.getMoney(100.00);
        //带着钱带着mm就一路向西
        me.travel(mm,money);
    }     
 } 

 class Me{
     private String name;
     //set get

     public String getMoney(double money){
         //从建行取钱
         JianHangBack jhBack = new JianHangBack();
         retun jhBack.getWithdrawals(money);

     }
     public void travel(PatternMM mm,String money){
         System.out.println(this.name + "带着" money +"和"+ 
         mm.getName +"一路向西->去旅行,准备好一路的各种happy了!"):
     }
 }

 class PatternMM{
     private String name;
     //set get

 }

 //建行
 class JianHangBack{
     //double countMoney = 100;

     public String getWithdrawals(double money){
         //if(money <= countMoney){ 
   
         // return "取钱" + money;
         //}else{}
         //假设银行钱非常多,实际也是非常多
         return moeny+"元";
     }

 }

大家看这样的代码有没有觉得怪怪的,假如我先行建行里面没有钱了,我要去工商银行取钱,那么我 getMoney() 是不是就要修改,还有一种可能性是我旅行到一个地方,那个地方没有了建行,我该怎么取钱呢?

为了解决上面的问题,getMoney()又有了下面的这一版代码 .

//(工,农,中,建) 四大行取钱 -- 具体实现参考JianHangBack类
public String getMoney(String backName,double money){

    if("jianhang".equals(backName)){
        //从建行取钱
        JianHangBack类 back = new JianHangBack();
        retun back.getWithdrawals(money);
     }else if("nonghang".equals(backName)){
        //农行取钱
        NongHangBack back = new NongHangBack();
        return back.getWithdrawals(money);
     }else if("gonghang".equals(backName)){
        //工商银行取钱
        GongShangBack back = new GongShangBack();
        return back.getWithdrawals(money);
     }else if("zhongguo".equals(backName)){
         //中国银行取钱
        ZhongGuoBack back = new ZhongGuoBack();
        return back.getWithdrawals(money);
     }

     return "no money";
}

这一版比上一版能好一点,灵活了一点,此时我jianhang没钱了,我可以传入gonghang,去工商银行去取钱。比如

 public class Client{

    public static void main(String agrs[]){
        PatternMM mm = new PatternMM();
        Me me = new Me();
        String money = me.getMoney("jianhang",100.00);
        //带着钱带着mm就一路向西
        me.travel(mm,money);
    }    

 } 
}

虽然灵活了很多,但是上面这一版还是存在一些问题。
– 当我银行卡不是(工,农,中,建)了,有新开户了银行卡,那么我还是需要修改getMoney,使用更多的额if–else 了,大量条件语句的存在还将影响系统的性能,程序在执行过程中需要做大量的条件判断。!!!
– 还有银行和我耦合太严重了,我(Me类)职责过重,累坏我了。能不能有一个专门创建银行的地方,我只是去获取钱就好了,其他的我都不关注!!!!

分析:上面这种方式直接使用new 去创建具体的银行(使用new创建实例),这种方式使得我和银行高度的耦合,灵活性和可扩展性很差,并且一但我又新增银行卡,我就必须修改Me中的getMoney,不符合设计模式的开闭原则和面向接口编程,通过分析我们可以发现每个银行都有一个getWithdrawals,方法,此时可以将此抽象出来!

我发现上面的问题后,我就问设计模式MM!
– 我问:MM好,有没有好的办法解决上面的问题呢?
– MM回答:应该有,你可以了解一下工厂模式!

故事主角——工厂模式

通过上面的例子引入,本篇要讲的工厂设计模式,循序渐进:
简单工厂模式(青铜级别)—> 工厂方法模式(铂金级别) ——> 抽象工厂模式(钻石级别)

简单工厂模式不属于23种设计模式,但是这种思想在应用还是很常见的。这里使用简单工厂模式入门了。下面看简单工厂模式!

为什么要引入工厂类,大家可参见:创建对象与使用对象——谈谈工厂的作用

主角1:简单工厂模式。

简单设计模式思想很就如同它的名字,很简单!还是用上面银行举例说明。

简单工厂模式:首先定义一个工厂类,然后这个工厂类可以根据不同的参数返回不同的实例,被创建的实例通常都具有共同的父类。

简单工厂的类图如下:
简单工厂模式类图

①:将所有的银行公共的代码抽象和提取后封装在一个Back类,称为抽象产品类;
②:每一个具体银行(具体的产品类)都是Back(抽象产品类)的的子类,即具体产品类是抽象产品类的子类;
③:工厂类用于创建各种产品,在工厂类中提供一个创建产品的工厂方法,该方法可以根据所传入的参数不同创建不同的具体产品对象!(本例中工厂创建不同的银行产品对象!)

public class abstract Back{ 
   

  public abstract String getWithdrawals(double money);
}

public class JianHangBack extends Back{ 
   
    public String getWithdrawals(double money){
        System.out.println("建行取钱....");
        return money;

    }

}

public class GongHangBack extends Back{ 
   
    public String getWithdrawals(double money){
        System.out.println("工行取钱....");
        return money;

    }

}
public class Factory{

public static Back getBack(String backName){

     if("jianhang".equals(backName)){
         //建行
         return new JianHangBack();
     }else if("nonghang".equals(backName)){
         //农行
         return new NongHangBack();
     }else if("gonghang".equals(backName)){
         //工商银行
         return new GongHangBack();
     }
     //....
  }

}

因为简单工厂中创建实例的方法是静态的方法,有时候也被称为简单静态工厂!

  • Factory(工厂角色):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑;工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product。

  • Product(抽象产品角色):它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。

  • ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法。

此时我们的Client就可以这样写了:

 public class Client{

    public static void main(String agrs[]){
        PatternMM mm = new PatternMM();
        Me me = new Me();
        Back back = Factory.getBack("jianhang"); //这样Me就更加单纯了,不在管理银行的创建了!
        String money = back.getMoney(100.00);
        //带着钱带着mm就一路向西
        me.travel(mm,money);
    }    

 } 
  • 这里提供一个简单的工厂,提供静态的方法,根据传入的参数创建不同的产品对象!
  • 这样做的好处是在一个地方去管理产品的创建的过程,产品的创建和客户代码解耦!这里只需要修改参数就能修改取钱的银行了!

比如:

Factory.getBack(“gonghang”); //获取工行实例

MM问:你现在这种方式还是不好,思考两个问题?
– 1:我要添加银行的时候还是要修改Factory类的代码,不符合设计原则的开闭原则啊?
– 2:在客户端中我要切换银行还是需要修改参数,这样还是不够灵活啊?

我说:第一个问题目前还没想到好的办法,等我继续研究研究!第二个问题我知道有一种解决办法,就是讲参数存储在配置文件中,这样只需要修改配置文件即可,就不需要修改代码了,这样就灵活了。

从配置文件读取参数这里就不讲解 了,可以使用properties或者xml,修改后的Client代码大概如下的样子:

//具体的PropertiesUtils 和 XMLUtils 读者可以自行实现,比较简单!这样就不需要客户端代码了,符合“开闭原则”,哈哈!
 public class Client{

    public static void main(String agrs[]){
        PatternMM mm = new PatternMM();
        Me me = new Me();
        // String backName = PropertiesUtils.getBackName();
        String backName = XMLUtils.getBackName();
        Back back = Factory.getBack("jianhang"); //这样Me就更加单纯了,不在管理银行的创建了!
        String money = back.getMoney(100.00);
        //带着钱带着mm就一路向西
        me.travel(mm,money);
    }    

 } 

总结一下简单工厂
  • 优点

    简单,工厂类决定什么时候创建哪一个产品的实例!使用工厂类可以使具体产品的创建和客户端解耦!

  • 缺点

    工厂集中了所有产品的创建过程,职责太重,一旦工厂出现问题,整个系统都收到影响,并且扩展性不好,在添加新的产品的时候需要修改工厂代码,当产品太多的时候,增加工厂的复杂度,不容易理解,难以维护!

  • 使用场景

    创建的类比较好的情况,并且业务逻辑不太复杂的场景!

主角2:工厂方法模式

MM的第一问,上面没能回答! 简单工厂模式虽然简单,但存在一个很严重的问题。当系统中需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,将违背“开闭原则”,如何实现增加新产品而不影响已有代码?

工厂方法(Factory Method)模式:定义了一个创建对象的接口,但由子类决定要实例化类哪一个。工厂方法让类的实例化推迟到子类。
  • 在工厂方法模式中,不在提供一个统一的类来创建所有的产品对象,而是针对不同的产品提供不同的工厂。
  • 这样就可以抽象出一个工厂角色,然后根据产品等级结构对应工厂等级结构!

工厂方法模式简单类图示意图:

工厂方法模式简单类图示意图

在工厂模式中有如下的角色:

  • Product(抽象产品):定义产品的接口。是工厂方法模式创建对象的超类型,也就是产品对象的公共父类。
  • ConcreteProduct(具体产品):实现了抽象产品接口,具体的产品由具体的工厂创建,具体工厂和具体产品之间一一对应。
  • Factory(抽象工厂):在抽象工厂类(可以是接口或者抽象类)中,申明一个工厂方法,用于返回一个产品,抽象工厂是工厂方法模式的核心,多有创建对象必须实现该接口。
  • ConcreteFactory(具体工厂):抽象工厂的子类,实现抽象工厂方法,由客户端调用,返回一个具体的产品实例。

根据上面的学习,重新改写之前的代码:

public class abstract Back{ 
   

  public abstract String getWithdrawals(double money);
}

public class JianHangBack extends Back{ 
   
    public String getWithdrawals(double money){
        System.out.println("建行取钱....");
        return money;

    }

}

public class GongHangBack extends Back{ 
   
    public String getWithdrawals(double money){
        System.out.println("工行取钱....");
        return money;

    }

}
public interface BackFactory{
    public Back createBack();
}

public JianHangBackFactory implements BackFactory{

    public Back createBack(){
        System.out.println("寻找建行....");

        JianHangBack back = new JianHangBack();

        retrun back;

    }

}

public GongHangBackFactory implements BackFactory{

    public Back createBack(){
        System.out.println("寻找工行....");

        GongHangBack back = new GongHangBack();

        retrun back;

    }

}

编写我们 的客户端代码:


 public class Client{

    public static void main(String agrs[]){
        PatternMM mm = new PatternMM();
        Me me = new Me();
        Factory facory = new JianHangBackFactory();
        facory.createBack();
        Back back = facory.createBack(); 
        String money = back.getMoney(100.00);
        //带着钱带着mm就一路向西
        me.travel(mm,money);
    }    

 } 

工厂方法模式中的工厂能否为静态方法,为什么?

  • 此种方式解决了MM提出的每次新增新的产品就要修改工厂。现在如果新增新的产品如NongHangBack时候,只需要继承Back,然后在创建实现BackFactory的工厂类就行了! 可以方便的扩展代码,扩展的时候也不需要修改原来的代码了。
  • 但是这种方式中客户端不能传递参数,这样当客户端需要从JianHang切换到gongHang 的时候,客户端的代码还是要修改啊,那该怎么办呢?

针对上面第二点,客户端在进行切换产品的时候还是需要修改代码,给出解决方案,就是使用Java 的反射功能!通过将具体的产品配置在配置文件中,获取 BackFactory 的信息,然后通过反射获取具体的实例对象。这里不做深入讲解,不知道如何使用反射的伙伴,请自行查资料了解!

简单举例properties的配置:factory.properties

BackFactory=com.dufy.pattern.factory.JianHangBackFactory

简单反射获取实例的伪代码:

public class PropertiesUtils{


    private PropertiesUtils(){}

    public Object getBackFactory(){

        //读取配置文件,获取到 name 为 BackFactory 的值
        String factoyName = propert.getPro(name);//伪代码
        Class cls = Class.forName(factoryName);

        return (Object)cla.newInstance(); //返回实例对象


    }
}

此时客户端代码为:


 public class Client{

    public static void main(String agrs[]){
        PatternMM mm = new PatternMM();
        Me me = new Me();
        BackFactory facory = (BackFactory)PropertiesUtils.getBackFactory();//强转为BackFactory对象
        facory.createBack();
        Back back = facory.createBack(); 
        String money = back.getMoney(100.00);
        //带着钱带着mm就一路向西
        me.travel(mm,money);
    }    

 } 

通过上面的讲解你能否发现这次这种实现方法比之前的简单工厂模式灵活了很多,然后通过反射方式,系统只需要修改配置文件就可以修改具体产品的创建,代码的可维护和可扩展性得到提升。

通过简单上面的示例,
MM问: 每个Back都有一个自己工厂类,那么我们就直接使用它自己的工厂获取钱就好了啊,我们不需要知道Back了,交个具体工厂搞定吧!你在想想怎么实现?

于是有了下面的这中工厂实现方法:

// 将BackFactory定义为抽象类

public abstract class BackFactory{
    public String getMoney(){
        Back back  =  this.createBack();
        String money = back.getMoney(100.00);
        return money;
    }
    public abstract Back createBack(); 
}

public class JianHangFactory implments Factory{

     public Back createBack(){
        System.out.println("寻找建行....");

        JianHangBack back = new JianHangBack();

        retrun back;

    }
}

public class Client{

    public static void main(String agrs[]){
        PatternMM mm = new PatternMM();
        Me me = new Me();
        //这样是不是更简单了一点呢,客户端只需要知道一个银行工厂,
        //然后我就取钱就好了,其他的都不去理会! 真实也是这样那该多好,哈哈!
        BackFactory facory = (BackFactory)PropertiesUtils.getBackFactory();//强转为BackFactory对象
        String money = facory.getMoney(100.00);
        //带着钱带着mm就一路向西
        me.travel(mm,money);
    }    

 } 

MM说:“通过将业务方法的调用移入工厂类,可以直接使用工厂对象来调用产品对象的业务方法,客户端无须直接使用工厂方法,在某些情况下我们也可以使用这种设计方案”。

总结一下工厂方法模式

总体上说工厂方法模式比简单工厂要好了很多,可以说工厂方法模式是简单工厂的一个延伸,继承了简单工厂的优点,也解决了简单工厂存在的一些问题。(简单工厂把全部的事情放到一个地方去处理了,而工厂方法模式是创建一个框架,让子类决定如何实现,更有弹性。)
工厂模式是使用频率很高的一个模式,在很多优秀的开源框架中能够看到。

  • 优点:
  • ①:面向抽象编程或者说面向接口编程,具体的工厂实现具体的产品,对用户影藏实现细节,用户只需关注产品的工厂即可,其他的不需要考虑
  • ②:能动态的扩展新的产品已经产品的实现工厂,不需要修改之前的代码,符合开闭原则。
  • ③:因为是具体的产品有具体的工厂实现,那么可以在实现的时候做一些自定义的操作!

  • 缺点:

  • ①:如果新增的产品类很多,那么需要新增具体产品实现的工厂也很多,可能因此增加类的个数,并增加系统的复杂度,在编译上给系统带来一定的开销。
  • ②:考虑到扩展性,引入抽象层,在客户端均使用抽象层进行定义,增加系统的理解难度,并且会用户其他技术,如反射等,增加实现难度。

  • 使用场景:

  • 客户端不需要知道具体的产品类,只需要知道对应的工厂就行,比如我不需要知道JianHangBack,我只需要知道JianHangBackFactory即可。可讲具体的工厂配置到配置文件等可以动态获取的地方,就能动态加载具体工厂。

MM说:到这里其实我们本次旅行的Money基本就可以解决了,不用为Money发愁了!但是做一种假设,如果我的钱放到建行和工商,必须在两个银行取完钱就能有足够的钱开始旅游,去浪漫的巴黎,以及东京的东京热!

主角3:抽象工厂模式

上面我们谈论了简单工厂和工厂方法模式,现在我们在认识一下最后的这一个压轴的主角!考虑到工厂方法模式中每个工厂只能生产一类产品,那么可能会导致存在大量的工厂类,所以我们考虑将一些相关的产品组成一个“产品族”,由同一个工厂类生产!这就是本场主角:抽象工厂模式!

抽象工厂有事工厂方法的一种拓展,在通常情况下,如果要使系统具有扩展性,那么就进行抽象,使用继承和组合的方式!


-- JianHangBack     --BeijingJiangHangBack     --ShangHaiJiangHangBack     ..
-- GongHangBack     --BeijingGongHangBack     --ShangHaiGongHangBack 

BeiJingBackFactory
    -- createJiangHangBack()     -- createGongHangBack() ..

产品族

上面这幅片结合上面的Back的代码!

interface Back{
    String getMoney(double money);
}
class JianHangBack implements Back{

    public String getMoney(double money){
        //返回钱
    }

}

interface BackFactory{
    public JianHangBack createJiangHangBack();
    public GongHangBack createGongHangBack();
}

public class BeiJingBackFactory implments BackFactory{

    public JianHangBack createJiangHangBack(){
        return new BeijingJiangHangBack();
    }

    public GongHangBack createGongHangBack(){
        return BeijingGongHangBack();

    }

}

public class ShangHaiBackFactory implments BackFactory{

    public JianHangBack createJiangHangBack(){
        return new ShangHaiJiangHangBack();
    }

    public GongHangBack createGongHangBack(){
        return ShangHaiGongHangBack();

    }

}

public class Client{

    public static void main(String agrs[]){
        PatternMM mm = new PatternMM();
        Me me = new Me();
        //从北京出发 假如我的钱存在建行和工商,必须在两个银行取
        BackFactory facory;
        JianHangBack jhBack;
        GongHangBack ghBack;
        //这里可以使用配置文件获取,到了北京使用北京的工厂,到了上海使用上海的工厂!
        facory = new BeiJingBackFactory(); 
        jhBack = facory.createJiangHangBack();
        gsMoney = facory.createGongHangBack();
        String jhMoney = jhBack.getMoney(100.00);
        String gsMoney = jhBack.getMoney(100.00);
        String money = jhMoney + gsMoney;
        //带着钱带着mm就一路向西
        me.travel(mm,money);
    }    

 } 

根据上面的简单例子来说一下抽象工厂模式吧:
###### 抽象工厂模式:定义一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类!

抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际传出的具体产品是什么。

抽象工厂模式

抽象模式中包括的几个角色;

抽象不一定就是 Abstract类 也可以是接口!
– AbastactFactrory(抽象工厂),申明一组用于创建一族产品的方法,每一方法对应一种产品。
– ConcreteFactory(具体工厂):实现抽象工厂中申明的创建产品的方法生成一组具体产品。
– AbstractProduct(抽象产品):为产品申明接口。
– ConcreteProduct(具体产品):实现抽象产品接口,并定义生成的具体类以及业务方法。

abstract class AbstractFactory { 
     
public abstract AbstractProductA createProductA(); //工厂方法一 
public abstract AbstractProductB createProductB(); //工厂方法二 
……  
}  

//相比工厂方法模式,,这个工厂可以生产一个产品族
class ConcreteFactory1 extends AbstractFactory { 
     
    //工厂方法一 
public AbstractProductA createProductA() {  
    return new ConcreteProductA1();  
}  

//工厂方法二 
public AbstractProductB createProductB() {  
    return new ConcreteProductB1();  
}  

……  
} 

注:上面内容摘自工厂三兄弟之抽象工厂模式(三)

总结抽象工厂
  • 优缺点:
    有点是新增产品族很方便,但是在原有的产品新增产品的时候回很麻烦。如新增一个XianBackFactory很容易,但是要在增加一个NonghangBack在BackFactory中的话,所有的产品族都要修改!也违反开闭原则。
  • 使用场景:产品等级稳定,当你想要创建产品家族将制造的相关产品集合起来时候使用。

在现实中如换肤操作可以使用,换肤后背景颜色变了,字体变了,可以使用抽象工厂模式!

到此整个工厂模式就扯完了,不管扯的咋个,花了时间看书学习,自己总算还是有收获!和MM造钱之旅完成!

Next 期待下一篇吧!! 有钱了出发了,然后不同的地方可以选择不同的交通工具(策略模式),那就讲一下如果选择交通工具吧!

参考

本专栏文章列表

一、设计模式-开篇—为什么我要去旅行? #和设计模式一起旅行#
二、设计模式-必要的基础知识—旅行前的准备 #和设计模式一起旅行#
三、设计模式介绍—她是谁,我们要去哪里? #和设计模式一起旅行#
四、单例模式—不要冒充我,我只有一个! #和设计模式一起旅行#
五、工厂模式—旅行的钱怎么来 #和设计模式一起旅行#
六、策略模式—旅行的交通工具 #和设计模式一起旅行#
七、观察者模式——关注我,分享旅途最浪漫的瞬间! #和设计模式一起旅行#
八、装饰者模式—巴厘岛,奶茶店的困扰! #和设计模式一起旅行#
九、命令模式—使用命令控制奶茶店中酷炫的灯 #和设计模式一起旅行#
十、模板方法模式—制作更多好喝的饮品! #和设计模式一起旅行#
十一、代理模式 —专注,做最好的自己!#和设计模式一起旅行#
十二、适配器模式——解决充电的烦恼 #和设计模式一起旅行#
十三、外观模式—— 简化接口 #和设计模式一起旅行#
十四、迭代器模式—— 一个一个的遍历 #和设计模式一起旅行#
十五、组合模式—— 容器与内容的一致性 #和设计模式一起旅行#
十六、状态模式—用类表示状态 #和设计模式一起旅行#
十七、访问者模式-访问数据结构并处理数据 #和设计模式一起旅行#
十八、职责链模式-推卸责任,不关我的事,我不管!#和设计模式一起旅行#
十九、原型模式—通过复制生产实例 #和设计模式一起旅行#
二十、设计模式总结—后会有期 #和设计模式一起旅行#


如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!

如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!


欢迎访问我的csdn博客,我们一同成长!

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页 : http://blog.csdn.net/u010648555

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/121101.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号