第二十九章 大学生毕业3条出路:学、仕、商——设计模式总结_大话设计模式_乐读窝小说
首页

第二十九章 大学生毕业3条出路:学、仕、商——设计模式总结

关灯 护眼    字体:

上一章 目录 下一章

    29.1 大学生毕业3条出路:学、仕、商

    时间:2月7日  地点:大B房间  人物:大B,小A

    小A:“师兄,在就业形势日益激烈的今天,大学毕业生应该如何面对求职难的问题。”

    大B:“按照现今社会的主流说法,大学生毕业后的走向大体有三条——学、仕、商。”

    小A:“学、仕、商?为什么?”

    大B:“上大学以前,我的最初想法是,本科毕业后,先从商,成也好,败也好,人生嘛,什么都经应该历一下。等自己喜欢安定了,就去感受一下仕途。”

    小A:“嗯!我现在也是这样认为的。”

    大B:“但上了大学,了解了很多现实,很多跟理想差距很大的现实,现阶段我们要做的就是选好一条有利于自己的并且可行的路,然后按照要求和计划,努力提高自己相关方面的能力。”

    小A:“我也曾这么想过。”

    大B:“但来到大学以后,比较充分了解了社会现状,而这个现状使得我不得不放弃原来的思路,而且一些新问题比我原来想象的要复杂得多。而现在,我首先想提出的问题是本科毕业后,是考研还是就业。”

    小A:“嗯,对!这是我一直在想的问题。”

    大B:“是啊!这个问题离我们比较近,所以考虑得比较多。就选择而言,两个都有各自的好处和风险。考研的好处在于,毕业后,可以有个更高的台阶去面对社会,个人的第一身份也比本科生高得多。但它的风险在于,选择考研,至少要投入三年以上的时间,更遭的是有很多人成为了所谓的考研专业户。而如果把这笔时间投入到社会上,说不定已经有不小的成就。但提前走入社会的风险在于可能几年时间下来,还是一事无成,那还不如多读几年书,有个更高的学识和文凭。总的来说,就是把本科后的四年作为一个发展段,把考研与就业带来的收益和风险作权衡,其中有个零界点,现阶段迷茫的是,以个人自身的条件,应该站在选择的自我定位在界限的哪边,选择的自我定位有哪些标准,怎样努力才能最大限度有利于自己的定位。”

    小A:“嗯!师兄的话真让我受益非浅。说的正是我现在困惑的问题。”

    大B:“还有一个疑问是职业困惑,就是离开学校,无论是本科毕业还是硕士毕业,选择职业的切入点在哪,或者说以什么样的标准来区分到底是去考公务员,还是自己在政途以外的地方寻找落脚点才能最大限度地获取自己能创造的价值。”

    小A:“嗯!”

    大B:“说到这,很汗颜,因为这些选择都有很强的趋利性。最后,在找到自己先天所适合的路以后,怎样培养自己,使得自己更适合这条路,沿着适合自己的道路努力奋进,假以时日,收获成功就不是一句空话了!”

    29.2 设计模式总结

    大B:“刚毕业的大学生面临人生的这一大转折。要好好准备,去迎接机遇与挑战。现在我们就来聊聊设计模式吧。学了这么久的设计模式,你有什么感想吗?今天我们来总结一下设计模式吧。”

    小A:“刚开始学习设计模式的时候,感到这些模式真的非常抽象。在设计过程中,我发现了很多设计模式的用处,也确实应用了很多设计模式,这让我越来越感到设计模式的重要性。”

    大B:“设计模式是个好东西,它给出了很多设计中的技巧与思路,对于很多优秀的设计,它加以总结与提炼。设计模式并非是人拍脑瓜想出来的,而是他们搜集了其他人优秀的设计,加以整理出来的,他们不是这些模式的创造者,仅仅是整理者。”

    小A:“应用设计模式还给我们带来了很多好处。”

    大B:“是啊!软件将变得更加灵活,模块之间的耦合度将会降低,效率会提升,开销会减少。更重要的,设计模式就好像美声唱法中的花腔,让你的设计更加漂亮。总的来说,设计模式似乎将软件设计提升到艺术的层次。”

    小A:“设计模式已经被广泛的应用了,在现在很多的图形界面框架都使用了MVC模式,大量跌代器模式的应用,彻底改变了我们对集合的操作方式。不仅如此,应用了设计模式的设计,往往被看成为优秀的设计。这是因为,这些设计模式都是久经考验的。”

    大B:“在学习和使用设计模式的时候,往往出现一个非常严重的误区,那就是设计模式必须严格地遵守,不能修改。但是设计模式不是设计模型,并非一成不变。正相反,设计模式中最核心的要素并非设计的结构,而是设计的思想。只有掌握住设计模式的核心思想,才能正确、灵活的应用设计模式,否则再怎么使用设计模式,也不过是生搬硬套。”

    小A:“当然,掌握设计模式的思想,关键是要仔细研究模式的意图和结构。一个模式的意图,就是使用这个设计模式的目的,体现了为什么要使用这个模式,也就是需求问题。”

    大B:“是啊!这个模式的结构,就是如何去解决这个问题,是一种手段、一种经典的解决方法,这种解决方法只是一种建议。两个方面结合起来,明白为什么需要设计模式,同时明白了如何实现这个模式,就容易抓住模式的本质思想。”

    小A:“在抓住意图和结构的基础上,实践也是掌握设计模式的必要方法。”

    大B:“当然,设计模式必须在某个场景下得到应用才有意义,这也是为什么要提供大量的例子用来说明模式的应用场景,这实际上是提供了一种上下文环境。学外语不是要强调‘语言环境’么,学习设计模式也是这样。”

    小A:“嗯!是的。”

    大B:“看到网上很多人在讨论设计模式,他们确实很有创意,满嘴都是模式的名字,恨不得写个Hello World都要应用到设计模式。设计模式确实是好东西,但是,中国有句古话叫作物极必反,即便是按照辩证法,事物总要一分为二的看。”

    小A:“是啊!我们说设计模式的目的是为了让软件更加灵活,重用度更高。”

    大B:“但是,某种意义上,设计模式增加了软件维护的难度,特别是它增加了对象之间关联的复杂度。”

    小A:“嗯。对!”

    大B:“我们总说,重用可以提高软件开发的效率。如果你是大牛,你自然希望你的设计可以被反复使用10000年,那就是:当世界毁灭的时候,你的设计依然存在。然而,现实是一个系统的设计往往在5年之内就会被抛弃,这是因为:1、软件技术产生了新的变化,使用新的技术进行的设计,无论如何都比你的设计好;2、硬件环境发生了很大变化,你的设计里对开销或者效率的追求已经没有意义了;3、新的大牛出现了,并且取代了你的位置。”

    小A:“应用设计模式会导致设计周期的加长,因为更复杂了,但是很多项目还在设计阶段就已经胎死腹中,再好的设计也没有发挥的余地。”

    大B:“当我们向设计模式顶礼膜拜的时候,我们还必须清醒地看到软件生产中非技术层面上的东西往往具有决定性作用。理想固然崇高,但现实总是残酷的。如何看清理想与现实的界限,恐怕是需要我们在实践中不断磨砺而体会出来的。在看完设计模式后,不妨反问以下自己,这些模式究竟能给你带来什么?”

    29.3 常见的23个设计模式概念

    小A:“所有结构良好的面向对象软件体系结构中都包含了许多模式。”

    大B:“实际上,当我们评估一个面向对象系统的质量时,所使用的方法之一就是要判断系统的设计者是否强调了对象之间的公共协同关系。在系统开发阶段强调这种机制的优势在于,它能使所生成的系统体系结构更加精巧、简洁和易于理解,其程度远远超过了未使用模式的体系结构。”

    小A:“喔。”

    大B:“这23个设计模式便是总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。”

    1、Abstract Factory 抽象工厂模式——提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

    2、Adapter 适配器模式—–将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

    3、Bridge 桥接模式——将抽象部分与它的实现部分分离,使它们都可以独立地变化。

    4、Builder 生成器模式——将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

    5、Chain of Responsibility 职责链模式——为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。

    6、Command 命令模式——将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。

    7、Composite 组合模式——–将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得客户对单个对象和复合对象的使用具有一致性。

    8、Decorator 装饰模式——动态地给一个对象添加一些额外的职责。就扩展功能而言,Decorator模式比生成子类方式更为灵活。

    9、Facade 外观模式——为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

    10、Factory Method 工厂方法模式——定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。

    11、Flyweight 享元模式——运用共享技术有效地支持大量细粒度的对象。

    12、Interpreter 解释器模式——给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。

    13、Iterator 迭代器模式—–提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。

    14、Mediator 中介者模式——用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

    15、Memento 备忘模式——在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。

    16、Observer 观察者模式:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。

    17、Prototype 原型模式——用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。

    18、Proxy 代理模式:为其他对象提供一个代理以控制对这个对象的访问。

    19、Singleton 单态模式——保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    20、State 状态模式:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。

    21、Strategy 策略模式——定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。

    22、Template Method 模板方法模式——定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

    23、Visitor 访问者模式—–表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

    附录:面向对象基础

    小A:“为什么要‘面向对象’?”

    大B:“面向对象方法使构建系统更容易,因为:解决正确的问题,正常工作,易维护,易扩充,易重用。大家发现面向对象更易理解,实现可以更简单。把数据和功能组合在一起简单而自然,分析和实现之间的概念跨度更小,设计良好的一组对象能弹性地适应重用和变化,可视化模型提供更有效的沟通,建模过程有助于创建通用词汇以及在开发者和用户/客户之间达成共识。非计算机编程人员也能理解对象模型 这些好处可以使用面向对象方法获得,但面向对象方法不能保证这一点。”

    小A:“怎样才能变成优秀的面向对象设计者?”

    大B:“只有靠经验和聪明的头脑才能做到。”

    ■ 过程化方法(The Procedural Approach)

    小A:“怎样过程化方法?”

    大B:“系统由过程(procedures)组成,过程之间互相发送数据,过程和数据各自独立,集中于数据结构、算法和运算步骤的先后顺序,过程经常难以重用,缺乏具有较强表现力的可视化建模技术,分析与实现之间需要进行概念转换,本质上是机器/汇编语言的抽象,从设计模型到代码实现跨度很大。”

    ■ 面向对象方法

    大B:“系统由对象组成,对象互相发送消息(过程调用)相关的数据和行为紧密地绑定在对象中,把问题领域建模成对象,要解决的问题自然的映射为代码的实现,可视模型表现力强,相对容易理解 集中于实现之前所确定的职责(responsibilities)和接口。强有力的概念:接口,抽象,封装,继承,委托(delegation)和多态。问题的可视模型逐渐进化成解决方案模型,设计模型与代码实现之间跨度较小努力缩减软件的复杂度。”

    ■ 温度换算

    大B:“下面我就以温度换算为例。”

    过程/函数化方法

    float c = getTemperature(); // 假定为摄氏度 Celsius

    float f = toFarenheitFromCelcius( c );

    float k = toKelvinFromCelcius( c );

    float x = toKelvinFromFarenheit( f );

    float y = toFarenheitFromKelvin( k );

    面向对象方法

    Temp temp = getTemperature();

    float c = temp.toCelcius();

    float f = temp.toFarenheit();

    float k = temp.toKelvin();

    包含有数据的Temp的内部单元是什么?

    ■ 建模(Modeling)

    小A:“成功的程序能解决真实世界的问题。”

    大B:“嗯,是的。它们紧密对应于需要解决的问题。对问题领域和用户活动进行建模。”

    小A:“建模促进与用户更好的可视化交流。”

    大B:“成功的面向对象设计总是一开始就由领域专家和软件设计者建立一个反映问题领域的可视化的‘对象模型’。”

    小A:“嗯。是的。”

    大B:“你愿意让承包人在没有设计蓝图的情况下建造你的新房子吗?”

    小A:“那当然不行啦。”

    ■ 对象

    大B:“你知道怎样去理解什么是对象吗?”

    小A:“对象代表真实或抽象的事物,有一个名字,有明确的职责(well-defined responsibilities),展示良好的行为(well-defined behavior),接口清晰,并且尽可能简单、自相容,内聚,完备(self-consistent,coherent,and complete)。”

    大B:“嗯,对。(通常)不是很复杂或很大,只需要理解自己和一小部分其他对象的接口,与一小部分其它对象协同工作(team players),尽可能地与其它对象松散耦合(loosely coupled),很好地文档化,以便他人使用或重用,对象是类的实例,每一个对象都有唯一的标识,类定义一组对象的接口和实现,即定义了这些对象的行为,抽象类不能拥有实例,只要有抽象类(如宠物),通常就会有能够实例化的具体类(如猫,狗等),一些面向对象语言(如Smalltalk)支持元类(metaclass)的概念,程序员可以随时(on-the-fly)定义一个类,然后实例化。这种情况下,类也是一个对象,即元类。对象一旦实例化,就不能更改它的类。”

    ■ 对象的特征

    大B:“那你知道对象有什么特征吗?”

    小A:“有唯一标识,可以分成许多种类(即类),可以继承或聚合。行为、职责明确,接口与实现分离,隐藏内部结构,有不同的状态,可以提供服务,可以给其它对象发送消息,从其它对象接收消息,并做出相应响应,可以把职责委托给其它对象。”

    大B:“对,说得非常全面。”

    ■ 类

    小A:“怎么样才叫类呢?”

    大B:“有公共的属性和行为的一组对象可以抽象成为类,对象通常根据你所感兴趣的属性而分类。”

    小A:“喔。”

    大B:“ 例如:街道,马路,高速公路... 不同的程序对它们分类也不同。交通模拟器程序,单行道,双通道,有分车道的,住宅区的,限制通行的维护调度程序,路面材料,重型卡车运输类本身也可以有属性和行为。例如:养老金管理程序中的“雇员”类雇员总数,雇员编制多少,不同语言对类的支持略有不同: Smalltalk 把类当作对象(很有好处),C++提供最小限度的支持(有时会带来很多烦恼),Java位于上述两者之间,类也是对象,类可以有属性“雇员”类可以有一个包含其所有实例的列表(list)“彩票”类可以有一个种子(seed)用于产生随机票号,该种子被所有实例共享,类可以有行为,雇员”类可以有 getEmployeeBySerialNum 行为。 “彩票”类可以有 generateRandomNumber 行为。”

    ■ 封装

    大B:“只暴露相关的细节,即公有接口(public interface)。”

    小A:“封装什么?如何封装?”

    大B:“隐藏“齿轮和控制杆”只暴露客户需要的职责,防止对象受到外界干扰,防止其它对象依赖可能变化的细节,信息隐藏有助于对象和模块之间的松散耦合,使得设计更加灵活,更易于重用,减少代码之间的依赖,“有好篱笆才有好邻居”。例如:汽车的气动踏板。”

    小A:“怎样才能更好地实践?”

    大B:“最佳实践:对象之间只通过方法(函数)互相访问。切忌直接访问属性。”

    class Person { public int age; } class BetterPerson { private int age; // change to dateOfBirth public int getAge() { return age; } }

    更完善的 Person 类可能是: private dateOfBirth

    ■ 抽象

    小A:“什么是抽象?”

    大B:“抽象使得泛化(generalizaions)成为可能,简化问题-忽略复杂的细节,关注共性,并且允许变更,人类经常使用泛化。 当你看见约翰和简家里的那头灰德国牧羊犬时,你有没有......想到“狗”这个词?抽象同样能简化计算机程序。例如,软件中有两个重要抽象:客户端和服务器(clients and servers)。”

    小A:“喔。”

    大B:“在图形用户界面中,系统可能会询问用户各种问题:是或不是多选一?输入数字,任意文本问题统一处理这些问题会显得很简单,每一个问题都作为Question类的特例(specialization);程序只需维护这些问题的实例列表,分别调用各自的askTheUser()方法。”

    ■ 继承

    小A:“什么是继承?”

    大B:“继承用于描述一个类与其它类的不同之处。例如:类Y像类X,但有下列不同... ”

    小A:“为什么使用继承?”

    大B:“你有两种类型,其中一种是另一种的扩展。有时(不是所有时候)你想忽略对象之间的不同,而只关注它们的共同之处(基类)。这就是泛化。假如某系统需要对不同的形状进行操作(经典例子):有时你并不关心你正在操作的形状的种类(例如,移动形状时)有时你必须知道形状的种类(在显示器上绘制形状)”

    小A:“怎样去理解派生类?”

    大B:“派生类继承自基类;派生类扩展了基类;派生类是基类的特殊化(specialization)。派生类能够提供额外的状态(数据成员),或额外的行为(成员函数/方法),或覆盖所继承的方法。 基类是所有它的派生类的泛化。如:通常所有宠物都有名字。基类(Base Class)=父类(parent class)=超类(superclass)派生类(Derived Class)=子类(child class)=子类(subclass)”

    小A:“喔。”

    大B:“继承含有(有些,不是全部)是一个(is-a)或是一种(is-a-kind-of)的关系,正方形是一种矩形(使用继承),Leroy 是一种狗(不使用继承),传统的过程分析和设计中不能很好地模拟这种关系。继承是一种强有力的机制,使我们关注共性,而不是特定的细节。使得代码可以重用且富有弹性(能适应变化)。”

    小A:怎样去实现继承?”

    大B:“实现继承(Implementation inheritance):派生类继承基类的属性和行为。”

    小A:“又应该怎样去接口继承?”

    大B:“接口继承(Interface inheritance):类实现抽象接口的方法,保留既定语义(intended semantics) C++允许多重实现继承。Java规定派生类只能有一个基类,但可以继承自多个接口。”

    ■ 多态

    小A:“什么是多态?”

    大B:“多态是一种允许多个类针对同一消息有不同的反应的能力。对于任何实现了给定接口的对象,在不明确指定类名的情况下,就可以使用。 例如:question.askTheUser(); 当然,这些不同反应都有类似的本质 尽可能使用接口继承和动态(运行期)绑定 Liskov 替换原则:如果Y是X的子类,那么在任何使用X实例的地方都可以用Y的实例来替换。”

    演示多态的Java代码

    // File: question/QuestionTest.java // 下面的代码将输出什么? // Refer to the Beginning Java link on the course web site. package question; abstract class Question { // Full class name is question. QuestionTest public Question( String _text ) { // Constructor theText = _text; } public abstract void askTheUser(); protected String theText; } class YesNoQuestion extends Question { public YesNoQuestion( String _text ) { super( _text ); } public void askTheUser() { System.out.println( theText ); System.out.println( "YES or NO ...?" ); } } class FreeTextQuestion extends Question { public FreeTextQuestion( String _text ) { super( _text ); } public void askTheUser() { System.out.println( theText ); System.out.println( "Well...? What’s the answer...?" ); } } public class QuestionTest { public static void main(String[] args) { Question[] questions = getQuestions(); for (int i = 0; i

    输出:

    Do you understand polymorphism?

    YES or NO ...?

    Why is polymorphism good?

    Well...? What's the answer...?

    更多的Java例子

    // File: Derived.java // What will the following Java code output to the screen? class Base { final void foo() { System.out.println("Base foo"); } void bar() { System.out.println("Base bar"); } } public class Derived extends Base { void bar() { System.out.println("Derived bar"); } public static void main(String[] args) { Derived d = new Derived(); d.foo(); d.bar(); Base b = d; b.bar(); } }

    输出:

    Base foo

    Derived bar

    Derived bar

    ■ 为什么面向对象有效

    小A:“为什么面向对象有效?”

    大B:“首先是减小复杂度。”

    小A:“嗯。”

    大B:“我们从封装、多态、继承、委托来具体说。“

    小A:“喔?”

    大B:“封装:只暴露公有接口,隐藏了复杂的实现细节,避免代码之间复杂的相互依赖。多态:允许有相同接口的类互相替换,由此减小代码的复杂度。继承:使用抽象类或接口实现泛化来减小复杂度。委托:通过从更小、封装更好的服务来构建更完整或更高层次的服务来减小复杂度。委托还增加了运行时的灵活性。”

    小A:“面向对象有效我们是不是还可以从语言学和辨识角度来说?”

    大B:“是的。我们主要使用名词,然后对它进行修饰和增加属性,最后,把它和动词联合在一起。面向对象设计遵循这个模式,过程化设计不遵循这个模式,这就是为什么人们经常发现对象更容易理解。我们从对象模型中能形成构造良好的主谓宾(Subject-verb-object)句子:人们拥有宠物。Paula 拥有Leroy。试试用功能分解来形成主谓宾句子!而且,人们广泛使用抽象和泛化...”

    ■ 面向对象是编程进化一个自然阶段

    小A:“为什么说面向对象是编程进化一个自然阶段?”

    大B:“首先出现机器语言。在此基础上发展出汇编语言,提供了符号。高级语言出现:Fortran, Pascal, C等。它们提供了程序语句之间的“结构”关系。“goto”的使用日渐稀少,这有助于简化程序结构。数据结构和算法提供了程序结构的可重用模式,促进更高层次上的抽象。面向对象的抽象是为了关注于解决问题,而不是机器。通过更高层次的抽象,程序语句之间的关系转化成为相对简单的对象协作关系,设计模式提供可重用的对象结构...”

    ■ 面向对象更多的好处

    大B:“面向对象还有更多的好处。”

    小A:“是吗?都还些什么好处哩?”

    大B:“组件非常有用,代码重用。设计模式很好,设计重用。接口不错,灵活健壮的代码。底层结构(infrastructure)和可重用服务同样很好。接口能完美分离个人和团队的职责,增加团队效率,松散耦合和模块化提高了扩展性、灵活性、可量测性和重用性。逻辑变化很自然的被隔离起来,这多亏了对象的模块化和信息隐藏(封装)。这意味着实现更快,维护更容易。面向对象中间件使我们无须关注位置、平台和语言。组设计良好的对象是我们可以增加新功能而不用更改设计。”

    ■ 好的面向对象设计

    小A:“什么样的是好的面向对象设计?”

    大B:“艺术多于科学。可解决问题的模型本质上当然没有问题,但是一些模型就是比其它的好,这是因为它们更实用、更灵活、更容易扩展、更方便理解、更简单...第一个设计几乎不可能是最好的设计。找到最好的抽象来对问题建模始终不是一件容易的事情,经验很重要。”

    小A:“经常需要尝试多次,来确定如何划分系统各部分之间的边界才是最好?每一部分应该为其它部分提供什么接口?”

    大B:“以体系结构为中心,而不是功能为中心。首先关注全面的大体的,其次才是具体的特定的。设计时首先做到这一点,就成功了一大半。设计中要考虑可能发生的扩展,使得以后扩展是递增式的,不用更改设计。不同的设计可以有完全相同的功能,但是在这方面可能完全不同。尽量推广可重用的面向服务的底层结构,这样,在需求不可避免的变化时,代码也能更快、更容易的更改。”

    ■ 职责

    小A:“什么是职责?”

    大B:“是面向对象分析中采用的最普遍的方法。基于“客户端/服务器”关系,对“客户端/服务器”有两种通用的解释:用于分布式体系中,服务器提供对共享资源(如数据库)的访问,客户端提供用户界面。用于面向对象术语中,服务器是一个提供服务的对象;在这里我们使用这个含义,客户端与服务器协作(发送消息)。一个对象可能在一个协作中是客户端,而在另一个协作中是服务器。服务器负责提供某种服务,一般来说,对象应该以某种定义良好的方式工作。”

    ■ 设计过程概述

    大B:“我们讲了这么多,你知道设计过程是什么吗?”

    小A:“查看领域,识别对象、类。通常首先识别出对象,通过对象分组找到类,确定对象之间和类之间的关系,结构关系,协作关系,赋予职责,基于协作关系,迭代,迭代,迭代,迭代,迭代,迭代...以领域建模作为开始,而不是以建模解决方案作为开始。”

    ■ CRC卡片

    小A:“什么是CRC卡片?”

    大B:“CRC方法使用3×5(英寸)索引卡片,一个类就是一张卡片,卡片上写有该类的职责以及为了完成这些职责必须与哪些类协作。类的简要描述写在卡片背面。下面的例子中,类Foo必须与类X和类Y协作(给它们发送消息),以完成“do something”责任。”

    ■ 例子:“棍子”游戏

    游戏设计两个玩家使用一台计算机来一起玩。游戏中许多棍子按行排列,当游戏开始时,它们如下排列:

    1: |

    2: | |

    3: | | |

    4: | | | |

    ■ 游戏规则

    玩家轮流参加,每人可以从任何一个非空行中移走一根或多根棍子。移走最后一根棍子的人为输家。

    游戏开始时,程序将显示游戏的状态:轮到谁了,还有几行,还有多少棍子。

    操作不符合规则,程序将给出提示。(如所移走的棍子数目超过该行的棍子总数)

    找到对象和类…

    用CRC卡片

    附加问题:哪个类负责记录轮到那个玩家了?

    ■ 词汇(Vocabulary)

    类(Class)

    – 抽象(Abstract) / 具体(Concrete) / 元(Meta)

    对象(Object)

    – 实例(Instance)

    – 标识(Identity)

    属性(Attribute)

    – 成员(Member)

    – 域(Field)

    – 状态(State)

    行为(Behavior)

    – 方法(Method)

    – 成员函数(Member Function)

    – 操作(Operation)

    – 职责(Responsibility)

    – 消息(Message)

    – 调用方法(Method Call)

    接口(Interface)

    抽象(Abstraction)

    – 泛化(Generalization)

    – 特殊化(Specialization)

    继承(Inheritance)

    – 接口(Interface) / 实现(Implementation)

    – 基类(Base) / 派生类(Derived), 父类(Parent) / 子类(Child), 超类(Super) / 子类(Sub)

    委托(Delegation)

    协作(Collaboration)

    多态(Polymorphism)

    – Liskov 替换原则(Liskov Substitution Principle)

    – 动态绑定(Dynamic (run-time) Binding)

    聚合(Aggregation)

    底层结构(Infrastructure)

    – 服务(Services) / 中间件(Middleware) / 框架(Frameworks)

    统一建模语言(Unified Modeling Language (UML))

    分析(Analysis) / 设计(Design) / 实现(Implementation) / 架构(Architecture) / 过程(Process)

    松散耦合(Loose Coupling & Flexibility)

    封装(Encapsulation)

    – 信息隐藏(Information Hiding)

    模块性(Modularity)

    透明(Transparency)

    Java

    – 构造函数(Constructor) / 包(Package) / 静态(Static)

    模式(Patterns)

    职责驱动设计(Responsibility driven design)

    ■ 设计模式与面向对象

    小A:“面向对象设计模式主要是解决什么问题哩?”

    大B:“面向对象设计模式解决的是“类与相互通信的对象之间的组织关系,包括它们的角色、职责、协作方式几个方面。面向对象设计模式是‘好的面向对象设计’。

    小A:“什么是‘好的面向对象设计’?”

    大B:“所谓‘好的面向对象设计’是那些可以满足‘应对变化,提高复用’的设计。”

    小A:“这么说来,面向对象设计模式主要都是讲些什么哩?”

    大B:“面向对象设计模式描述的是软件设计,因此它是独立于编程语言的,但是面向对象设计模式的最终实现仍然要使用面向对象编程语言来表达,基于C#语言,但实际上它适用于支持.NET框架的所有.NET语言,如Visual Basic.NET、C++/CLI等。面向对象设计模式不像算法技巧,可以照搬照用,它是建立在对“面向对象”纯熟、深入的理解的基础上的经验性认识。掌握面向对象设计模式的前提是首先掌握“面向对象”!从编程语言直观了解面向对象,各种面向对象编程语言相互有别,但都能看到它们对面向对象三大机制的支持,即: “封装、继承、多态”

    – 封装,隐藏内部实现

    – 继承,复用现有代码

    – 多态,改写对象行为

    使用面向对象编程语言,可以推动程序员以面向对象的思维来思考软件设计结构,从而强化面向对象的编程范式。C#是一门支持面向对象编程的优秀语言,包括:各种级别的封装支持;单实现继承+多接口实现;抽象方法与虚方法重写。但OOPL并非面向对象的全部。通过面向对象编程语言(OOPL)认识到的面向对象,并不是面向对象的全部,甚至只是浅陋的面向对象。OOPL的三大机制“封装、继承、多态” 可以表达面向对象的所有概念,但这三大机制本身并没有刻画出面向对象的核心精神。换言之,既可以用这三大机制做出“好的面向对象设计”,也可以用这三大机制 做出“差的面向对象设计”。不是使用了面向对象的语言(例如C#),就实现了面向对象的设计与开发!因此我们不能依赖编程语言的面向对象机制,来掌握面向对象。”

    小A:“OOPL没有回答面向对象的根本性问题——我们为什么要使用面向对象?我们应该怎样使用三大机制来实现“好的面向对象”? 我们应该遵循什么样的面向对象原则?”

    大B:“任何一个严肃的面向对象程序员(例如C#程序员),都需要系统地学习面向对象的知识,单纯从编程语言上获得的面向对象知识,不能够胜任面向对象设计与开发。”

    从一个示例谈起

    示例场景:

    我们需要设计一个人事管理系统,其中的一个功能是对各种不同类型的员工,计算其当月的工资——不同类型的员工,拥有不同的薪金计算制度。

    结构化做法

    1.获得人事系统中所有可能的员工类型

    2.根据不同的员工类型所对应的不同的薪金制度,计算其工资

    enumEmployeeType { Engineer; Sales; Manager; … } // 计算工资程序 if ( type == EmployeeType.Engineer) { …… } else if (type == Employeetype.Sales) { …… }

    面向对象设计

    1.根据不同的员工类型设计不同的类,并使这些类继承自一个Employee抽象类,其中有一个抽象方法GetSalary。

    2.在各个不同的员工类中,根据自己的薪金制度,重写(override)GetSalary方法。

    abstract class Employee { … public abstract int GetSalary(); } class Engineer: Employee { … public override int GetSalary() { … } } class Sales: Employee { … public override int GetSalary() { … } } // 显示工资程序 Employee e = emFactory.GetEmployee(id); MessageBox.Show( e.GetSalary());

    示例场景:

    现在需求改变了……随着客户公司业务规模的拓展,又出现了更多类型的员工,比如钟点工、计件工……等等,这对人事管理系统提出了挑战——原有的程序必须改变。

    结构化做法,几乎所有涉及到员工类型的地方(当然包括“计算工资程序”)都需要做改变……这些代码都需要重新编译,重新部署…….面向对象做法,只需要在新的文件里增添新的员工类,让其继承自Employee抽象类,并重写GetSalary()方法,然后在EmployeeFactory.GetEmployee方法中根据相关条件,产生新的员工类型就可以了。其他地方(显示工资程序、Engineer类、Sales类等)则不需要做任何改变。重新认识面向对象,对于前面的例子,从宏观层面来看,面向对象的构建方式更能适应软件的变化,能将变化所带来的影响减为最小。从微观层面来看,面向对象的方式更强调各个类的“责任”,新增员工类型不会影响原来员工类型的实现代码——这更符合真实的世界,也更能控制变化所影响的范围,毕竟Engineer类不应该为新增的“钟点工”来买单……

    小A:“对象是什么?”

    大B:“从概念层面讲,对象是某种拥有责任的抽象。从规格层面讲,对象是一系列可以被其他对象使用的公共接口。从语言实现层面来看,对象封装了代码和数据。”

    小A:“有了这些认识之后,怎样才能设计“好的面向对象”?”

    大B:“遵循一定的面向对象设计原则熟悉一些典型的面向对象设计模式。从设计原则到设计模式,针对接口编程,而不是针对实现编程。客户无需知道所使用对象的特定类型,只需要知道对象拥有客户所期望的接口。优先使用对象组合,而不是类继承。类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。继承在某种程度上破坏了封装性,子类父类耦合度高;而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。封装变化点,使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。使用重构得到模式——设计模式的应用不宜先入为主,一上来就使用设计模式是对设计模式的最大误用。没有一步到位的设计模式。敏捷软件开发实践提倡的“Refactoring to Patterns ”是目前普遍公认的最好的使用设计模式的方法。”

    小A:“有没有更具体的原则?”

    大B:“有啊,我和你说说几条更具体的设计原则。单一职责原则(SRP):一个类应该仅有一个引起它变化的原因。开放封闭原则(OCP):类模块应该是可扩展的,但是不可修改(对扩展开放,对更改封闭)。

    Liskov 替换原则(LSP):子类必须能够替换它们的基类。依赖倒置原则(DIP):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于实现细节,实现细节应该依赖于抽象。接口隔离原则(ISP):不应该强迫客户程序依赖于它们不用的方法。”

    小A:“这样就好记多了。”

    大B:“设计模式描述了软件设计过程中某一类常见问题的一般性的解决方案。面向对象设计模式描述了面向对象设计过程中、特定场景下、类与相互通信的对象之间常见的组织关系。深刻理解面向对象是学好设计模式的基础,掌握一定的面向对象设计原则才能把握面向对象设计模式的精髓,从而实现灵活运用设计模式。”

    小A:“嗯,我记住了。”

    大B:“我再给你最后讲讲三大基本面向对象设计原则:1、针对接口编程,而不是针对实现编程。2、优先使用对象组合,而不是类继承。3、封装变化点,使用重构得到模式。敏捷软件开发实践提倡的“Refactoring to Patterns”是目前普遍公认的最好的使用设计模式的方法。”

    ■ Java常识

    大B:“师弟,我来给你介绍一些JAVA的常识,这样对你以后学习JAVA有帮助。”

    小A:“嘿嘿!好啊!”

    大B:“你最好就好好地记住它。”

    1、jave的方法中的所有变量都必须初始化之后才能使用,否则无法通过编译,提示no initialize。而在方法外的变量则会被自动初始化,可以在该“{}”中使用。所有的变量都仅在自己声明的“{}”中起作用。

    2、java的包分类:lang(构成语言的核心包)、awt(抽象图形工具包)、applet(已封装的applet小程序类)、io(基本的输入输出类)、net(与网络编程相关的类)、util(实用程序包,包括随机生成数字等)。

    3、java中摒弃了C/C++中的指针与存储管理等应用,从而提高了程序的健壮性,防止内存漏洞与存储器漏洞。

    4、用加号“+”进行字符串连接。

    5、java中的boolean类型不能与int类型进行转换。

    6、java的条件控制语句(if())中,括号中使用的是布尔表达式,而不是C/C++使用的数字值。因为java中布尔类型不能与数字类型转换,因而“if(x)”这种写法是错误的,应改为“if(x!=0)”。

    7、switch()语句中的条件必须是与int类型是扶植兼容的,byte、short、char类型可以被升级,不允许使用浮点和long表达式。

    8、注意label与break和continue等跳出语句的使用。break label/continue label,跳转到label出继续执行。

    9、java中数组是一组同种数据类型的集合,是一种对象,声明是不分配内存空间,只创建了该对象的一个引用,数组元素的实际内存空间是通过new()方法进行初始化而动态分配的。数组声明的两种方法:char []s 或者 char s[]。

    10、声明可以不指出数组的大小。

    11、java支持多维数组,不但支持矩阵型数组,而且支持非矩阵型数组。

    12、java中具有数组拷贝函数(System.arraycopy())。

    13、子类从超类(父类)继承所有方法和变量;但子类不从超类继承构造函数;包含构造函数的两个办法是a、使用缺省构造函数,b、写一个或多个显式构造函数。

    14、多态性是个运行时问题,与重载相反,重载是一个编译时问题。

    15、关键字super 可被用来引用该类中的超类。它被用来引用超类的成员变量或方法,可以使用super.method()的格式来调用。

    16、instanceof的使用,在对象强制类型转换时常常使用。

    17、在一个类中可以通过参数个数/参数类型不同,从而构造重载函数。在子类与父类之间,可以在子类中定义与父类具有一样函数名称、参数个数、参数类型、返回类型的函数,从而达到函数覆盖的作用。

    18、通过子类的数据初始化父类的成员变量,可以在子类的构造函数中使用super()来调用父类的构造函数初始化父类中的成员变量。

    19、import语句必须先于所有类的声明。import 语句被用来将其它包中的类带到当前名空间。当前包,不管是显式的还是隐含的,总是当前名空间的一部分。

    20、类中的static变量可以被该类的所有实例共享,如果声明为private,只有该类的实例才能访问,如果声明为public,可以不通过该类的实例,直接在类体外通过类名就可以访问。

    21、类中的static方法可以不通过该类的实例,直接在类体外通过类名就可以访问,static 方法不能访问与它本身的参数以及static 变量分离的任何变量。访问非静态变量的尝试会引起编译错误。没有this 值。

    22、静态方法不能被覆盖成非静态。

    23、static block。“static {}”。

    24、final 类不能被分成子类;final 方法不能被覆盖;final 变量是常数。被标记为static 或private 的方法被自动地final,因为动态联编在上述两种情况下都不能应用。如果变量被标记为final,其结果是使它成为常数。想改变final 变量的值会导致一个编译错误。

    25、声明方法的存在而不去实现它的类被叫做抽象类。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类。

    26、接口是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。

    27、内部类不能声明任何static 成员;只有顶层类可以声明static 成员。

    28、finally 语句定义一个总是执行的代码块,而不考虑异常是否被捕获。如果终止程序的System.exit()方法在保护码内被执行,那么,这是finally 语句不被执行的唯一情况。这就暗示,控制流程能偏离正常执行顺序,比如,如果一个return 语句被嵌入try 块内的代码中,那么,finally 块中的代码应在return 前执行。

    29、java中类的成员变量是在对象实例化之后才分配内存空间,类变量则在类加载的时候分配空间,该类以及该类的实例对象都共享类变量。类的成员方法是在类的第一个对象实例化的时候才分配入口地址的,当再创建对象时,不再分配入口地址,就是说,所有对象共享一个入口地址,而类方法则是在类被加载到内存的时候分配入口地址的,因而可以被类以及类的实例所调用。

    30、this指向本类,可以通过成员访问运算符"."访问类的成员变量或方法,但this不能出现在类方法中,因为类方法是可以直接通过类名调用。

    31、a、public b、protected c、友好的(无修饰符) d、private ,private在子类中无法继承父类的成员变量与成员方法,在同包中,子类可继承父类的a b c类型的变量与方法,不同包中,子类只能继承a b类型的变量与方法。

    32、当子类与父类之间如果存在同名的成员变量时,则父类的成员变量被隐藏,当子类与父类存在返回类型、参数类型以及个数都相同的函数时,父类的成员方法被隐藏,因此可以通过重写子类的成员函数而将父类的状态和行为改变为自身的状态与行为。但子类重写方法时,访问权限不能低于父类的修饰符。

    33、如果一个方法被修饰为final方法,则这个方法不能被重写,如果一个成员变量被修饰为final的,就是常量。

    34、对象的上转型对象,A是B的父类,A a=new B(),则a是b的上转型对象,上转型对象不能操作子类新增的成员变量,但可以操作子类继承或重写的成员变量,也可以使用子类继承的或重写的方法,不可以将父类创建的对象的引用赋值给子类声明的对象。

    35、接口用interface声明,用于java多继承,接口体中包含常量定义和方法定义两部分,接口体中只进行方法的声明,不许提供方法的实现,所以方法的定义没有方法体,且用分号。

    36、如果一个类使用了某个接口,那么这个类必须实现该接口的所有方法。如果一个类声明实现一个接口,但没有实现接口中的所有方法,那么这个类必须是abstract类。

    37、接口回调:可以把实现某一接口的类创建的对象的引用赋值给该接口声明的接口变量中,那么该接口变量就可以调用被类实现的接口中的方法。当接口变量调用被类实现的接口的方法时,就是通知相应的对象调用接口的方法。

    38、string类型变基本类型,可以用public int Integer.parseint(string s) 其他基本类型相似,基本类型变string类型,可以用string.valueof(int/char/float/double),对象类型用date.toString()转换。

    39、stringtokenizer类可以实现字符串的分析,stringtokenizer objectname=new stringtokenizer(s," 分隔符列表 用空格间隔"),重要方法:counttoken()、nexttoken()、hasmoretokens()。

    40、charactor类重要方法:isDight(char) isLetter(char) isLetterOrDight(char) isLowerCase(char) isUpperCase(char) toLowerCase(char) toUpperCase(char) isSpacechar(char) 。

    41、将字符串转变成字符数组,可以使用string类的String.toCharArray()方法,char a[]=String.toCharArra()

    42、string(char[],int offset,int length) 返回一个string对象,getChars(int start,int end,char c[],int offset) 功能:字符串变字符数组,由string对象使用。

上一章 目录 下一章