如何在代码中应用设计模式入门基础_模式小白常识

为什么要使用设计模式因为我们的项目的需求是永远在变的,为了应对这种变化,使得我们的代码能够轻易的实现解耦和拓展。如果能够保证代码一次写好以后都不会再改变了,那可以想怎么写怎么写了。如何判断那里需要使用设计模式在我们实现中,有一些代码是一次写好后续基本不会改变的,或者不太需要扩展的,比如一些工具类等。有一部分是会经常变得,设计模式大多都应用在需求会变化的这一部

如何在代码中应用设计模式入门基础

为什么要使用设计模式

因为我们的项目的需求是永远在变的,为了应对这种变化,使得我们的代码能够轻易的实现解耦和拓展。如果能够保证代码一次写好以后都不会再改变了,那可以想怎么写怎么写了。

如何在代码中应用设计模式入门基础_模式小白常识

如何判断那里需要使用设计模式

在我们实现中,有一些代码是一次写好后续基本不会改变的,或者不太需要扩展的,比如一些工具类等。有一部分是会经常变得,设计模式大多都应用在需求会变化的这一部分。分析这些代码会如何变,选择合适的设计模式来优化这部分代码。

以促销活动需求为例

需求

为了促进商品的销售,各大电商品台会在平时或者一些节日的时候退出一些促销活动刺激用户消费,活动的类型可能会各不相同,如下:

  • 满减,满400减20
  • 代金卷,玛莎拉蒂5元代金卷
  • 折扣,9折,8折
  • 每满减,每满200减10
  • 等等

其中有些可以叠加,有些只能单独使用。

简单实现

上面的需求看起来还是比较简单的,但是如果考虑到我们是不可能一次定义好所有的促销活动类型,后续我们可能会随时都添加新的类型,要保证能够简单的实现功能扩展,那就比较麻烦了。

先拿到需求的时候,也不用去想那么多,挽起袖子就是一通操作:

public class OrderPromotion {
    public BigDecimal promotion(Order order, int[] promotions){
        for (int promotion:promotions){
            switch (promotion){
                case 1:
                                    //计算该类型折扣后的价格
                break;
                case 2:
                                    //计算该类型折扣后的价格
                break;
                case 3:
                                    //计算该类型折扣后的价格
                break;
                //....
            }
        }
        return order.getResultPrice();
    }
}

单从功能实现上来说,上面的代码已经完成了基本功能了。但是上面的代码也是致命的,虽然看起来很简单,但是那只不过是因为大多数功能都用注释代替了,换成实际代码的话一个方法可能就得上千行。

尤其是当我们需要添加新的促销活动的话就需要在switch中添加新的类型,这对于开发来说简直是灾难,并且维护这些代码也是一个麻烦。

优化一:单一职责原则

上面的代码中,promotion(…)方法直接完成了所有的工作,但是咋我们实际实现中最好让一个方法的职责单一,只完成某一个功能,所以这里我们将对折扣类型的判断和计算价格分开:

public class OrderPromotion {
    public BigDecimal promotion(Order order, int[] promotions){
        for (int promotion:promotions){
            switch (promotion){
                case 1:
                                    calculate1(order);
                break;
                case 2:
                                    calculate2(order);
                break;
                case 3:
                                    calculate3(order);
                break;
                //more promotion
            }
        }
        return order.getResultPrice();
    }
    public void calculate1(Order order){
        //计算使用折扣一后的价格
    }
    public void calculate2(Order order){
        //计算使用折扣二后的价格
    }
    public void calculate3(Order order){
        //计算使用折扣三后的价格
    }
    //more calculate
}

这里我们将折扣类型的判断和计算价格分开,使得promotion(…)方法的代码量大大降低,提升了代码的可读性。

优化二:策略模式

上面优化后的代码提升了原有代码的可读性,但是原来OrderPromotion类代码大爆炸的问题还是没有解决。针对这个问题,我们希望能够将计算的代码和当前代码分离开,首先我们能想到的就是定义一个类,然后将计算的代码复制到这个类中,需要的时候就调用。这样到的确是分离开了,但是完全是治标不治本。在添加新的促销活动是两个类都要改。

所以我们希望能够将不同的促销活动的实现分离开,这样对每一种活动的实现都是分开的,修改也不会影响其他的,基于此我们完全可以选择策略模式来实现。

策略模式

策略模式的思想是针对一组算法,将每一种算法都封装到具有共同接口的独立的类中,从而是它们可以相互替换。策略模式的最大特点是使得算法可以在不影响客户端的情况下发生变化,从而改变不同的功能。

public class OrderPromotion {
    public BigDecimal promotion(Order order, int[] promotions){
        for (int promotion:promotions){
            switch (promotion){
                case 1:
                                    new PromotionType1Calculate(order);
                break;
                case 2:
                                    new PromotionType1Calculate(order);
                break;
                case 3:
                                    new PromotionType1Calculate(order);
                break;
                //more promotion
            }
        }
        return order.getResultPrice();
    }
}

上面的代码很明显已经精简很多了,到了现在如果需要添加一个促销活动的话只需定义一个促销类,实现PromotionCalculation接口然后在switch中添加即可。

优化三:工厂模式

上面的代码虽然已经将促销活动的实现分离开了,但是OrderPromotion还是一直在变得,每一次添加或者下线活动都需要修改该类。现在我们希望OrderPromotion是不变的,将PromotionCalculation的实例化剥离开来。创建类很明显是使用工厂设计模式了。

OrderPromotion

public class OrderPromotion {
    public BigDecimal promotion(Order order, int[] promotions){
        for (int promotion:promotions){
            PromotionFactory.getPromotionCalculate(promotion).calculate(order);
        }
        return order.getResultPrice();
    }
}

类的创建工作交给工厂来实现。

PromotionFactory

public class PromotionFactory {
    public static PromotionCalculate getPromotionCalculate(int promotion){
        switch (promotion){
            case 1:
                            return new PromotionType1Calculate(order);
            break;
            case 2:
                            return new PromotionType1Calculate(order);
            break;
            case 3:
                            return new PromotionType1Calculate(order);
            break;
            //more promotion
        }
        return null;
    }
}

使用工厂模式后OrderPromotion类就不需要改了,每一次添加新的促销活动后只需要在工厂类中添加即可。

优化四:配置+反射

上面的代码还存在的问题在于每一次需要添加新的促销活动的时候还是需要修改工厂类中的代码,这里我们通过配置文件加反射的方式来解决。

定义映射配置文件

mapping.properties

1=design.order.PromotionType1Calculate
2=design.order.PromotionType2Calculate
3=design.order.PromotionType3Calculate

PromotionFactory

public class PromotionFactory {
    private static Map<Integer, String> mapping = new HashMap<Integer, String>();
    static {
        try {
            Properties pps = new Properties();
            pps.load(new FileInputStream("Test.properties"));
            Iterator<String> iterator = pps.stringPropertyNames().iterator();
            while(iterator.hasNext()){
                String key=iterator.next();
                mapping.put(Integer.valueOf(key), pps.getProperty(key));
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static PromotionCalculate getPromotionCalculate(int promotion) throws Exception {
        if(mapping.containsKey(promotion)){
            String beanName = mapping.get(promotion);
            return Class.forName(beanName).newInstance();
        }
        return null;
    }
}

通过上面的代码就可以实现不改变已有代码的前提下实现对功能的灵活扩展。当然,这里的代码只是作为演示用的,实际上可以改进的地方还有不少,像最后反射效率较低,也可以通过其他的方式来实现。

小结

设计模式是我们一定要了解的东西,熟悉设计模式能让我们设计出易于扩展和维护的代码结构。但是并不是任何地方都需要上设计模式,应该结合我们的项目实际进行分析是否需要设计模式,使用哪种设计模式。

海计划公众号
(0)
上一篇 2020/03/30 16:06
下一篇 2020/03/30 16:06

您可能感兴趣的内容

  • css3动画transition的使用和介绍新手入门_css3菜鸟知识

    CSS3中可以使用transition来做最简单动画效果,transition表示到一个元素的属性值发生变化时,我们可以看到页面元素从旧的属性慢慢变化为新的属性值的过程,这种效果不是立即变化的,而是体现出一种动画过程。在transition出现之前css是没有过渡效果的(时间轴),也就是所有的属性的变化都是即时完成。transition是由4个属性的合体简写

    2020/04/06
  • i18next使用说明_一个页面层语言国际化js框架

    i18next使用说明 官方网址:http://i18next.com/ GitHub:https://github.com/i18next/i18next 简介描述:一个页面层语…

    2020/03/06
  • css兄弟选择器(零基础入门 和 ~)的使用和区别指南攻略_选择器

    这篇文章主要讲解css中兄弟选择器的使用,以及+和~的区别有哪些?+ 选择器如果需要选择紧接在另一个元素后的元素,而且二者有相同的父元素,可以使用相邻兄弟选择器。 比如:h1 + p {margin-top:50px;color:red;}

    This is paragraph.

    <

    2020/04/05
  • PHP如何解决表单重复提交基础入门_表单使用教程

    利用session表单隐藏域中存放session(表单被请求时生成的标记)。采用此方法在接收表单数据后,检查此标志值是否存在,先进行删除,然后处理数据; 若不存在,说明已提交过,忽略本次提交。加载提交的页面时候,生成一个随机数$code = mt_rand(0,1000000);存储在表单的隐藏输入框中:< input type="hidden" name=

    Web前端 2020/03/29
  • 程序员表白被拒后,听过最狠的话小白攻略_程序员使用攻略

    喜欢一个人,到了极限。就想跟她表白,但表白成功率并不是百分之百。所以被拒绝也事常有的事,下面看看程序员表白被拒后,听过最狠的话…. 1、不行诶……我电脑坏了还能找你吧?2 、我怕失去一个好朋友。3、你长得丑,配不上我!4、你想和一个不喜欢你的人在一起吗5、你太矮了6、啊?我一直把你当好哥们啊7、我对你的喜欢只是朋友的喜欢8、你性格挺好的,但不是我喜欢的那

    2020/04/03
  • 分布式系统的负载均衡小白攻略_分布式菜鸟教程网

    一、 什么是负载均衡?记得第一次接触 Nginx 是在实验室,那时候在服务器部署网站需要用 Nginx 。Nginx 是一个服务组件,用来反向代理、负载平衡和 HTTP 缓存等。那么这里的 负载均衡 是什么?负载均衡(LB,Load Balance),是一种技术解决方案。用来在多个资源(一般是服务器)中分配负载,达到最优化资源使用,避免过载。资源,相当于每个

    2020/03/26
  • JS中一定要了解的数据类型和数据转换 使用指南_类型使用帮助

    一、数据类型前言Js中的类型只有6种,其中基本数据类型有5种分别为string,number,boolen,null,undefined,引用类型有一种,就是object,object是一个大的综合体,在JS中除了那5个基本数据类型以外,其他的一切皆对象。以下是最近自学js整理的一些笔记,希望能让更多初学者了解到数据类型的基本概念。 正文1.获取变量类型t

    2020/03/23
  • OSCHINA.NET在线工具使用帮助_中国开源社区,ostools为开发设计人员提供在线工具

    OSCHINA.NET在线工具使用帮助 官方网址:http://tool.oschina.net/ 简介描述:中国开源社区,ostools为开发设计人员提供在线工具 中国开源社区,…

    2020/03/07
  • css实现input file上传按钮自定义样式小白入门_按钮小白指南

    input file的样式不能直接用css来美化,我们可以曲线救国,把input file的透明度降低为0,相当于把这个控件隐藏了,实际上只是透明度为0,还是存在的,然后把div套上去,让div充当file的按钮。所以这个办法很简单,但是,把input file的透明度降低,连选择文件后的文件名也被隐藏了,这下可怎么办?那就只能用jquery获取file的文

    2020/03/29
  • 软件开发教给我们的7个生活指南菜鸟教程网_开发小白知识

    我们在做软件开发时学到的很多思维、方法、工具、模型、算法……其实可以迁移到生活中使用,让我们生活得更美好哦。我这里暂举 7 个,以后有时间,慢慢补坑,争取补到 60 个。大家有兴趣的,可以留言补充你最有感觉的。1. 网络协议应用于人际沟通在网络编程中,客户端和服务器要通信,必须寻找特定端口,建立链接,遵守一定的协议,才能传输数据。比如 http、ftp、te

    2020/04/03
  • 如何渲染几万条数据并不卡住界面?入门基础教程_渲染入门攻略

    如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条 都渲染出来,而应该一次渲染部分 DOM,那么就可以通过 requestAnimationFrame 来 每 16 ms 刷新一次。代码

    <meta name="viewport"

    2020/03/23
  • CSS之calc()菜鸟指南_函数新手入门

    calc() 函数支持任意CSS长度单位的混合计算,遵循标准数学运算优先级规则,可以动态计算长度值。注意,calc()函数内部的运算符两侧各加一个空白符,否则会产生解析错误。calc()使用的难点在于百分比,理解了百分比定位,使用calc()就非常便捷。譬如以background-position中使用为例。将背景图片偏移距离底部10px<div class

    2020/03/26
  • js判断变量是否为整数基础入门_变量基础教程

    代码如下://返回false则不为整数数字,返回ture则反之
    var isIntNumber=function(val){if(isNaN(val) || Math.floor(val) != val){return false;}return true;
    }
    //例:
    alert(isIntNumber(“1.1”));说明:1.NaN 属性是代表非数字

    2020/03/26
  • 网站主题和内容的三个类型菜鸟知识_网站入门知识

    网站主题和内容应该是最先要考虑的。这决定了你后面很多的事情和选择及操作。我大致将其分为三个类型:1、 纯原创,比如个人博客。那么要注意的,就是不要写成生活流水记账,而是要有核心主题,所有文章,创业,内容都是为这个主题服务。然后再想办法去引流,宣传,推广。好处是,既然是原创,如果你的内容不是太差,那么只要努力创作,努力推广,慢慢就会有一定流量。但是速度嘛,一

    2020/03/22
  • react-native时间轴组件的使用入门基础_组件使用攻略

    最近在写公司的项目,因产品设计需求,需要类似如下的效果,像是一个时间轴。本着不重复造轮子的目标,在最喜欢的github上找寻合适的组件,终于发现了一个非常棒的组件:react-native-step-indicator 使用就非常简单了 第一步,添加组件依赖,这里墙裂建议大家使用yarn管理项目依赖yarn add react-native-step-ind

    2020/03/30
  • 浅析前端页面渲染机制使用攻略_机制零基础入门

    作为一个前端开发,最常见的运行环境应该是浏览器吧,为了更好的通过浏览器把优秀的产品带给用户,也为了更好的发展自己的前端职业之路,有必要了解从我们在浏览器地址栏输入网址到看到页面这期间浏览器是如何进行工作的,进而了解如何更好的优化实践,本篇主要围绕这两点展开阐述。前端页面渲染机制可谓是老生常谈,但又很有必要再谈的话题,于是还是决定写一篇,即是对知识的回顾总结,

    2020/04/06