从《狂飙》中学习工厂模式

142 阅读7分钟

二零二三年二月九日晚十点,程序员胖虎,像往常一样坐在电脑旁边 ,思考着如何完成领导交予的任务。

窗外细蒙蒙的雨丝夹着一星半点的雪花,正纷纷淋淋地向着大地飘洒着,写字楼刚下班的人们裹紧衣服,急忙忙地向地铁口走去。

这样的日子,如果不是上班,人们宁愿一整天足不出户。

夜晚十二点 ,胖虎回到家中,住在同屋的大雄见他闷闷不乐的样子,急忙忙地问胖虎出了什么事。

胖虎:今天领导交给了我一个任务,具体是:要根据用户消费的金额,给用户发放不同类型的奖品。

大雄:这也不难,先把你写的代码给我看下,我帮你分析下。

胖虎写了如下代码:


public class SendAward1 {
    public static void sendAward(){
        // 用户消费金额
        double xiaoFeiPrice=250.00;
        if(xiaoFeiPrice<100.00){
            System.out.println("给用户发放10元代金券");
        }
        if(xiaoFeiPrice>=100.00&&xiaoFeiPrice<=200.00){
            System.out.println("给用户发放酷我豪华VIP月卡");
        }
        if(xiaoFeiPrice>200.00){
            System.out.println("给用户发放实体奖品");
        }
    }
    public static void main(String [] args){
        sendAward();
    }
}

胖虎:老板说这样的代码不支持可扩展性,如果想发送其它类型奖品还得加一个ifelse判断去修改主类,这样扩展性较低,同时既增加了主类的修改风险,也增加了测试回归的过程。

大雄:其实这个也不难,让我帮你联想一下。胖虎你最近是不是在看《狂飙》,如果让你用代码去实现一个高启强去面馆吃猪脚面的过程, 你怎么实现?

胖虎:这还不简单, 这个和发优惠券过程差不多,就这么写不就行了:

public class MianGuan1 {
    // 面馆大厅
    public static void mianGuanDaTing(){
        // 高启强点红烧猪脚面
        String qiangLikeEat="红烧";
        if(qiangLikeEat=="麻辣"){
            System.out.println("做一份麻辣猪脚面  做面步骤1 清洗辣椒, 2 油炸辣椒, 步骤3 放辣椒与猪脚面 ");
            System.out.println("强哥吃麻辣猪脚面");
        }
        if(qiangLikeEat=="红烧"){
            System.out.println("做一份红烧猪脚面 做面步骤1 油炸猪脚, 步骤2 放入红烧料包, 步骤3 放红烧的猪脚与面中  ");
            System.out.println("强哥吃红烧猪脚面");
        }
        if(qiangLikeEat=="清炖"){
            System.out.println("做一份清炖猪脚面步骤1 炮制猪脚, 步骤2 放入炖料包, 步骤3 中火清炖半小时 放入面中 ");
            System.out.println("强哥吃清炖猪脚面");
        }
    }
    public static void main(String [] args){
        // 面馆大厅开业
        mianGuanDaTing();
    }
}

大雄:你这个代码把饭馆做面和强哥吃面都放在一起了,实际生活中,面馆的大厅和做面肯定不是强耦合的,做面只需要在厨房做就行了,如果在大厅做,会影响强哥的用餐体验。 也就是说,要把做面的过程封装起来,放在厨房去做 ,强哥只管吃就行。

胖虎点了点头,改进了代码:


public class MianGuan2 {
    // 面馆大厅
    public static void mianGuanDaTing(){
        // 高启强想吃的口味
        String qiangLikeEat="红烧";
        // 给高启强做好的面
        String qiangEat=mianGuanChuFang(qiangLikeEat);
        System.out.println("强哥吃"+qiangEat);
    }
    // 面馆厨房
    public static String mianGuanChuFang(String qiangLikeEat){
        // 做好的面
        String qiangEat="";
        if(qiangLikeEat=="麻辣"){
            System.out.println("做一份麻辣猪脚面  做面步骤1 清洗辣椒, 2 油炸辣椒, 步骤3 放辣椒与猪脚面 ");
            qiangEat="麻辣猪脚面";
        }
        if(qiangLikeEat=="红烧"){
            System.out.println("做一份红烧猪脚面 做面步骤1 油炸猪脚, 步骤2 放入红烧料包, 步骤3 放红烧的猪脚与面中  ");
            qiangEat="红烧猪脚面";
        }
        if(qiangLikeEat=="清炖"){
            System.out.println("做一份清炖猪脚面步骤1 炮制猪脚, 步骤2 放入炖料包, 步骤3 中火清炖半小时 放入面中 ");
            qiangEat="清炖猪脚面";
        }
        return qiangEat;
    }
    public static void main(String [] args){
        // 面馆大厅开业
        mianGuanDaTing();
    }
}

大雄: 这次修改的很好,使用了封装提高了餐馆吃面的体验。也可以进一步把做面的过程进一步封装程具体服务类,每个类归属一个职责。

胖虎挠了挠头,盯着代码 ,不知如何下手。

胖虎:做面的过程进一步封装程具体服务类 ,也就是说厨房只需要知道强哥吃什么口味的面就行,不同口味的面去生成不同的做面服务类就可以。但是不同的服务类调用的方法名不一样,返回结果也有可能有差异,如何进行统一呢?

大雄:除了封装,还有继承和多态你还没有用啊,想想怎么把继承和多态用上,提高可扩展性。(如果对多态和继承不清楚,请参考百度, 本文只做使用引导)

胖虎豁然开朗,急急忙忙说道:大雄 你是说, 虽然不同口味面使用了不同服务类,但是这些不同的类可以实现同一个接口,这样调用方法和返回结果就可以统一了。

大雄:很聪明嘛,那你在改下代码我看看。

胖虎(翘着二郎腿),修改代码:

public class MianGuan4 {
    // 面馆大厅
    public static void mianGuanDaTing(){
        // 高启强想吃的口味
        String qiangLikeEat="红烧";
        // 给高启强做好的面
        IMianGuanMakeMian mianGuanMakeMian = ChuFangFactory.makeMian(qiangLikeEat);
        String qiangEat=mianGuanMakeMian.makeMian();
        System.out.println("强哥吃"+qiangEat);
    }
    public static void main(String [] args){
        // 餐馆大厅开业
        mianGuanDaTing();
    }
}

// 做面工厂(厨房)
public class ChuFangFactory {
    public static IMianGuanMakeMian makeMian(String likeEat){
        switch (likeEat){
            case "麻辣":
                return new MianGuanMaLaMianService();
            case "红烧":
                return new MianGuanHongShaoMianService();
            case "清炖":
                return new MianGuanQingDunMianService();
            default:return null;
        }
    }
}
// 做面接口
public interface IMianGuanMakeMian {
    public String makeMian();
}
// 红烧猪脚面
public class MianGuanHongShaoMianService implements IMianGuanMakeMian {
    // 模拟Autowire注入 红烧猪脚面的服务
    HongShaoMianService hongShaoMianService=new HongShaoMianService();
    @Override
    public String makeMian() {
        return hongShaoMianService.makeHongShaoZhuJiaoMian();
    }
}
// 麻辣猪脚面
public class MianGuanMaLaMianService implements IMianGuanMakeMian {
    // 模拟Autowire注入 麻辣猪脚面服务
    MalaMianService malaMianService=new MalaMianService();
    @Override
    public String makeMian() {
        return malaMianService.makeMalaMian();
    }
}
// 清炖猪脚面服务
public class MianGuanQingDunMianService implements IMianGuanMakeMian {
    // 模拟Autowire注入 清炖猪脚面服务
    QingDunMianService qingDunMianService=new QingDunMianService();
    @Override
    public String makeMian() {
        return qingDunMianService.makeQingDunZhuJiaoMian();
    }
}

三个真正的做面服务类:


// 制作红烧猪脚面服务
public class HongShaoMianService {
    public String makeHongShaoZhuJiaoMian(){
        System.out.println("做一份红烧猪脚面 做面步骤1 油炸猪脚, 步骤2 放入红烧料包, 步骤3 放红烧的猪脚与面中");
        return "红烧猪脚面";
    }
}
// 制作麻辣猪脚面服务
public class MalaMianService {
    public String makeMalaMian(){
        System.out.println("做一份麻辣猪脚面  做面步骤1 清洗辣椒, 2 油炸辣椒, 步骤3 放辣椒与猪脚面 ");
        return "麻辣猪脚面";
    }
}
// 清炖猪脚面服务
public class QingDunMianService {
    public String makeQingDunZhuJiaoMian(){
        System.out.println("做一份清炖猪脚面步骤1 炮制猪脚, 步骤2 放入炖料包, 步骤3 中火清炖半小时 放入面中");
        return "清炖猪脚面";
    }
}

胖虎:做面过程封装到了做面工厂里,根据不同的口味返回不同的做面服务,目前有三个做面服务MalaMianService HongShaoMianService QingDunMianService而且里面做面方法也不一样 分别是makeMalaMian makeHongShaoZhuJiaoMian makeQingDunZhuJiaoMian 为了统一调用统一返回,我增加三个实现类MianGuanHongShaoMianService MianGuanMaLaMianService  MianGuanQingDunMianService 实现了ICanGuanMakeMian接口 ,把三个做面的服务类进行了一层包装。

大雄看了胖虎写好的代码,夸赞道:你也太聪明,这样即使强哥想吃卤汁口味的猪脚面,只需要实现IMianGuanMakeMian接口 ,添加一个卤汁面的做面Service服务就可以,mianGuanDaTing作为调用方是无感知的,从而提高了面馆的扩展性。

大雄:现在知道怎么改进老板给你的任务了吧?

胖虎:嘻嘻,知道了,我去修改一下:

public class SendAward2 {
    public static void sendWard(){
        // 用户消费金额
        double xiaoFeiPrice=250.00;
        // 奖品工厂
        ISendAward sendAward = SendAwardFactory.sendAward(xiaoFeiPrice);
        String award=sendAward.sendAward();
        System.out.println("用户获得的奖品是:"+award);
    }
    public static void main(String [] args){
        // 发送奖品
        sendWard();
    }
}

// 发送奖品工厂
public class SendAwardFactory {
    public static ISendAward sendAward(double xiaoFeiPrice){
        if(xiaoFeiPrice<100.00){
            return new SendDaiJinQuanService();
        }
        if(xiaoFeiPrice>=100&&xiaoFeiPrice<=200){
            return new SendKuWoVIPService();
        }
        if(xiaoFeiPrice>200){
            return new SendCommandService();
        }
        return null;
    }
}

三个包装服务类 实现统一接口ISendAward

public class SendDaiJinQuanService implements ISendAward {
    // 模拟Autowire注入 代金券服务
    DaiJinQuanService daiJinQuanService=new DaiJinQuanService();
    @Override
    public String sendAward() {
        return null;
    }
}
public class SendCommandService implements ISendAward {
    // 模拟Autowire注入 实体奖品服务
    ShiTiCommandService shiTiCommandService=new ShiTiCommandService();
    @Override
    public String sendAward() {
        return shiTiCommandService.sendCommand();
    }
}
public class SendKuWoVIPService implements ISendAward {

    // 模拟Autowire注入 酷我VIP服务
    KuWoVIPService kuWoVIPService=new KuWoVIPService();
    @Override
    public String sendAward() {
        return kuWoVIPService.sendKuWoVIP();
    }
}

三个真正发送奖品服务类:

// 发代金券服务
public class DaiJinQuanService {
    public String sendDaiJinQuan(){
        return "给用户发放10元代金券";
    }
}
// 酷我VIP
public class KuWoVIPService {
    public String sendKuWoVIP(){
        return "给用户发放酷我豪华VIP月卡";
    }
}
// 实体奖品服务
public class ShiTiCommandService {
    public String sendCommand(){
        return "实体奖品";
    }
}

胖虎:大雄,这样改之后,工厂类还是有ifelse啊,该怎么办?

大雄:你忘了还有反射吗,可以利用反射修改,减少ifelse调用,但是现在你必须睡觉了,因为已经晚上十二点了,明天我在告诉你吧(下期),现在的代码已经实比原来好很多了,明天上班你可以先交差了。

夜晚十二点,胖虎看着修改后代码,伸了伸懒腰,起身朝着卧室走去。

窗外飘飘零零的雪花早已融化,一个个平平常常的夜晚似乎什么都没发生过,只是早上洗脸时会发现,又是另外一张成熟的脸庞。