Java·代码的复用之美

Java的三大特性:封装,继承和多态,相信学习过的Java的都能够说出来但是,这三个特性是为了什么而存在呢,我认为,归根到底就是为了使代码能够复用,即我们应该把更多的心思放在新的功能上,而不是反复的写一些陈旧的代码。
首先,封装使得每个独立的功能模块化了。我们需要按照一个系统中各个业务的“边界”划分功能模块,而功能模块的直接载体就是类了。当然,并不是指一个功能就只能编写在一个类中。实际情况是,我们往往需要一组类的配合使用才能完成业务场景的需求,如下左图。但是我们要学会养成“分类”的习惯。这样的代码不仅结构清晰而且容易被复用。 另外,就算是熟练的程序员,也不能保证自己写过的代码没有错误,代码已经封装就可以保证它在功能范围内的正确性。这样,我们就不用浪费时间去检查和编写那些已有的功能了。良好的封装能够大大的降低模块间的耦合度,这就意味着我们不仅可以组装一个系统,以后还能拆解这个系统中的组件来使用

那么我们应当如何去封装?
一个优秀的API应该只对外暴露业务相关的方法和竟可能精简的参数列表。我们要善于使用权限控制来达到这一点,那些只是我们内部使用的方法不要以公开来呈现。我们的成员变量也不能直接暴露,这样不利于数据的安全,而应当用的getter / setter来访问一个比较好的例子如下:
公共班级学生{
    私人字符串名称;
    私人字符串年龄;

    私人字符串handleName(字符串名称){
       返回“我是”+名字;
    }

    私人字符串handleAge(字符串年龄){
        回归年龄+“岁”;
    }

    public String getName(){
        返回名称;
    }

    public void setName(String name){
        this.name = handleName(name);
    }

    public String getAge(){
        回归年龄;
    }

    public void setAge(String age){
        this.age = handleAge(age);
    }
}
封装的原则又是什么呢?这里我引出了一个叫做“合适粒度”的标准。


通俗来说,就是我们在分解一个系统的时候,应该保证各个功能点不会因为功能太过羸弱而失去复用的价值 高内聚 ,我们应当尽可能的把相关的功能聚合在一个类中编写从而方便我们去复用这个功能:一个很典型的例子就是,我们开发一个商城在线支付系统,可能涉及到拉取支付订单,确认消费用户,计算金额,选择支付方式,生成订单,校验付款密码和扣款信息回调等诸多步骤。这其实是一套完整的支付流程,我们如果把这一个个的功能点分散在不同的类中。可能我当前程序使用起来毫无影响,但如果我下次去开发一款网上书店的系统,里面也涉及到在线支付。如果我想要复用之前的代码,难道还要到不同的类中去摘选出这些代码么,对于小型的项目这可能也不足为虑,但是对于一个巨型的项目,这就无疑是一个噩梦了。当然,如果在这个支付 功能中加入了一些其他的不相关业务流程,那么复用代码也会非常的棘手。这就是我要说的另外一点,不要使得一个功能太过庞大而使得它无法复用。还是拿商城支付系统举例子,如果我们把支付业务整合到了一个完整的购物流程之中。这将使得我们要复用支付系统这部分代码变得很困难,因为这个庞大的类中还有其他的不相干的(起码和支付没有直接关联的)代码,我们又得去筛选一遍哪些方法能用上,哪些方法对我来说是无用的。

对于封装,我还有一些补充,你不需要把你的接口细节暴露给外部,你要保证客户端程序员只能向你的类发送消息而不能获取其中的细节(曾有人尝试过,由于代码没有做好权限访问,只需要使用一个类去继承你的代码,我就能得到你的代码中的细节,然后注入一些非法的数据去破坏系统的平衡,代刷游戏币等就可以通过这种途径来实现),另外,对于一个尚未完全确定的接口,可能你的部分代码还不能完全的公开,你后续可能还会对它进行修改。你不希望你的变动会影响到客户端程序员的代码的编写。这时候就更有必要做好权限控制,当你的代码测试无误之后才以公开的形式进行公布。



第二个话题,就是继承了。其实我本人并不建议初学者去接触继承这个东西。虽然,在Java的API的中有很多经典的通过继承来复用代码的案例(I / O)如下左图,可以优雅的去复用代码。但是这一切的实现都是基于JDK开发者对于java的特性深层次的了解才能得到这种恰如其分的效果,对于初学者来说,这种能力并不一定具备。一种比较理想的设计方法如下右图,在接口中抽象出某个功能所要用到的所有方法,然后使用一个抽象类去实现这个接口。你可能会质疑为什么要使用抽象类,因为抽象类中既可以存在抽象方法又可以存在非抽象方法,非抽象方法就是我们用来实现一些公用的基础类(类似于对象类中的的toString,散列等方法),然后那些抽象方法就交由下面各个不同的实现类去实现了。
其实在公司里面,我们往往没有抽象类这一层,我们的代码的消费者往往就是我们自己最多也就是公司内部一些很小的人群(嘿嘿,公司的代码都是机密啊!)。我们不需要去考虑那么多公用的普及大众的东西,一般都是定义“接口1:1实现类”。这种形式,因为这里采用的是另外一种复用代码的策略 - 组合也就是说,我们的业务就是以一个个合适粒度下的功能模块进行有机的组合。如果说继承是一种纵向的复用代码复用(升级强化某个类的功能,从而使得它的功能更加强大,这一般适合于服务端程序员开发),那么组合就是一种横向的代码复用(把已有的功能按照业务以不同顺序进行调用,这一般适用于客户端程序员开发)。
对于继承要补充的两点:。最终的合理使用和继承下的初始化前面提到了权限控制,这样我们的确做到了细节的不暴露,可是一旦使用私人去修饰它将变得不仅不能被继承,而且都不能够被访问了。这时候最后就派上了用场,我们仍然可以将一个方法或者一个变量修饰为公,可是我们可以再加上一个最终这样就完美的解决这个问题(最终修饰下的变量引用不可改变,方法不能被覆盖,类不能被继承);单个类的初始化相信大家一定不陌生了,这里我来介绍下继承下的多个类的初始化首先,对于代码块中用到的所有类,我们会先按从父类到子类的顺序加载所有类的基本信息(包括类中的静态修饰的部分,静态修饰的成员在类中有一块属于自己的独立的存储区间),然后将所有的成员变量置为零值。涉及到实例化对象从而调用其中的方法,我们会调用其 构造器。由于存在继承关系,需要逐级往上调用最顶级父类的构造器。如下下图所示,我们三个类已经加载完毕了,现在实例化一个导出类的对象,那么我们首先需要按照顶级父类成员变量定义的初始化方法进行初始化,然后执行顶级父类的构造器。按照接着父类的 成员变量定义的初始化方法进行初始化......以此类推最后调用导出类的构造器。如果感觉看起来有点绕,不如看看如下下图的代码实例吧

        

 最后要说的就是java的的第三个特性。 - 多态,多态是基于接口还有向上转型来实现的很多人问我为什么不使用延伸而是使用实现来诠释多态这源于的java中的一个特性:。允许多重实现而不允许多重继承如何一个方法只能够接受一个类而不是一个接口,那么我们的类就必须去继承这个类如果我们在其他地方还需要复用这个方法恐怕就不可以了。但是如果我们在这里接受的是一个接口,我们只要实现这个接口所需的方法。那么我们这个类在其他地方依然能够得到很好的复用。另外,使用多态使我们不需要在代码编译期就确定我该调用的具体方法,而是推迟到代码的运行时间再去确立这样的好处顾名思义:我们的代码变得可以随机应变,而不是死板的去做固定的工作比起。那些粗糙的的if-else代码块,这种设计的好处显而易见。这一切都是得益于Java的接口 后期绑定机制,而不像诸如Ç语言的前期绑定。当然,对于代码复用我还有很多东西要和大家进行分享,这一节我主要分享的一些基础的理论和思想同时回顾了一下经典的java的三大特性。下一节我将和大家谈一谈比较常用而又经典的设计模式,其中的几种设计模式(适配器模式,工厂模式)也能很好的复用代码。感谢大家的耐心阅读,希望这篇文章或多或少对你有点用途!

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值