本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
重学 Java 设计模式:实战工厂方法模式
奔跑的男人 · 376浏览 · 发布于2020-05-21 +关注

一、前言

好看的代码千篇一律,恶心的程序升职加薪。

该说不说几乎是程序员就都知道或者了解设计模式,但大部分小伙伴写代码总是习惯于一把梭。无论多少业务逻辑就一个类几千行,这样的开发也可以归纳为三步;定义属性、创建方法、调用展示,Done!只不过开发一时爽,重构火葬场。

好的代码不只为了完成现有功能,也会考虑后续扩展。在结构设计上松耦合易读易扩展,在领域实现上高内聚不对外暴漏实现细节不被外部干扰。而这就有点像家里三居(MVC)室、四居(DDD)室的装修,你不会允许几十万的房子把走线水管裸漏在外面,也不会允许把马桶放到厨房,炉灶安装到卫生间。

谁发明了设计模式? 设计模式的概念最早是由 克里斯托佛·亚历山大 在其著作 《建筑模式语言》 中首次提出的。 本书介绍了城市设计的 “语言”,提供了253个描述城镇、邻里、住宅、花园、房间及西部构造的模式, 而此类 “语言” 的基本单元就是模式。后来,埃里希·伽玛、 约翰·弗利赛德斯、 拉尔夫·约翰逊 和 理查德·赫尔姆 这四位作者接受了模式的概念。 1994 年, 他们出版了 《设计模式: 可复用面向对象软件的基础》 一书, 将设计模式的概念应用到程序开发领域中。

其实有一部分人并没有仔细阅读过设计模式的相关书籍和资料,但依旧可以编写出优秀的代码。这主要是由于在经过众多项目的锤炼和对程序设计的不断追求,从而在多年编程历程上提炼出来的心得体会。而这份经验最终会与设计模式提到的内容几乎一致,同样会要求高内聚、低耦合、可扩展、可复用。你可能也遇到类似的经历,在学习一些框架的源码时,发现它里的某些设计和你在做开发时一样。

我怎么学不会设计模式? 钱也花了,书也买了。代码还是一坨一坨的!设计模式是由多年的经验提炼出来开发指导思想。就像我告诉你自行车怎么骑、汽车怎么开,但只要你没跑过几千公里,你能记住的只是理论,想上道依旧很慌!

所以,本设计模式专题系列开始,会带着你使用设计模式的思想去优化代码。从而学习设计模式的心得并融入给自己。当然这里还需要多加练习,一定是人车合一,才能站在设计模式的基础上构建出更加合理的代码。

二、开发环境

  1. JDK 1.8

  2. Idea + Maven

  3. 涉及工程三个,可以通过关注公众号:bugstack虫洞栈,回复源码下载获取。你会获得一个连接打开后的列表中编号18:itstack-demo-design

    工程描述
    itstack-demo-design-1-00场景模拟工程,用于提供三组不同奖品的发放接口
    itstack-demo-design-1-01使用一坨代码实现业务需求,也是对ifelse的使用
    itstack-demo-design-1-02通过设计模式优化改造代码,产生对比性从而学习
    • 1-00,1 代表着第一个设计模式,工厂方法模式

    • 1-00,00 代表模拟的场景

    • 1-01,01 代表第一种实现方案,后续 02 03 以此类推

二、工厂方法模式介绍

工厂方法模式,图片来自 refactoringguru.cn

  • 工厂方法模式,图片来自 refactoringguru.cn

工厂模式又称工厂方法模式,是一种创建型设计模式,其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。

这种设计模式也是 Java 开发中最常见的一种模式,它的主要意图是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

简单说就是为了提供代码结构的扩展性,屏蔽每一个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调用即可,同时,这也是去掉众多ifeslse的方式。当然这可能也有一些缺点,比如需要实现的类非常多,如何去维护,怎样减低开发成本。但这些问题都可以在后续的设计模式结合使用中,逐步降低。

三、模拟发奖多种商品

模拟发奖多种商品,bugstack虫洞栈

为了可以让整个学习的案例更加贴近实际开发,这里模拟互联网中在营销场景下的业务。由于营销场景的复杂、多变、临时的特性,它所需要的设计需要更加深入,否则会经常面临各种紧急CRUD操作,从而让代码结构混乱不堪,难以维护。

在营销场景中经常会有某个用户做了一些操作;打卡、分享、留言、邀请注册等等,进行返利积分,最后通过积分在兑换商品,从而促活和拉新。

那么在这里我们模拟积分兑换中的发放多种类型商品,假如现在我们有如下三种类型的商品接口;

序号类型接口
1优惠券CouponResult sendCoupon(String uId, String couponNumber, String uuid)
2实物商品Boolean deliverGoods(DeliverReq req)
3第三方爱奇艺兑换卡void grantToken(String bindMobileNumber, String cardId)

从以上接口来看有如下信息:

  • 三个接口返回类型不同,有对象类型、布尔类型、还有一个空类型。

  • 入参不同,发放优惠券需要仿重、兑换卡需要卡ID、实物商品需要发货位置(对象中含有)。

  • 另外可能会随着后续的业务的发展,会新增其他种商品类型。因为你所有的开发需求都是随着业务对市场的拓展而带来的。

四、用一坨坨代码实现

如果不考虑任何扩展性,只为了尽快满足需求,那么对这么几种奖励发放只需使用ifelse语句判断,调用不同的接口即可满足需求。可能这也是一些刚入门编程的小伙伴,常用的方式。接下来我们就先按照这样的方式来实现业务的需求。

1. 工程结构

itstack-demo-design-1-01└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── AwardReq.java
    │           ├── AwardRes.java
    │           └── PrizeController.java 
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java
  • 工程结构上非常简单,一个入参对象 AwardReq 、一个出参对象 AwardRes,以及一个接口类 PrizeController

2. ifelse实现需求

public class PrizeController {    private Logger logger = LoggerFactory.getLogger(PrizeController.class);    public AwardRes awardToUser(AwardReq req) {
        String reqJson = JSON.toJSONString(req);
        AwardRes awardRes = null;        try {
            logger.info("奖品发放开始{}。req:{}", req.getuId(), reqJson);            // 按照不同类型方法商品[1优惠券、2实物商品、3第三方兑换卡(爱奇艺)]
            if (req.getAwardType() == 1) {
                CouponService couponService = new CouponService();
                CouponResult couponResult = couponService.sendCoupon(req.getuId(), req.getAwardNumber(), req.getBizId());                if ("0000".equals(couponResult.getCode())) {
                    awardRes = new AwardRes("0000", "发放成功");
                } else {
                    awardRes = new AwardRes("0001", couponResult.getInfo());
                }
            } else if (req.getAwardType() == 2) {
                GoodsService goodsService = new GoodsService();
                DeliverReq deliverReq = new DeliverReq();
                deliverReq.setUserName(queryUserName(req.getuId()));
                deliverReq.setUserPhone(queryUserPhoneNumber(req.getuId()));
                deliverReq.setSku(req.getAwardNumber());
                deliverReq.setOrderId(req.getBizId());
                deliverReq.setConsigneeUserName(req.getExtMap().get("consigneeUserName"));
                deliverReq.setConsigneeUserPhone(req.getExtMap().get("consigneeUserPhone"));
                deliverReq.setConsigneeUserAddress(req.getExtMap().get("consigneeUserAddress"));
                Boolean isSuccess = goodsService.deliverGoods(deliverReq);                if (isSuccess) {
                    awardRes = new AwardRes("0000", "发放成功");
                } else {
                    awardRes = new AwardRes("0001", "发放失败");
                }
            } else if (req.getAwardType() == 3) {
                String bindMobileNumber = queryUserPhoneNumber(req.getuId());
                IQiYiCardService iQiYiCardService = new IQiYiCardService();
                iQiYiCardService.grantToken(bindMobileNumber, req.getAwardNumber());
                awardRes = new AwardRes("0000", "发放成功");
            }
            logger.info("奖品发放完成{}。", req.getuId());
        } catch (Exception e) {
            logger.error("奖品发放失败{}。req:{}", req.getuId(), reqJson, e);
            awardRes = new AwardRes("0001", e.getMessage());
        }        return awardRes;
    }    private String queryUserName(String uId) {        return "花花";
    }    private String queryUserPhoneNumber(String uId) {        return "15200101232";
    }

}
  • 如上就是使用 ifelse 非常直接的实现出来业务需求的一坨代码,如果仅从业务角度看,研发如期甚至提前实现了功能。

  • 那这样的代码目前来看并不会有什么问题,但如果在经过几次的迭代和拓展,接手这段代码的研发将十分痛苦。重构成本高需要理清之前每一个接口的使用,测试回归验证时间长,需要全部验证一次。这也就是很多人并不愿意接手别人的代码,如果接手了又被压榨开发时间。那么可想而知这样的 ifelse 还会继续增加。

3. 测试验证

写一个单元测试来验证上面编写的接口方式,养成单元测试的好习惯会为你增强代码质量。

编写测试类:

@Testpublic void test_awardToUser() {
    PrizeController prizeController = new PrizeController();
    System.out.println("\r\n模拟发放优惠券测试\r\n");    // 模拟发放优惠券测试
    AwardReq req01 = new AwardReq();
    req01.setuId("10001");
    req01.setAwardType(1);
    req01.setAwardNumber("EGM1023938910232121323432");
    req01.setBizId("791098764902132");
    AwardRes awardRes01 = prizeController.awardToUser(req01);
    logger.info("请求参数:{}", JSON.toJSON(req01));
    logger.info("测试结果:{}", JSON.toJSON(awardRes01));
    System.out.println("\r\n模拟方法实物商品\r\n");    // 模拟方法实物商品
    AwardReq req02 = new AwardReq();
    req02.setuId("10001");
    req02.setAwardType(2);
    req02.setAwardNumber("9820198721311");
    req02.setBizId("1023000020112221113");
    Map extMap = new HashMap();
    extMap.put("consigneeUserName", "谢飞机");
    extMap.put("consigneeUserPhone", "15200292123");
    extMap.put("consigneeUserAddress", "吉林省.长春市.双阳区.XX街道.檀溪苑小区.#18-2109");
    req02.setExtMap(extMap);

    commodityService_2.sendCommodity("10001","9820198721311","1023000020112221113", extMap);

    AwardRes awardRes02 = prizeController.awardToUser(req02);
    logger.info("请求参数:{}", JSON.toJSON(req02));
    logger.info("测试结果:{}", JSON.toJSON(awardRes02));
    System.out.println("\r\n第三方兑换卡(爱奇艺)\r\n");
    AwardReq req03 = new AwardReq();
    req03.setuId("10001");
    req03.setAwardType(3);
    req03.setAwardNumber("AQY1xjkUodl8LO975GdfrYUio");
    AwardRes awardRes03 = prizeController.awardToUser(req03);
    logger.info("请求参数:{}", JSON.toJSON(req03));
    logger.info("测试结果:{}", JSON.toJSON(awardRes03));
}

结果:

<pre style="margin: 10px 0px; padding: 0px; text-align: left; color: rgb(0, 0, 0); text-transform: none; text-indent: 0px; letter-spacing: normal;

相关推荐

PHP实现部分字符隐藏

沙雕mars · 1325浏览 · 2019-04-28 09:47:56
Java中ArrayList和LinkedList区别

kenrry1992 · 908浏览 · 2019-05-08 21:14:54
Tomcat 下载及安装配置

manongba · 970浏览 · 2019-05-13 21:03:56
JAVA变量介绍

manongba · 962浏览 · 2019-05-13 21:05:52
什么是SpringBoot

iamitnan · 1086浏览 · 2019-05-14 22:20:36
加载中

0评论

评论
分类专栏
小鸟云服务器
扫码进入手机网页