OO + Design Patterns

本文主要干两件事情,推荐几本书,以及安利一点莫名其妙的笔记。

 

前面提到我被自己写的词法分析程序深深地伤害了,因为它竟然如此之丑,于是决心学习设计模式。

貌似一般很少能找到某个领域,是有一本公认的最权威的参考书籍的,然而设计模式就是这样的“异类”。GoF的<Design Patterns>简直是该领域的至尊。但是我要推另一本<Head First 设计模式>,适合完全没有什么OO经验的人去读。前者基于c++和少量smalltalk,后者基于Java。个人感受是第一本更像是一本字典,要理解各种模式的内涵可能会比较吃力,第二本则是绝对的神书,轻松爆笑不管多困都看的下,并且里面的例子更基础更具体,更适合入门。

因为最近刚刚开始学java的OO(Object Oriented),所以<Head First 设计模式>给了我很多的灵感。读到有不懂的地方我就去翻<Head First Java>,一周过去涨了很高的姿势。

 

有一些关于OO的想法要分享。

关于继承。以前不懂得继承是为了什么,做作业为了花样炫技就强行继承。后来这学期老师说继承不为什么,也只不过是为了复用一些代码,让你少码一点(当时我暗想:这理由好蠢)。后来过了好久,直到我在看设计模式的时候才重新想到这句话。我觉得其实复用的意义更在于它更支持“维护”,说的土一点是修改的时候你不用改得很崩溃。这是个很关键的点。

在c面向过程编程时也提到过类似的概念,比如要代码重构包装出一些子函数来,就是为了在出错时便于修改。打ACM的时候这也是很重要的,比如那些大模拟题,如果相同的代码段只是不断地ctrl v而不装在一个子函数里,当发现出错的时候一个一个改是要崩的,思路很容易就飞了。

继承所支持的“可维护”包括但不局限于此。但我感觉继承并不是OO的主角。

 

可能是因为刚学所以正热乎,我感觉多态才是OO的精髓,当然多态离不开继承等概念的配合,只是说多态这一概念本身最能体现OO的强大。

 

 

 

OO的基础就是抽象、封装、多态、继承。

OO原则:

封装变化(把可能发生变化的地方封装起来,如各种需求变更)。找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。建立可维护的OO系统,要诀就在于随时想到系统以后可能需要的变化以及应对变化的原则。

多用组合,少用继承。使用组合来建立系统具有更大的弹性,不仅可将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。对应“策略模式”

组合(composition)和委托(delegation)可以在运行时具有继承行为的效果。利用一般继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地扩展。我们可以利用此技巧把多个新职责,甚至是设计超类时没有想到的职责加在对象身上。而且,可以不用修改原来的代码。通过动态地组合对象,可以写新的代码、添加新的功能,而无须修改现有代码。既然没有改变现有代码,那么引进BUG或产生意外副作用的机会将大幅度减少

针对接口编程,不针对实现编程(“接口”是一个概念,并不特指java里面的interface,因为有时类也可以作“接口”。另外因为JAVA不支持多重继承,所以它要变相靠“接口”来实现;我看GoF那本书中,C++中貌似就是直接用类来实现“接口”的)

为交互对象之间的松耦合设计而努力。这里针对有“交互”行为的对象间,对应“观察者模式”。

类应该对扩展开放,对修改关闭。对应“装饰者模式”。这一规则一般不可能被百分之百遵守,但是这个意识会帮助你权衡要遇到什么样的困境才值得去打破原则。

依赖抽象,而不要依赖具体类。“依赖倒置原则”(Dependency Inversion Principle)。大概是强调一个多态,即把抽象的方法放到抽象类中,而不是在基类中分类实现子类的函数(否则每次增删一个子类都得改动抽象类的函数实现)。

OO模式

策略模式:定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

观察者模式:在对象之间定义一对多的以来,这样一来,当一个对象改变状态,依赖它的对象都会收到通知并自动更新。

装饰者模式:动态地将责任附加到对象身上。想要扩展功能,装饰者模式提供了有别于继承的另一种选择。

工厂方法模式:定义了一个创建对象的接口(比如new的过程),但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。(大概是通过一个接口来控制一批接口生产出一个系列的产品。对于这个理解我暂时不敢确定)

后面还有适配器模式、模板模式、组合模式和MVC很著名,不知道还有没有时间好好看。

 

 

大概只学到这里,最近这一周的code世界观天翻地覆,以前以为懂得了那些基本概念就是懂了C++,现在想来真是管中窥豹。

毕竟纸上得来终觉浅,最近要用c++写一个电商,我还得花点功夫看看java的这些奇技淫巧在c++里是怎么做的(看来不得不啃GoF那本神书了)。这学期压力太大,没有太多自由学习的时间,先分享到这里,等电商写到告一段落再来更新一些心得。

0 等你来赞

java的“值传递” + 异常抛出

方法调用(call by)(相当于c++中的“函数调用”),根据参数传递的情况分为值调用( call by reference ) 和引用调用( call by value ) 。

Java的对象参数传递仍然是值调用,这里强调的是“对象”的参数传递。

Java中除了primitive type以外一切都是对象。primitive type只有以下几种:QQ图片20151027211946

注意,就连我们熟知的数组或者String也都是对象。Java没有指针,所以对primitive type实现著名的swap函数就显得有些困难,一种可行的做法是先特地把它们包装成对象。

 

Java中对象变量名实际上代表的是对象在堆中的地址(专业术语叫做对象引用 )。在Java方法调用的时候,参数传递的是对象的引用。重要的是,形参和实参所占的内存地址并不一样,形参中的内容只是实参中存储的对象引用的一份拷贝。这正是“值传递”的表现形式。

通过传递对象引用,形参和实参指向了同一个地址。所以修改形参的成员变量,相当于修改了实参的成员变量

 

试着对一个对象实现swap。

交换失败。前面说到形参和实参都是对象引用,本质上是引用,那么上面那段代码只是把形参的两个引用交换了一下(相当于c++中,只是交换了指针,而不是交换指针所指向的内容),对实参的情况毫无影响。下面用图给出直观的描述:QQ图片20151027213817

由此可进一步理解,为什么说JAVA本质上仍然是值传递。

 

下面讲讲异常抛出。

若要实现以下功能:

QQ图片20151027214740

第一种方法,使用异常抛出:

第二种写法,不使用异常抛出:

显然第二种写法更简明清晰,也是我在学异常抛出之前做错误处理一贯的方式。这难道说明异常抛出反而是累赘吗?不是的,这实际上是一个失败的例子。

可以看到,在第一种写法中,既没有用到JAVA的“重新抛出异常”(就是在这个类的方法内部不处理异常,而是交给调用它的那个类的方法去处理,从里面一直往外抛, 简单说就是谁调用谁处理异常,我不管),而且也只有一层代码,事实上这种情况下很少强行使用异常抛出。

异常是有层级关系的错误处理机制,是专门抽象出来的一个用来处理错误的语法。子函数在出现异常的时候,可以自己处理掉,也可以抛给调用者处理,还可以自己处理一部分调用者处理一部分。处理异常的代码可以非常清晰地与逻辑主体隔绝开来。并且,Exception被捕获的话就不会终止程序,相比之下error则会使之宕掉。

可以设想这么一个场景,你在某个地方调用一个函数,这个函数的作用,是接受学生成绩并做相应的处理。但是,如题所说,输入的学生成绩可能是错误的。那么你在调用的地方怎么获取这个信息呢?两种办法,一种是用返回值表征状态,一种是用异常处理机制。此例中返回值可以有两个枚举值,0表示正常,1表示成绩不在0到100的闭区间内。

然而对于更一般的情况来说,返回值能表示的信息很有限,也就是,只能表示错误状态,却难以将更多的错误信息表达出来。比如如果接受的是一个数组,那么你可能还需要知道脱离0到100范围的数的下标是多少。出现别的错误时,你可能又想获取别的信息,这种情况下返回值就很难做到了。

所以后来我把代码改成了这个样子:

这样才不会背离“异常处理”的原则太远。

 

初初接触异常抛出,除了明白基本概念,我感觉自己还需要知道什么时候该用以及该怎么用,在网上找到了相关有营养的建议。

1.只在必要使用异常的地方才使用异常,不要用异常去控制程序的流程。谨慎地使用异常,异常捕获的代价非常高昂,异常使用过多会严重影响程序的性能。

2.切忌使用空catch块。千万不要使用空的catch块,空的catch块意味着你在程序中隐藏了错误和异常,并且很可能导致程序出现不可控的执行结果。如果你非常肯定捕获到的异常不会以任何方式对程序造成影响,最好用Log日志将该异常进行记录,以便日后方便更新和维护。

3.不要将提供给用户看的信息放在异常信息里。因为业务逻辑跟用户界面要解耦合,展示给用户的错误提示信息若写死在里面了,万一要改成英文版的,不是又得改下程序;万一你觉得这个promt不合适,想换句好听点的话,又得改下程序;万一你要支持多语言,你还得在代码里面敲进N多种语言,再用一个swtich去拿…

其中一种做法是拿一个 txt 文件或者 ini 文件之类的,把错误编码跟错误提示文本存起来。然后主程序再跑去文件里面根据错误编码找到具体的对应提示文本,这就是一种配置文件。如下图:

QQ图片20151028224530

4.避免多次在日志信息中记录同一个异常。只在异常最开始发生的地方进行日志信息记录。很多情况下异常都是层层向上跑出的,如果在每次向上抛出的时候,都Log到日志系统中,则会导致无从查找异常发生的根源。

5.异常处理尽量放在高层进行。尽量将异常统一抛给上层调用者,由上层调用者统一之时如何进行处理。如果在每个出现异常的地方都直接进行处理,会导致程序异常处理流程混乱,不利于后期维护和异常错误排查。由上层统一进行处理会使得整个程序的流程清晰易懂。

6.在finally中释放资源。如果有使用文件读取、网络操作以及数据库操作等,记得在finally中释放资源。这样不仅会使得程序占用更少的资源,也会避免不必要的由于资源未释放而发生的异常情况。

 

reference:

Java的8种基本数据类型(primitive Type)

【解惑】Java方法参数是引用调用还是值调用?

JAVA异常重新抛出

Java异常处理和设计

以及,与@erueat的聊天

0 等你来赞

linux + github + notes + habit

大家可以看到我上一篇文章加了密码,事情是这样的,听我解释。这是我们课内的作业,贴上来的东西分分钟就是我自己的实验报告,并且deadline还远着。然后我是很相信我同学的“正确使用搜索引擎”的能力的,并且我之前装了一个插件可以帮助google搜索抓取我的页面使之更易被检索到,所以,这只是为了避免不必要的悲剧发生,等到deadline过去以后自然会解放权限。密码是,我的高中校名的拼音。如果不知道是众多校名称呼的哪一个,多试几次就好了,毕竟我只读过一个高中。

 

孟祥武孟爷爷老是说:“你们不能只用一个操作系统啊,除了windows,还应该多试试别的。如果只用一个操作系统,你就会觉得windows就是操作系统,操作系统就是windows——这是不对的。”

类似的,孟爷爷还有“不能只学一门语言啊”,“不能只用一种浏览器啊”的谆谆教导。

 

于是下定决心在自己电脑上装了个虚拟机,先装了ubuntu kylin 15.04,但是觉得不流畅,网上评价也很一般,于是转ubuntu kylin 14.04,使用至今感觉不错。之所以不用原版ubuntu,是因为麒麟版本对于中文更加友好,并且跟原版也没什么差别。网上有人在撕麒麟是不是国产的,其实明显它并不是国产,说是联合开发,实际上kylin只是ubuntu一个子分支,用起来不用心存芥蒂。

顺手贴个很著名的很洗脑的文章,《完全用Linux工作》。书架上还有一本《鸟哥私房菜》嗷嗷待操。

 

转型的过程总体还算顺利,装codeblocks写c++,然后g++和gcc系统是没有自带的,所以顺手装了好多东西。换语言的时候出了问题,出了一个“源引用”错误(提示信息: “ubuntu kylin It is impossible to install or remove any software”),需要打开某个属性栏然后勾选或者是删除某个选项才行,具体的记不清了(翻了一下历史记录没找到),似乎没什么遇到跟我一样的情况;再就是选择简体中文的时候,如下图

QQ图片20151026204344

一开始那个“汉语(中国)”的选项是在下面,而且是浅色的,常识告诉我它仍不能选。google了以后发现正确的打开方式是:点住他、拖动到语言栏的最上方。太魔性,别人不跟我说的话真是一辈子都找不出来毛病。

eclipse至今没有建起来,原因是我搜的那个办法有一步是在shell内使用vim修改一个源文件,当场我就萌比了。还有github理论上是可以直接用shell去push和pull的然而也还没学会(因为暂时还不知道怎么用shell往git直接提交,所以我是开了github的网页然后直接粘上去的)。shell命令也基本不懂,而这才是linux的精髓。以上,列入TODOLIST了。

linux部分大概就这些,使用了半个月,用户体验非常好。因为里面没有qq没有qq音乐也没有全家桶们没有弹窗们,没有任何可以打扰我的东西,做事效率高一点,并且是不是能骗取路人们的仰慕,无形装逼。

 

之前在windows上配好了github没用过,这次在linux上也配了一下(但是还不懂怎么push和pull),然后写作业的时候就真正用github做版本管理了。放几张效果图

QQ图片20151026205638

github的界面简洁明了

QQ图片20151026205529

上图为各个版本

QQ图片20151026205744

版本管理非常方便,点开以后可以看到当前版本与上一版本相比做了什么修改,红色行为原来的写法,其中深红色(此图没有)是具体的修改或删除的地方;绿色行为该版本的修改,深绿色标明了修改或添加的地方。没有改动的代码段自动折叠。最左有新旧两版本的行号对照。支持多人管理同一文档,每一个改动都会标明修改者id、修改内容的题目、修改内容的简要说明等关键信息。

实在很强大,难怪田队年初的时候决心在ACM校队内普及git的用法。今年北邮的校赛的出题验题工作就都是在git上完成的。

总之,github是一个强大的工具。

 

最后做一点个人的读书笔记。

关于如何写注释。

1.不要为那些从代码本身就能快速推断的事实写注释。eg:构造函数就不用再写 //constructor了

2.不要给不好的名字加注释,即注释不应用于粉饰不好的名字。一个好的名字比一个好的注释更重要,因为在任何用到这个函数的地方都能看见它。好代码 > 坏代码 + 好注释。

3.不要写废话。如果一定要解释一个函数,请尝试给出它的逻辑细节。

4.记录你的思想,克服“作者心理阻值”。例如,假如你正在写一个函数,然后心想:“哦,天啊,如果一旦这东西在列表中有重复的话会变得很难处理的”,不如把它写下来:

It does help. 并且最后review的时候你可以尝试改一下这句话,把具体问题解释得更清楚一点。

5.在代码中加入注释记录你对代码有价值的见解。例如,

6.为代码中的瑕疵写注释。

尝试使用标记。书里给出一种流行的写法。“TODO”:我还没有处理的事情。“FIXME”:已知的无法运行的代码。“HACK”:对于一个问题不得不采用的比较粗糙的解决方案。“XXX”:危险!这里有重要的问题。

7.当定义常量时,通常背后有一个关于它是什么或者为什么它是这个值的“故事”。

这可以让读代码的人有了调整这个值的指南。经验告诉我们,很多常量可以通过加注释得到改进,而这只不过是匆匆记下你在决定这个常量值时的想法而已。

8.可能需要一些高级别的注释,比如文件级别的:

——以上摘自DB & TF 的<The Art of Readable Code>

 

45个习惯。

7.懂得丢弃。敏捷的根本之一就是拥抱变化。在学习一门新技术的时候,多问问自己,是否把太多旧的态度和方法用在了新技术上。例如,学习一门新的编程语言时,应该使用推荐的集成开发环境,而不是你过去开发时用的工具插件。转换的时候,完全不要使用过去的语言开发工具。只有更少被旧习惯牵绊,才更容易养成新习惯。对旧习惯不是完全摒弃,如果环境合适,可以举一反三地灵活应用。

9.把我开发节奏。以固定、有规律的长度运行迭代。

14.提早集成,频繁集成。在产品的开发过程中,集成是一个主要的风险区域。让你的子系统不停地增长,不去做系统集成,就等于一步一步把自己置于越来越大的风险中。而若在早期就进行集成,你会更清晰地看到子系统之间的交互和影响,就可以估算它们之间的通信和共享的数据信息。如果落实得好,集成就不再会是一个繁重的任务。它只是编写代码周期中的一部分,集成时产生的问题都会是小问题并且容易解决

——以上摘自VS & AH 的 <Practices of an Agile Developer>

 

0 等你来赞

linux系统下的fork函数,wait函数和exit函数

fork()函数通过系统调用创建一个新进程(子进程),系统为它分配资源,并把其父进程的当前的情况复制到子进程中,从整体上看相当于有两份数据(所以简单改动一个进程内的变量的数据完全不会影响到另一个进程)。

fork()函数调用一次,返回两次。看下面一段代码

这里fork()函数只出现了一次,即只被调用了一次,但此时它已经创建了另一个进程,所以共两个进程。可以理解为,父进程调用了fork()函数,创建子进程以后,fork()返回了一个值给父进程,同时还返回了一个值给新创建的子进程。

fork()函数有三种返回值:

1)在父进程中,fork()返回新创建子进程的进程ID;

2)在子进程中,fork返回0;

3)如果出现错误,fork返回一个负值;

上面提到fork()函数只复制所谓“当前的情况”,可以理解为“此时已经执行过的语句所呈现的情况”,所以时间上在fork()以后才执行的语句会在两个进程中执行。若有这样的语句:

则执行完后系统将会有4个进程。具体的,一开始只有进程1,执行完第一个fork()以后进程1创建了进程2,fork()分别给他们返回了一个值。之后,对于进程1和进程2,都是从得到一个返回值以后继续运行的,也就是分别调用了一次fork()。这样进程1就创建了进程3,进程2创建了进程4(此处数字不表示先后创建顺序,见下一段)。面试题中可能会有这样的考点,给出的例子是最简单最基本的情形。

 

创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

如果要控制fork()以后,等子进程执行完并返回了,再执行父进程,可以调用wait()函数。讲wait()函数之前,先简单讲一个概念:僵尸进程

在UNIX系统下,若一个子进程结束了,但它的父进程没有等待(调用wait() /waitpid())它,那它将会变成一个僵尸进程。僵尸进程的危害在于:若不及时释放,它会一直占用着它的进程号,而系统所能使用的进程号是有限的,所以大量的僵尸进程可能导致系统无法创建新进程。

讲回wait()函数。进程一旦调用了wait(),就立即阻塞自己,并由wait自动分析是否有某个当前进程的子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

 

exit()函数可用于终结一个进程。exit的返回值是返回给操作系统的,但如果是多进程,则是返回给父进程的。

 

写了一个简单的小程序:创建子进程,在子进程中计算斐波那契数列并把父进程挂起。

 

 

文中提到的相关概念只做了很粗浅的解释,深入学习的话请移步下面链接。

reference:

fork()函数

僵尸进程

wait()函数

exit和return的区别

 

 

0 等你来赞

博客重建

之前用BAE3+wordpress建的站有些页面打不开 似乎是数据库传得不好

于是把整个数据库删掉,然后选了一个更老的版本重新上传、安装,再更新,终于弄好了:)

调整了半天调出了自己喜欢的配色,摸索着装上了各种插件,试验了各种功能,总算把工作做得七七八八了~

开心开心~~~

 

域名和服务器均在gegehost购买

建站参考资料:

戈戈主机客户中心自助购买主机教程图解

http://www.gegehost.com/2010/05/21/buy-gegehost/

Gegehost域名注册与管理教程

http://www.gegehost.com/2011/02/28/register-and-manage-domain-with-gegehost/

cPanel安装WordPress中文教程

http://www.gegehost.com/2009/03/16/cpanel-wordpress-chinese/

1 个人赞过辣