资讯详情

图解设计模式,看完秒懂!!!

《设计模式是什么鬼》最终由人民邮电出版社出版。标题:秒理解设计模式,作者:刘涛,笔名:凹凸。以下摘自本书第一章的初步探索,这里提供免费的学习和交流。

第1章 初探

在这个软件开发日新月异的时代,软件产品不知所措,软件需求不可预测,难以预测。作为技术人员,我们在软件开发过程中经常遇到代码重复的问题,这就需要改变和重构系统,因此良好稳定的软件架构非常重要。设计模式是为了解决这些问题,为各种场景提供最合适的代码模块重用解决方案。

最早的设计模式是1994年Gang Of Four(四人组)提出C 以面向对象语言为例,如今已广泛应用于面向对象语言中Java、C#等程序面向设计语言开发。事实上,设计模式与编程语言无关。编程语言只是与计算机交流的媒介,可以用自己的方式实现设计模式。从某种意义上说,设计模式不是一种特定的技术,而是一种思想和模式。这本书将是当今最流行的Java以面向对象编程语言为例,分析了23种设计模式。

在学习设计模式之前,我们必须弄清楚什么是面向对象。我们都生活在现实世界中,这里充满了各种各样的对象,如本章的封面图所示,包括山川、花鸟鱼虫、高层建筑和繁忙的交通。我们每天都要面对他们,与他们交流和互动,这是对面对象最简单的理解。为了在计算机世界中重现现实世界,我们想出了各种方法来为这些对象建立数字模型(类别),但理想是丰满的,现实是残酷的,我们永远不能包括世界上的一切。当人们在创造的过程中发现类与类之间存在着密不可分的联系时,他们想到了面向对象的独特编程方法,使用包装、继承和多种方法进行建模,大大减少了重复代码,减少了类之间的耦合,并像积木一样组装了整个世界。封装

1.1 封装

对于包装的概念,我们以现实世界中的事物为例,如胶囊混合各种药物的包装;包装现金、身份证和银行卡;主板的电脑底盘CPU以及内存和其他部件的包装,包装可以说在我们的生活中随处可见。让我们举一个现实生活中最常见的例子。我们都用过饮料,如图1-2所示。

图1-1 饮料的封装

如图1-1所示,请注意盘子里的可乐杯。饮料装在杯子里杯子里,盖上盖子,只有一个孔用于插吸管饮用。这实际上是包装。内部饮料隐藏在封装中,可能会有冰块,而外面有一个接口。这种做法是多余的吗?会带来什么好处?首先是方便快捷,这样我们就可以带着饮料四处走走,随吸随喝,而不是到处洒饮料,因为零散的数据管理不集中,更难记录、引用和阅读。更重要的是,包装后的可乐更干净卫生,防止外部灰尘落入,杯子里有关键词private声明中的可乐将成为内部私有化的对象,以防止外部随机访问,避免数据污染。最后,暴露在外的吸管接口带来了极大的便利。顾客在喝可乐时不需要关心内部对象或工作机制,比如杯子里的冰如何冷却可乐;如何改变杯子内部的气压;气压差是如何导致可乐流出的,这对顾客来说是完全隐藏的。留给顾客的操作其实很简单。你只需要用吸的公共方法就可以喝凉可乐。

让我们再想想电脑主机的包装。它必须需要一个底盘外壳来包装各种配件,如主板CPU、内存、显卡、硬盘等。一方面,它起到保护作用,防止乱七八糟的东西肆意进入内部损坏电路,如老鼠和昆虫。另一方面,底盘并不完全关闭。它必须预留一些外部访问接口,如各种启动和重启按钮,以便用户可以使用计算机。请参阅计算机主机的类别图,如图1-2所示。

图1-2 电脑主机类图

事实上,包装的概念在历史发展中非常常见。事实上,这是随着时间的推移,前人经验技术产品逐渐堆叠和组合的过程。例如,早期的枪设计非常原始和简单,需要很长时间才能准备一颗子弹。填充时,先将火药倒入枪管,然后装入铅弹,最后用棍子戳,然后发射,然后循环填充过程,耗时费力。为了解决这个问题,人们开始思考,既然弹药很难填充,最好把弹头和火药结合起来,然后包装在弹壳里。这样,只要撞到底火弹头,爆炸的火药就会崩出,一触即发,如图1-3所示。

图1-3 弹药的发展

如图1-3所示,从弹药到子弹的发展实际上只是弹药的封装,大大提高了装弹效率。事实上,一次发子弹是不够高效的。如果再进一步,在子弹外包装一层弹夹会带来更显著的改善。我们定义了一个堆栈数据结构来模拟弹夹,以确保最早压入(push)子弹最终弹出(pop),这就是栈结构先进后出,后进先出的特点,可以一次更换或击发多颗子弹,然后只需更换弹夹即可。至此,封装层层叠叠,机枪发明后,冷兵器时代彻底结束。

在Java在编程语言中,一对大括号{}是类的外壳和边界,它可以很好地包裹类的各种属性和行为,包装在类的内部,固化成一个整体。从外部看,封装类被视为黑匣子,看不到其内部结构和运行机制外部只需访问其暴露的属性或方法。需要注意的是,我们不能过度设计、过度包装,更不用说拉、攀爬亲戚,如台灯、轮子、茶杯等物品包装在一起,或计算机主机包装算盘这种荒谬的做法,一些不相关的业务对象一起包装,做大,代码膨胀,类结构复杂,难以管理,太多,所以包装必须停止。

1.2 继承

继承是面向对象的一个非常重要的特征。没有它,代码量将变得非常大,难以维护。继承可以将父类的属性和方法延伸到子类中,这样子类就不需要再定义了,子类可以通过重写来修改继承方法,或者通过添加来实现属性和功能扩展的目的。从某种意义上说,如果类是对象的模板,那么父类(或超类)就可以被视为模板。

什么是生物可以代代延续?是的,是遗传基因DNA,如图1-4所示。俗话说龙生龙凤,老鼠儿子会打洞。没有这种遗传机制,代码量会急剧增加,很多功能和资源会重复定义,造成很大的冗余和浪费。因此,受大自然的启发,面向对象有继承机制。

图1-4 生物遗传基因

儿子从父亲那里继承了一些东西,不需要自己努力工作,避免重复轮子,如富有的第二代继承财产,生来财富;另一个例子是天生的好声音,因为母亲是歌手。因此,无论是身体上还是财富上,继承都能让子孙后代得到父母基业的传承。以我们最常见的动物为例。狗是人类最忠实的朋友。它们在一万多年的演变中不断繁殖。此外,人类的培育产生了多种品种,如图1-5所示。

图1-5 犬类的继承

让我们考虑如何使用代码进行建模。如果每个狗品种定义一个类并包装自己的属性方法,不言而喻,这将不可避免地导致代码泛滥。让我们以最典型和最常用的类继承为例。无论什么品种的狗,它们都必须有一些共同的特征和行为,如性别属性、吠叫等。因此,我们需要拔出公共狗基因,并将其包装在狗的祖先中供后代继承。请参阅代码清单1-1。

代码清单1-1 犬类的先祖Dog

 publicclassDog{ protectedStringbreeds;//品种 protectedbooleansex;//性别 protectedStringcolor;//毛色 protectedintage;//年龄  publicDog(Stringbreeds){ this.age=0;///初始化为0岁 this.breeds=breeds;///初始化犬品种 }  publicvoidbark(){//犬吠 System.out.println("汪汪汪"); }  publicStringgetBreeds(){ returnbreeds; }  /*假设自诞生以来就不能变种,所以这里不应该暴露setBreeds方法。 publicvoidsetBreeds(Stringbreeds){ this.breeds=breeds; } */ publicbooleanisSex(){ returnsex; }  publicvoidsetSex(booleansex){ this.sex=sex; }  publicStringgetColor()&nbs;{
    return color;
  }

  public void setColor(String color) {
    this.color = color;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }
}

如代码清单1-1所示,我们为犬类定义了品种、性别、毛色、年龄4个属性,并且带有响应的setter和getter方法,还有第12行的吠叫方法,这也是所有犬类品种的共有行为,统统都能被子类继承。需要注意的是,倘若我们把犬类属性的访问权限由“protected”改为“private”,这就意味着子类不能再直接访问这些属性了,但这并无大碍,最终其依旧可以通过继承下来的并且声明为“public”的getter和setter去间接访问它们,总之能达到继承的目的即可。好了,接下来我们用子类哈士奇犬来说明如何继承,请参看代码清单1-2。

代码清单1-2 哈士奇类Husky

public class Husky extends Dog {

  public Husky() {
    super("哈士奇");
  }

  public void sleighRide() {//拉雪橇
    System.out.println("拉雪橇");
  }

}

如代码清单1-2所示,为了延续父类的基因,哈士奇类在第一行的类定义后用“extends”关键字声明了对父类Dog的继承关系。第3行构造时以“super”关键字调用了父类的构造方法,并初始化了狗的品种breeds为“哈士奇”,当然年龄一并会被父类初始化为0岁。我们可以看到哈士奇类的代码已经变得特别简单了,并没有定义任何getter或setter方法,也没有出现犬吠方法的定义,而当我们调用这些方法时能神奇般地得到结果,自然而然地,它沿用了父类的方法。只是单单继承父类的本领还是不够的,哈士奇类更应该有自己的特色,比如增加自己的属性、方法,可以看到代码第7行我们增加了哈士奇特有的“拉雪橇”本领,这是父类所不能及的。除此之外,哈士奇吠叫起来比较特殊,可能是基因突变或者是返祖现象导致,这时我们甚至可以重写吠叫方法让它发出狼的叫声。接下来其他的子类继承各尽其能,比如贵宾犬可以作揖,藏獒可以看家护院等等,读者可以自己发挥。总之,继承的目的并不是一成不变的全盘照搬,基于父类的基因,子类更可以灵活扩展。

扩展阅读

大家都知道任何类都有一个toString()方法,但我们根本没有声明它,这是为什么呢?其实这就是从Object类继承下来的优良传统,因为Object是一切类的祖先类

1.3 多态

众所周知,在我们创建对象的时候通常会再定义一个引用指向它以便后续进行对象操作,而这个引用的类型则决定着其能够指向哪些对象,用犬类定义的引用绝不能指向猫类对象,所以说对于父类定义的引用只能指向本类或者任何子类实例化出来的对象,这就是一种多态。除此之外还有其他形式的多态,例如抽象类引用指向子类对象,或者接口引用指向实现类的对象。

我们继续以上一节中的犬类继承为例,如果以犬类Dog作为父类,那么哈士奇、贵宾、藏獒、吉娃娃等等都可以作为其子类,如果我们定义犬类引用dog,那么它就可以指向犬类的对象,或者其任意子类的对象,翻译成大白话就是“哈士奇是犬类,藏獒是犬类……”。下面我们用代码来表示,请参看代码清单1-3。

代码清单1-3 犬类多态构造示例

Dog dog; //定义父类引用
dog = new Dog();//父类引用指向父类对象(狗是犬类)
dog = new Husky()//父类引用指向子类对象(哈士奇是犬类)

Husky husky = new Dog();//错误:子类引用指向父类对象(犬类是哈士奇)

如代码清单1-3所示,前3行都没有任何问题,犬类引用可以指向狗,也可以指向哈士奇,这让dog引用变得更加灵活、多变,可以引用任何本类或子类对象。然而第5行代码就会出现问题,让哈士奇的引用指向犬类Dog的对象就行不通了,这就好像是说“犬类就是哈士奇”一样,逻辑混乱。

再进一步讲,多态其实是利用了继承(或接口实现)这个特性体现出来的另一番光景。我们以食物举例,中华美食博大精深,菜品众多且色香味俱全,这都离不开各种各样的食材,如图1-6所示。

图1-6 有机食物的多态性

虽然说食材形态各异,但是万变不离其宗,它们都是自然界生长出来的有机生物。而作为人类,我们可以食用哪些东西?显而易见人类只可以食用机食物类,对于金属、塑料等是不能消化的。所以正如人类与食物的关系类图1-7所描述的那样,我们的食物一定可以是蔬菜、水果、肉等有机食品的多形态表现,而决不能食用金属类物质。

图1-7 人类与食物类图

我们在设计的时候,对多态的把握一定要适可而止,切勿无限放大,否则通通用Object作为引用不就更加灵活,多态性更加丰富吗?所以说物极必反,过于灵活的自由散漫会有失规范性,无规矩则不成方圆,越泛化不代表越好,适可而止才是最好的。

1.4 电脑与外设

为了更透彻深入地理解面向对象的特性,以及设计模式如何巧妙利用面向对象来组织、布局各种模块协同工作,我们以一个形象贴切的例子切入实战部分。如图1-8所示,相信很多读者都没有见过这种电脑,没错,这是一台早期的个人电脑,可以明显看到它的键盘、主机和显示器等都是集成为一体的。

图1-8 老式电脑

越是古老的电脑越是高度集成,恨不得把所有配件都一体化,配件之间的耦合性极大,如胶似漆难以拆分。这种过度封装的电脑为什么会退出历史舞台?试想,某天显示器坏掉了麻烦就来了,我们只能去送修,然后把整个壳子拆开进行更换,如果显示器是焊接在主板上的那就更惨了,缺少接口的设计造成了极大的耦合度,而更糟的是这种显示器已经停产了根本买不到新的来匹配,结果只能整机换新。

这种局面后来被某公司打破,为解决这个问题设计人员提出了模块化的概念,各种外部设备如雨后春笋般涌现,如:鼠标,键盘,摄像头,打印机,外接硬盘……但这时又出现一个问题,每种设备都有一种接口,那电脑主机上得有多少种接口啊?串口、并口、PS2接口……接口泛滥将是一场灾难,制定标准化的接口势在必行,于是便有了现在的USB接口。USB提供了一种接口标准,电压5V,双工数据传输,最重要的是其物理形态上的统一规范,只要是USB标准设备则可以进行接驳,最终电脑发展成了如图1-9的样子。

图1-9 现代电脑设计

我们每天都在接触电脑,但对于这种设计可能从未思考过,为了便于理解我们让电脑和各种设备鲜活起来,下面是它们之间展开的一场精彩对话,其中角色包括一个电脑类,一个USB接口,还有各种USB设备类,故事就这样开始了。

电脑:“我宣布,从现在开始USB接口晋升为我的秘书,我只接收它传递过来的数据,谁要找我沟通必须通过它。”

USB接口:“要接驳我的设备是什么我不关心,但我规定你必须实现我定义的readData(Data data)这个方法,但具体怎样实现我不管,总之我会调用你的这个方法把数据读过来。”

USB键盘:“我有readData(Data data)这个方法,我已经实现好了,传过去的是用户输入的字符。”

USB鼠标:“我也一样,但传过去的是鼠标移动或点击数据。”

USB摄像头:“没错,我也实现了这个方法,只是我的数据是视频流相关的。”

USB接口:“无所谓你们是什么类型的数据,只要传过来转换成Data就行了,我接收你们的接驳请求,除了PS2鼠标。”

PS2鼠标:“@电脑,老大这怎么办?你找来这个USB接口太霸道了,我们根本无法沟通,你们都不能尊重一下老人吗?”

Computer电脑:“你自己想办法,要顺应时代潮流,与时俱进。”

PS2鼠标:“……”

显然,通过这场对话我们有了更深刻的认识,电脑中封装了一个USB接口,这就是“封装”,而键盘、鼠标及摄像头都是USB接口的实现类,广义上理解这就是一种“继承”,所以最终电脑的USB接口就能接驳各种各样的USB设备的,这就是“多态”,我们来看它们的类结构图,如图1-10所示。

图1-10 新型电脑类图

通过对电脑接口的抽象化、标准化,我们对各个模块重新分类,规划,合理封装,最终实现电脑与外设的彻底解耦,多态化的外部设备使电脑功能更加强大、灵活多变、可扩展可替换。其实这就是设计模式中非常重要的一种“策略模式”,接口的定义是解决耦合问题的关键所在。但针对一些老旧的设备模块我们暂时还无法使用,正如同上面故事里那个可怜的PS2鼠标。

然而,我们都知道有一种设备叫转换器,它能轻松地将老旧的接口设备调制适配到新的接口达到兼容的目的,这就是“适配器模式”了。当然,这些设计模式之后都会被讲到,我们不要想一口就吞个胖子,学习需要由浅入深一步一个脚印才能将它们逐个攻破,并且理论与实践相结合,最终融会贯通于实际项目中去,这样才能设计出更加优雅、健壮、灵活的应用程序。

书籍目录

另外,随机送出5本,抽奖即可,明天中午12点开奖,中奖者把收货信息及时写入,三天内发出!

对本书有兴趣,扫码可以直接购买!

标签: 气压变送器显示仪

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台