本文来自网易云社区设计模式
做者:陆秋炜
架构
引言 :好久以前,在作中间件测试的时候,看到开发人员写的代码,有人的代码,看起来老是特别舒服,但有的开发代码,虽然逻辑上没有什么问题,但总给人感受特别难受。后来成为了一位专职开发人员,渐渐发现,本身的代码也是属于“比较难受”的那种。后来随着代码的增长,编写代码时,总有一些比较乖巧的方式,这就是以前不懂的“设计模式”。以前代码架构比较少(只是写一些测试工具),用不到这些,只有本身慢慢作了一些架构工做后,才用获得,并去主动了解。ide
但今天想说的,并非具体的哪种设计模式的优劣,而是想记录一下,设计模式中存在的一些设计思想。有了这些设计思想,某些设计模式就天然而然的出现了。因此说,所谓的“设计模式”并非被发明出来的,而是被咱们本身“发现”的。工具
以前不管是做为开发仍是测试,习惯性的以为,别人提供了什么功能,就用什么样的功能,这样作天经地义。然而,在本身的架构设计过程当中,若是有了这样额思惟,很容易让本身的程序设计陷入困境。学习
打个装修的比喻,咱们必定是有设计师设计相关方案(具体的风格),而后分解成对应的家具,而后再购买材料,打造对应的家具。若是咱们将这一过程倒过来,先有什么材料,而后看这些材料能打造出什么家具,再把家具组合起来,那么最后的装修效果必定会很是差。测试
图1 正确的设计方式编码
图2 自底向上的设计结果,必定是最后的整合有问题spa
因此优秀的设计必定是从总体到局部设计出来的。从局部构造总体,不可能获得优秀的设计。架构设计
了解清楚某个功能模块(或者整个功能)具体要干什么事情,咱们才可以知道具体要如何作设计。而不是找一个设计方案,可以实现主要功能就好了,其余功能再次基础上修修补补。设计
再举一个简单的例子:好比说咱们要喝水(表面功能/基础目标),那么咱们就须要找相关盛水的容器(设计实现)。咱们找到了如下容器(可能的实现方案):
图三 各类盛水容器的实现
三种容器都能喝水,但具体要使用哪一个呢?若是随便选一个酒杯,但具体实现(或者将来可能的功能)要求可以带到户外去,总不能给酒杯再加个盖子吧;同理,若是咱们要品酒,却选了个保温杯的实现,到时候直接设计推倒重来了。因此,要有合适的设计,必定要对产品自己的需求(以及将来可能的需求)作详细的分析和了解,而后肯定设计方案。
在学习“面向对象”的语言时,咱们首先被教会“封装、继承、多态”。今后,感受有点关系的都要进行继承,以为这样能节省好多代码。而后咱们的代码中便出现了继承的乱用
正常状况下,这样作没有问题,但问题的起源在于,咱们的需求是不断的修改和添加的,若是使用了继承,在超类中的方法改动,会影响到子类,并可能引发引发子类之间出现冗余代码。
举个汽车的例子吧,一辆汽车开行(drive)是同样的,但车标(logo)是不同的,因此用继承
public abstract class Car { /** * 驾驶汽车 */ public void drive(){ System.out.print("drive"); } /** * 每辆车的车标是不同的,因此抽象 */ public abstract void logo() ; }class BMW extends Car{ @Override public void logo() { System.out.print("宝马"); } }class Benz extends Car{ @Override public void logo() { System.out.print("奔驰"); } }class Tesla extends Car{ @Override public void logo() { System.out.print("特斯拉"); } }
一切看起来解决的很完美。忽然加了一个需求,要求有个充电(change)需求,这时候,只有特斯拉(tesla)才有充电方法。但若是使用继承,在父类添加change方法的同时,就须要在BMW和Benz实现无用的change方法,对于子类的影响很是大。但若是使用组合,使用ChangeBehavior,问题就获得了有效解决,
public interface ChargeBehavior { void charge() ; }public abstract class Car { protected ChargeBehavior chargeBehavior ; /** * 驾驶汽车 */ public void drive(){ System.out.print("drive"); } /** * 每辆车的车标是不同的,因此抽象 */ public abstract void logo() ; /** * 充电 */ public void change(){ /** * 不用关心具体充电方式,委托ChargeBehavior子类实现 */ if (chargeBehavior!=null) { chargeBehavior.charge(); } } }class Benz extends Car{ @Override public void logo() { System.out.print("奔驰"); } }class BMW extends Car{ @Override public void logo() { System.out.print("宝马"); } }class Tesla extends Car{ @Override public void logo() { System.out.print("特斯拉"); } public Tesla() { super(); chargeBehavior = new TeslaChargeBehavior() ; } }class TeslaChargeBehavior implements ChargeBehavior{ @Override public void charge() { System.out.print("charge"); } }
经过将充电的行为委托给changeBehavior接口,子类若是不须要的话,就能够作到无感知接入。
这样的代码有三个优点
1,代码不须要子类中重复实现
2,子类不想要的东西,能够无感知实现
3,子类运行的行为,能够委托给behavior实现,子类本省自己无需任何改动
在刚刚接触面向对象的时候,封装,对咱们来讲就是类,实例化后就是对象。最基本功能是对于数据进行隐藏,对于行为进行开放(如JavaBean)。慢慢用多了之后渐渐发现,其实咱们能够封装跟多东西,好比某些实现的细节(私有方法方法),实例化规则(构造器)等。
因为咱们的代码是分层和分模块的,但咱们的需求又是常常要变化的,咱们但愿修改新功能,对于除了模块自己外,调用方是无感知的。因此,咱们的类(或者说是模块吧)变封装了变化自己。对于调用方来讲,只须要知道不会变的功能名(方法名)就够了,而不须要了解可能变化的内容。
图四 变化自己进行封装
在一类实现中,咱们其实能够分析发现,代码的实现上是有一些共性的,好比说处理的流程(如何调用一些方法的顺序),也有一些彻底一致的操做(好比上文提到的car均可以drive,实现一致的方法)。但也有一些可变性:如必须存在(共性),但实现不一致的操做(如上文car里面的logo方法,必须有,但不一致)。这时候,咱们就能够对这些实现进行一些简单的抽象,成为抽象类。抽象类就是将共性变为以实现的方法,而将可变性变为抽象方法,让子类予以实现。
图五,共性和抽象类
代码看多了,写多了,便会发现,看起来舒服的代码,在可维护性,可读性,可扩展性上相对来讲都比较高。代码界也有“颜值即战斗力”这一说法,很有一番玄学的味道。但分析具体的缘由,其实能够发现,优秀的编码设计,在其抽象,封装,都有其合理之处,其总体的架构设计上,亦有其独到之处。
网易云大礼包:https://www.163yun.com/gift
本文来自网易云社区,经做者陆秋炜受权发布
相关文章:
【推荐】 流式断言器AssertJ介绍