崩溃!如何面对令人脱发的老代码?

本文通过生动的比喻和代码示例,深入浅出地介绍了软件设计中的策略模式和适配器模式。老T以电脑硬件的发展为例,解释了如何通过接口和抽象来降低代码耦合,提高系统的可扩展性和可维护性。通过键盘、鼠标与USB接口的关系,展示了如何使用策略模式实现模块化,以及如何利用适配器模式解决接口不兼容问题。强调了设计模式在软件开发中的重要性,提倡在项目初期就考虑架构设计,避免后期的混乱与重构难题。
摘要由CSDN通过智能技术生成

点击关注公众号,实用技术文章及时了解2899b533d56229c3561a3a05709273d6.png

办公室里,老T戴上耳机播放一曲浪漫旋律,写出的代码散发出一股诗般的文艺气息。每一次从容不迫的敲下回车,枯燥无味的程序逻辑突然变得有血有肉,运行时的每个字节仿佛都会随着音乐的节奏而舞动。伴随着荷尔蒙的迸发,它们像是被赋予了鲜活的生命,跃然于Monokai风格黑底花字的IDE之上。

4327d9e1f2d62cd31b1f756d5c0a6c78.png

小白是公司新招的实习生,初入职场的他经验不算丰富,一直对老T的神操作充满了好奇与崇敬。这天一大早小白就跑来老T的办公桌前:

“T哥,听什么呢,能否请教您一些问题?”

老T摘掉耳机,虽然被打断但依旧面带微笑:“哦,什么问题你说。”

小白焦躁的眼神中透着几分期许:‘’我实现的一个模块已经交付了,可是需求不停地变更,太烦人了。这导致我的代码要经常改来改去,我头都大了。而且随着业务的深入,大家的代码量都越来越大,经常看不懂别人的代码,维护起来简直让人抓狂。就像下面的情况一样。所以,如何才能杜绝这些问题的发生?”

044e119e5232aab0b03bf0a76e26f836.png

老T扶了一下眼镜,胸有成竹地说:“首先你要明白,我们并不是为了写代码而写代码。一个好的软件架构,一定是将不变的部分抽象出来固化,把变化的部分隔离开来,使它们统统各司其职地模块化,这样下来你代码维护起来就变得轻而易举,就像下面这样井井有条。”

0f9419570227d4d2548ef3e93bd28cad.png

小白好像听了天书一样,一脸茫然地问:“虽然看起来赏心悦目,但这是如何实现的?能不能讲得更具体一些?”

“15分钟,我帮你彻底梳理清楚。”老T不紧不慢,徐徐道来。

“对于一个优秀设计的软件而言,你可以不用去看别人封装好的代码,更不必试图弄清楚其内部运行机制,你只需要知道它的功能是什么;输入输出是什么,怎么用它就可以了。”

小白眼前一亮:“这么神奇?但大多数情况我都得拆开别人的代码看,这是为什么?”

老T淡定地打开了PPT,边画边讲:“我们就以电脑的发展史来举例,故事从一台老电脑开始”

90b61ea047595d03354971a1bfe061c8.png

“这台早期的电脑高度集成,配件之间的耦合性极大,因为整机都一体化成了一个类,内部线路如胶似漆难以拆分,就像我们上面的类一样庞杂。如果键盘坏了可能就比较麻烦,我们得把主机拆开来更换,因为它对外根本没有接口。”

“后来有人提出了模块化的概念,设备间通过接口传递数据,于是各种外部设备如雨后春笋般涌现:鼠标,键盘,摄像头,打印机,外接硬盘……”

“但这时又出现一个问题,每种设备都有各自的接口,那么电脑主机上得有多少种接口啊?串口、并口、PS2接口……接口泛滥成灾,制定标准化的接口势在必行,于是便有了现在的USB接口。”

“USB提供了统一的接口标准,只要符合这种标准的设备则可以进行接驳,然后各种各样的设备也都陆陆续续支持了USB接口。”

096b63c1554fdf1e59c303117c035ea4.png

“光说不练不成,我们来写点代码。”老T顺手打开了IDE。“照着上面这个图,我们先写一个USB接口。”

public interface USB {


    public String readData();


}

“一目了然,上面是一个接口,名字就叫USB,它确立了一个标准的接口方法readData()。接着外设键盘(Keyboard)和鼠标(Mouse)两个类统统实现(Implements)了USB接口。”

public class Keyboard implements USB {


    @Override
    public String readData() {
        return "键盘敲击,字符舞动……";
    }


}
public class Mouse implements USB {


    @Override
    public String readData() {
        return "鼠标移动、划出一道彩虹……";
    }


}

看了代码,小白连连点头:“的确,这真的太形象了”。

“接口和它的外设都写好了,下来我们得有一台电脑主机,我们就叫它Computer类。”

public class Computer {


    private USB usb;


    public void setUSB(USB usb){
        this.usb = usb;
    }


    public void display(){
        System.out.println("屏幕上显示:" + usb.readData());
    }


}

“看好了,这电脑里面包含了一个USB的接口(第3行),并且对外暴露了一个setUSB(USB usb)的方法(第5行),这就意味着它允许我们把任何USB设备插上去,但是一定得是USB接口的实现,否则就会报错。”

小白逐渐进入了状态:“对,这就好像以前的PS2圆口鼠标,没办法插入USB接口”。

老T趁热打铁:“此外,电脑主机还有一个display()方法(第9行)用以将usb里读取的内容展示于屏幕上。最后,见证奇迹的时刻到了,我们让这台电脑跑起来,再接入设备测试一下。”

public class Client {


    public static void main(String[] args){


        Computer computer = new Computer();
        System.out.println("电脑启动成功。");


        System.out.println("1.接入USB设备……");
        computer.setUSB(new Keyboard());
        computer.display();


        System.out.println("2.接入USB设备……");
        computer.setUSB(new Mouse());
        computer.display();


    }


}

键盘劈里啪啦的作响,看着老T在键盘上飞舞的双手,小白不禁解读到:“实例化电脑后,usb接口上插入了键盘(第9行),那么屏幕上应该蹦出来键盘入的字符;然后又换上了鼠标(第13行),这次屏幕上一定会显示出鼠标指针了。”

“说得不错,让我们拭目以待。”老果断运行了Client类的main方法。

031e63e5a8b7c27440d645da8d54f5a3.png

“果然如你所料,接上哪个设备,电脑就显示谁送来的数据了。”老T喝了一口茶接着说:“对于电脑而言,它根本不知道对接的是谁,它只管向USB接口拿数据即可。而对于实现类已经成为独立于主机(宿主)之外的外挂模块(插件)了。在设计模式里这叫作【策略模式】”。

小白似乎已经完全搞懂了,茅塞顿开:“原来如此,这种设计简直太妙了!一个插件类就是对某种行为的封装,后面如果有新的设备需求,我只需要实现标准的USB接口,然后优雅地调用setUSB(插件对象)把它接入即可,根本不需要拆开主机重新修改里面的逻辑了,这扩展性也太棒了!”

老T接着抛出了其惊世骇俗的哲学观:“变化是世界的常态,唯一不变的就是变本身,天时地利人和,拥有顺势而为、随机应变的能力才能立于不败之地。策略模式的运用能让系统的应变能力得到提升,适应永远得不到满足而随时变化的需求。接口的巧妙运用让一系列的策略都可以脱离系统之外而单独存在了,使系统拥有更灵活、更强大的【可插拔】扩展功能。”说罢顺手抛出一张类图。

cb1a3620d2a4d79d85987a7693ad0dc1.png

此刻老T发现小白的脸上又出现了一个大写的“懵”字:“好吧,类图其实就对应着下面这张图,对比着看是不是更好理解了?”

71fae910c0729bdc1a77399172159508.png

小白仔细看了两张图,又回忆了一下代码,瞬间醍醐灌顶般透彻:“秒懂了,秒懂了。”

老T接着发问:“之前你提到的PS2鼠标(类图带红叉的那个类),你看它传递的数据类型是byte[],无法完成与电脑主机的对接,你该怎么办?”

小白挠挠了头:“是不是有另一种模式可以解决?”。

“没错,我们都知道有一种设备叫转换器,它能轻松地将老旧的接口设备调制适配到新的接口达到兼容的目的,这个模式叫作【适配器】”

4c6959391cb0e155c0d1b63c5aff3efb.png

“具体的设计模式还有很多,这个要靠你自己去学习实践了。总之,对于一个庞大的软件系统,我们绝不能想到哪里就写到哪里,这样就是蛮干。如此下去,我们会造出很多只哥斯拉一样庞大的怪物类,内部堆积着大量的变量与逻辑代码,而且类与类之间的关系可能也是驴唇不对马嘴。久而久之,系统会变得错综复杂,难以维护。终于有一天,你突然发现,代码已经没法改了,千丝万缕的逻辑牵一发而动全身,稍微一个微小的改动就会造成整个系统的瘫痪。”

小白又疑惑地问:“是什么导致程序一步一步发展成这样的?

老T笑着说:“你想想,当系统中爬满了怪物类以后,你哪有精力一个个拆开去仔细研读?当类间的耦合太强,而你为了添加新功能又不得不修改其中代码的时候,很可能就更倾向于绕开老代码的方式来打补丁,修窟窿,这样造成更多的逻辑堆积与混乱。一旦陷入这种困境后,程序逻辑会变得越发古怪,越是古怪后人更是无法维护,致使软件变得臃肿不堪,运行效率低下,一种恶性循环的死亡怪圈就此形成。”

fcd115073b0069d5083d0910cd3b52d3.gif

小白被眼前的景象震惊了:“这简直太形象了。”

由于画面过于酸爽,老T顺手合起了电脑:“嗯,等你积累的项目经验足够多了,就会发现重构越早发生越好,一般都是边写边改。甚至是在设计之初我们就有了大体的架构,后面重构就会少发生。所以自始至终我们都得有设计模式的思维与格局,贯穿于整个设计开发周期,而不是等到上面情况发生才亡羊补牢。”

老T最后总结到:“相较于无脑化的代码堆叠,设计模式能让系统以一种更为优雅的方式解决现实问题,并有能力应对不断扩展的需求。恰当的设计模式运用能使软件系统的结构变得更加合理,让软件模块间耦合度大大降低,提升系统的灵活性与扩展性,以便我们在最小改动或者不做改动的前提下,通过增加模块的方式对系统功能进行功能增强。”

小白终于意识到了:“原来设计模式如此重要。”

“设计模式虽然多,但总结下来也就20多种。好了,讲太多你也吸收不了,不要想一口就吞个胖子,学习需要由浅入深一步一个脚印才能将它们逐个攻破,并且理论需要与实践相结合,最终才能融会贯通于实际项目中去。所以慢慢来,今天就到这里吧。”


声明:以上内容由《秒懂设计模式》作者摘取改编自本书章节:

7a23e48b4bbd88bd3a4185f7315dec8a.png

老规矩,小程序抽奖,抽出10名幸运粉丝,每人免费赠送一本《秒懂设计模式》。(疫情地区,快递不接的不予发货,见谅)

ce7fdfe0ff0b6c165cdc52adc9cd27d8.png

附:《秒懂设计模式》目录

d48b18f1086c59052c0bb0de654b8a95.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值