php面向对象编程程题

导读:【练习题】01.类的成员变量:,【练习题】02.类的成员变量:,最后在测试类Vehicle中的main()中实例化一个交通工具对象,【练习题】03.类的成员变量与方法、构造方法,以方便创建对象时初始化成员变量,this.second=//创建对象时初始化成员变量},【练习题】01.类的成员变量:猜数字游戏:一个类A有一个成员变量v,有一个初值100。定义一个类,对A类的成员变量v【练习题】01.类的成员变量: 猜数字游戏:一个类A有一个成员变量v,有一个初值100。定义一个类,对A类的成员变量v进行猜。如果大了则提示大了,小了则提示小了。等于则提示猜测成功。 import java.util.*; class A{
int v = 100;
} } public class b01 {
public static void main(String args[]){
A a = new A();
Scanner intput = new Scanner(System.in);
int intputvalue = intput.nextlnt();
if(intputvalue>a.v)
System.out.println(\你的输入大于这个值\ if(intputvalue<a.v)
System.out.println(\你的输入小于这个值\ if(intputvalue==a.v)
System.out.println(\猜测成功\ } }
【练习题】02.类的成员变量: 请定义一个交通工具(Vehicle)的类,其中有: 属性:速度(speed),体积(size)等等 方法:移动(move()),设置速度(setSpeed(int speed)),加速speedUp(),减速speedDown()等等. 最后在测试类Vehicle中的main()中实例化一个交通工具对象,并通过方法给它初始化speed,size的值,并且通过打印出来。另外,调用加速,减速的方法对速度进行改变。 class Vehicle {
void move(){
void setSpeed(int speed){
this.speed =
void speedUp(){
speed = speed+1;
this.speed=
void speedDown(){
speed = speed-1;
this.speed=
} } public class b01 { public static void main(String args[]){
Vehicle a = new Vehicle();
a.setSpeed(55);
a.size = 80;
a.speedUp();
System.out.println(\
} } 【练习题】03.类的成员变量与方法、构造方法 在程序中,经常要对时间进行操作,但是并没有时间类型的数据。那么,我们可以自己实现一个时间类,来满足程序中的需要。 定义名为MyTime的类,其中应有三个整型成员:时(hour),分(minute),秒(second),为了保证数据的安全性,这三个成员变量应声明为私有。 为MyTime类定义构造方法,以方便创建对象时初始化成员变量。 再定义diaplay方法,用于将时间信息打印出来。
为MyTime类添加以下方法: addSecond(int sec) addMinute(int min) addHour(int hou) subSecond(int sec) subMinute(int min) subHour(int hou) 分别对时、分、秒进行加减运算。 class MyTime {
public MyTime(int hour,int minute,int second){
this.hour =
this.minute =
this.second =//创建对象时初始化成员变量
void diaplay(){ 包含总结汇报、IT计算机、办公文档、资格考试、教程攻略、外语学习、计划方案以及Java面向对象编程练习题答案等内容。
相关内容搜索Java面向对象编程练习题_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
Java面向对象编程练习题
&&适合java初学者的一些经典题目及答案
你可能喜欢Java面向对象编程练习题-360文档中心
360文档中心免费免积分下载,各行业
知识、技术、信息等word文档下载网站
Java面向对象编程练习题
适合java初学者的一些经典题目及答案【练习题】01.类的成员变量? 猜数字游戏?一个类A有一个成员变量v?有一个初值100。定义一个类?对A类的成员变量v进行猜。如果大了则提示大了?小了则提示小了。等于则提示猜测成功。
import java.util.*;public class lianxi{public static void main(String[] dsa){A a=new A();Scanner input=new Scanner(System.in);while (1==1){System.out.println("请猜测数字");int i=input.nextInt();if (i<a.v){System.out.println("小了");}else if (i>a.v){System.out.println("大了");}else{System.out.println("恭喜你猜对了!");}}}}class A{public int v=100;}【练习题】02.类的成员变量? 请定义一个交通工具(Vehicle)的类?其中有: 属性?速度(speed)?体积(size)等等 方法?移动(move())?设置速度(setSpeed(int speed))?加速speedUp(),减速speedDown()等等. 最后在测试类Vehicle中的main()中实例化一个交通工具对象?并通过方法给它初始化speed,size的值?并且通过打印出来。另外?调用加速?减速的方法对速度进行改变。
public class Vehicle{private Spublic void move(){System.out.println("i'm moving");}public void setSpeed(int speed){System.out.println("now i'm running with"+speed+"per hour");}public void speedUp(){Vehicle v=new Vehicle();v.setSpeed(1000);}public void speedDown(){Vehicle v=new Vehicle();v.setSpeed(20);}public static void main(String[] dsa){Vehicle v=new Vehicle();v.speed=100;v.size="50/m^3";System.out.println("the initial speed is"+v.speed+"and my size is "+v.size);v.speedUp();v.speedDown();}}【练习题】03.类的成员变量与方法、构造方法 在程序中?经常要对时间进行操作?但是并没有时间类型的数据。那么?我们可以自己实现一个时间类?来满足程序中的需要。 定义名为MyTime的类?其中应有三个整型成员?时?hour??分?minute??秒?second??为了保证数据的安全性?这三个成员变量应声明为私有。 为MyTime类定义构造方法?以方便创建对象时初始化成员变量。 再定义diaplay方法?用于将时间信息打印出来。
为MyTime类添加以下方法? addSecond(int sec) addMinute(int min) addHour(int hou) subSecond(int sec) subMinute(int min) subHour(int hou) 分别对时、分、秒进行加减运算。
public class MyTime{public MyTime(int x,int y,int z){this.hour=x;this.minute=y;this.second=z;}public void display(){System.out.println("the time is "+this.hour+":"+this.minute+":"+this.second);}public void addHour(int hou){this.hour=this.hour+}public void addSecond(int sec){thi
免费下载该文档:
Java面向对象编程练习题的相关文档搜索天极传媒:天极网全国分站
您现在的位置:
& >&吐槽:面向对象编程从骨子里就有问题
名家吐槽:面向对象编程从骨子里就有问题博客 10:12
  面向对象是90年代“结构化编程”的万金油,风靡一时,但很难说它就是那些用它开发出来的东西的“终极”编程模式。
  “面向对象编程是一个极其糟糕的主意,只有硅谷里的人能干出这种事情。” ― Edsger Dijkstra(图灵奖获得者)
  “面向对象设计是用罗马数字做计算。” ― Rob Pike(Go语言之父)
  “‘面向对象’这个词包含很多意思。有一半是显而易见的,而另一半是错误的。”― Paul Graham(美国互联网界如日中天的教父级人物)
  “实现上的继承就跟过度使用goto语句一样,使程序拧巴和脆弱。结果就是,面向对象系统通常遭受复杂和缺乏复用的痛苦。” ― John Ousterhout( Tcl and Tk 的创始人) Scripting, IEEE Computer, March 1998
  “90%的这些胡说八道都称现在它很流行,非要往我的代码里搓揉进面向对象的石粒。” ― kfx
  “有时,优雅的实现只需要一个函数。不是一个方法。不是一个类,不是一个框架。只是一个方法。” ― John Carmack(id Software的创始人、第一人称射击游戏之父)
  “面向对象编程语言的问题在于,它总是附带着所有它需要的隐含环境。你想要一个香蕉,但得到的却是一个大猩猩拿着香蕉,而其还有整个丛林。” ― Joe Armstrong(Erlang语言发明人)
  “我一度曾经迷恋上了面向对象编程。现在我发现自己更倾向于认为面向对象是一个阴谋,企图毁掉我们的编程乐趣。” ― Eric Allman(sendmail的创造者)
  面向对象是90年代“结构化编程”的万金油,风靡一时,但很难说它就是那些用它开发出来的东西的“终极”编程模式。
  并且,至少它的这种最流行的形式可能我带来极大的危害,极度的增加复杂性。
  继承带来的危害远超过好处。这“代码复用”的令人质疑的幌子下,我们的环境中被愚蠢的增添了大量的毫无用处的复杂性,使得必须引入大量的语法上的简写来让这混乱得到最低限度的管理。
  C 来自 索尼公司Tony Albrecht
  英文原文:
  译文链接:http://www.aqee.net/object-oriented-programming-is-inherently-harmful/
(作者:IT外刊评论责任编辑:Sunny)
天极新媒体&最酷科技资讯扫码赢大奖
* 网友发言均非本站立场,本站不在评论栏推荐任何网店、经销商,谨防上当受骗!
办公软件IT新闻整机弊端是,没有人还记得面向对象原本要解决的问题是什么。&br&&br&1、面向对象原本要解决什么(或者说有什么优良特性)&br&似乎很简单,但实际又很不简单:面向对象三要素&b&封装、继承、多态&/b&&br&&br&(&b&警告&/b&:事实上,从业界如此总结出这面向对象三要素的一刹那开始,就已经开始犯错了!)。&br&&br&&b&封装&/b&:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,或者叫&b&接口&/b&。&br&&br&有了封装,就可以明确区分&b&内外&/b&,使得类实现者可以修改封装&b&内&/b&的东西而不影响&b&外&/b&部调用者;而外部调用者也可以知道自己不可以碰哪里。这就提供一个良好的合作基础——或者说,只要&b&接口&/b&这个基础约定不变,则代码改变不足为虑。&br&&br&&br&&br&&b&继承+多态&/b&:继承和多态必须一起说。一旦割裂,就说明理解上已经误入歧途了。&br&&br&先说&b&继承&/b&:继承同时具有两种含义:其一是继承基类的方法,并做出自己的改变和/或扩展——号称解决了代码重用问题;其二是&b&声明&/b&某个子类&b&兼容&/b&于某基类(或者说,接口上完全&b&兼容&/b&于基类),外部调用者可无需关注其差别(内部机制会自动把请求派发[dispatch]到合适的逻辑)。&br&&br&再说&b&多态&/b&:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。&br&&br&很显然,多态实际上是&b&依附于继承的两种含义&/b&的:“改变”和“扩展”本身就意味着必须有机制去自动选用你改变/扩展过的版本,故无多态,则两种含义就不可能实现。&br&&br&所以,多态实质上是继承的实现细节;那么让多态与封装、继承这两个概念并列,显然是&b&不符合逻辑&/b&的。不假思索的就把它们当作可并列概念使用的人,显然是从一开始就被误导了——正是这种误导,使得大多数人把注意力过多集中在多态这个战术层面的问题上,甚至达到近乎恶意利用的程度;同时却忽略了战略层面的问题,这就致使软件很容易被他们设计成一滩稀屎(后面会详细谈论这个)。&br&&br&&br&实践中,继承的第一种含义(实现继承)意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。&br&&br&继承的第二种含义非常重要。它又叫“接口继承”。&br&&b&接口继承&/b&实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做&b&归一化&/b&。&br&&br&&br&&b&归一化&/b&使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。&br&&br&&b&归一化&/b&的实例:&br&a、一切对象都可以序列化/toString&br&b、一切UI对象都是个window,都可以响应窗口事件。&br&&br&——必须注意,是一切(符合xx条件的)对象皆可以做什么,而不是“一切皆对象”。后者毫无意义(从信息论角度上说,一切皆xx蕴含的信息量为0)。&br&&br&&br&显然,&b&归一化&/b&可以大大简化&strong&使用者&/strong&的处理逻辑:&br&这和带兵打仗是类似的,班长需要知道每个战士的姓名/性格/特长,否则就不知道该派谁去对付对面山坡上的狙击手;而连长呢,只需知道自己手下哪个班/排擅长什么就行了,然后安排他们各自去守一段战线;到了师长/军长那里,他更关注战场形势的转变及预期……没有这种层层简化、而是必须直接指挥到每个人的话,累死军长都没法指挥哪怕只是一场形势明朗的冲突——光一个个打完电话就能把他累成哑巴。&br&&br&反过来也对:军长压根就不应该去干涉某个步兵班里、几个大头兵之间的战术配合;这不仅耽误他行使身为军长的职责,也会干扰士兵们长久以来养成的默契。他的职责是让合适的部队在合适的时机出现在合适的战场,而不是一天到晚对着几个小兵指手画脚、弄的他们无所适从。&br&&br&约束各单位履行各自的职责、禁止它们越级胡乱指挥,这就是&b&封装&/b&。&br&&br&正是通过封装和归一化,我们才可以做到“如果一个师解决不了问题,那就再调两个师”“如果单凭陆军解决不了问题,那就让空军也过来”——这种灵活性显然是从良好的部队编制得来的。在软件设计里,我们叫它“通过合理模块化而灵活应对需求变更”。&br&&br&&br&&br&软件设计同样。比如说,消息循环在派发消息时,只需知道所有UI对象都是个window,都可以响应窗口消息就足够了;它没必要知道每个UI对象究竟是什么(归一化)、也不应该关心这个UI对象的内部执行细节(封装)——该对象自己知道收到消息该怎么做;而且若它出了问题,只需修改该对象即可,不会影响外部。&br&&br&合理划分功能层级、适时砍掉不必要的繁杂信息,一层层向上提供简洁却又完备的信息/接口,高层模块才不会被累死——KISS是最难也是最优的软件设计方法,没有之一。&br&&br&&br&可见,&b&封装和归一化才是战略层面、生死攸关的问题&/b&。遵循它并不能保证你一定能打胜仗,但违反它你必定死的很难看。&br&&br&但这两个问题太大、太难,并且不存在普适性答案。这就使得&b&没有足够经验、缺乏认真思考的外行们根本无从置喙&/b&。&br&&br&&br&&br&前面提到过,人们&b&错误的把多态这个战术技巧提到“封装和归一化”相同的战略层面上&/b&。这就致使本该谈论战略的设计工作被一群&b&毫无实践经验、只会就着浅显的多态胡扯八道的战术家&/b&攻占和把持,进而使得“&b&以战术代替战略&/b&”成为普遍现象——因为对他们来说,&b&多态是既容易理解又容易玩出诸多花样的&/b&;而封装和归一化就太空泛又太复杂,对他们来说完全无从着手了。&br&所以,他们把一切精力都用在多态的滥用上,却从不谈封装和归一化:即使谈到了,也是作为多态的附庸出现的。&br&&br&这种战术层面的空谈很容易、也很容易出彩,但并不解决问题——反而总是导致简单问题复杂化。&br&然而,对于如何解决问题,他们并不在行,也不在乎。因为他们没有能力在乎。&br&这就要命了。&br&&br&&br&&br&&b&总结&/b&:面向对象的好处实际就这么两点。&br&一是通过封装明确定义了何谓接口、何谓接口内部实现、何谓接口的外部调用者,使得大家各司其职,不得越界;&br&二是通过继承+多态这种内置机制,在语言的层面支持&b&归一化&/b&的设计,并使得内行可以从代码本身看到这个设计——但,注意仅仅只是&b&支持&/b&归一化的设计。不懂如何做出这种设计的外行仍然不可能从瞎胡闹的设计中得到任何好处。&br&&br&&br&显然,不用面向对象语言、不用class,一样可以做归一化的设计(如老掉牙的泛文件概念、游戏行业的一切皆精灵),一样可以封装(通过定义模块和接口),只是用面向对象语言可以直接用语言元素显式声明这些而已;&br&而用了面向对象语言,满篇都是class,并不等于就有了归一化的设计。甚至,因为被这些花哨的东西迷惑,反而更加不知道什么才是设计。&br&&br&&br&2、人们以为面向对象是什么、以及因此制造出的悲剧以及闹剧&br&&br&误解一、&b&面向对象语言支持用语言元素直接声明封装性和接口兼容性,所以用面向对象语言写出来的东西一定更清晰、易懂&/b&。&br&&br&事实上,既然class意味着声明了封装、继承意味着声明了接口兼容,那么错误的类设计显然就是错误的声明、盲目定义的类就是无意义的喋喋不休。而&b&错误的声明比没有声明更糟;通篇毫无意义的喋喋不休还不如错误的声明&/b&。&br&&br&除非你真正做出了漂亮的设计,然后用面向对象的语法把这个设计声明出来——仅仅声明真正有设计、真正需要人们注意的地方,而不是到处瞎叫唤——否则不可能得到任何好处。&br&&br&&b&一切皆对象实质上是在鼓励堆砌毫无意义的喋喋不休&/b&,并且用这种战术层面都蠢的要命的喋喋不休来代替战略层面的考量。&br&&br&大部分人——注意,不是个别人——甚至被这种无意义的喋喋不休搞出了神经质,以至于非要在喋喋不休中找出意义:没错,我说的就是设计模式驱动编程,以及&a href=&//link.zhihu.com/?target=http%3A//coolshell.cn/articles/8745.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&如此理解面向对象编程&i class=&icon-external&&&/i&&/a&。&br&&br&&br&&br&误解二、&b&面向对象三要素是封装、继承、多态,所以只要是面向对象语言写的程序,就一定“继承”了语言的这三个优良特性&/b&。&br&&br&事实上,如前所述,封装、继承、多态只是语言层面对良好设计的支持,并不能导向良好的设计。&br&如果你的设计做不出真正的封装性、不懂得何谓归一化,那它用什么写出来都是垃圾(不仅如此,因为你的低水平,“面向对象三要素”反而会误导你,使你更快、更远、更诡异的偏离目标)。&br&&br&&br&&br&误解三、&b&把软件写成面向对象的至少是无害的&/b&。&br&&br&要了解事实上是什么,需要先科普几个概念。&br&&br&&br&1、什么是真正的&b&封装&/b&?&br&&br&——回答我,封装是不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”?&br&&br&显然&b&不是&/b&。&br&如果功能得不到满足、或者未曾预料到真正发生的需求变更,那么你怎么把一个成员变量/函数放到private里面的,将来就必须怎么把它挪出来。&br&&br&你越瞎搞,越去搞某些华而不实的“灵活性”——比如某种设计模式——真正的需求来临时,你要动的地方就越多。&br&&br&&b&真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明(注意:对外透明&/b&的意思是&b&,&/b&外部调用者可以顺利的得到自己想要的任何功能,&b&完全意识不到内部细节的存在;&/b&而不是外部调用者为了完成某个功能、却被碍手碍脚的private声明弄得火冒三丈;最终只能通过怪异、复杂甚至奇葩的机制,才能更改他必须关注的细节——而且这种访问往往被实现的如此复杂,以至于稍不注意就会酿成大祸)。&br&&br&一个设计,只有达到了这个高度,才能真正做到所谓的“封装性”,才能真正杜绝对内部细节的访问。&br&&br&否则,生硬放进private里面的东西,最后还得生硬的被拖出来——当然,这种东西经常会被美化成“访问函数”之类渣渣(不是说访问函数是渣渣,而是说因为设计不良、不得不以访问函数之类玩意儿在封装上到处挖洞洞这种行为是渣渣)。&br&&br&&br&&br&一个典型的例子,就是C++的new和过于灵活的内存使用方式之间的耦合。&br&这个耦合就导致了new[]/delete[]、placement new/placement delete之类怪异的东西:这些东西必须成对使用,怎么分配就必须怎么释放,任何错误搭配都可能导致程序崩溃——这是为了兼容C、以及得到更高执行效率的无奈之举;但,它更是“抽象层次过于复杂,以至于无法做出真正透明的设计”的典型案例:只能说,c++设计者是真正的大师,如此复杂的东西在他手里,才仅仅付出了如此之小的代价。&br&&br&(更准确点说,是new/delete和c++的其它语言元素之间是非正交的;于是当同时使用这些语言元素时,就不可避免的出现了彼此扯淡的现象。即new/delete这个操作对其它语言元素非透明:在c++的设计里,是通过把new/delete分成两层,一是内存分配、二是在分配的内存上初始化,然后暴露这个分层细节,从而在最大程度上实现了封装——但比之其它真正能彼此透明的语言元素间的关系,new/delete显然过于复杂了)&br&&br&这个案例,可以非常直观的说明“设计出真正对外透明的封装”究竟会有多难。&br&&br&&br&2、&strong&接口继承&/strong&真正的好处是什么?是用了继承就显得比较高大上吗?&br&&br&显然不是。&br&&br&接口继承没有任何好处。它只是声明某些对象在某些场景下,可以用归一化的方式处理而已。&br&&br&换句话说,如果不存在“需要不加区分的处理类似的一系列对象”的场合,那么继承不过是在装X罢了。&br&&br&&br&&br&&br&了解了如上两点,那么,很显然:&br&1、如果你没有做出好的抽象、甚至完全不知道需要做好的抽象就忙着去“封装”,那么你只是在“封”和“装”而已。&br&这种“封”和“装”的行为只会制造累赘和虚假的承诺;这些累赘以及必然会变卦的承诺,必然会为未来的维护带来更多的麻烦,甚至拖垮整个项目。&br&&br&正是这种累赘和虚假的承诺的拖累,而不是为了应付“需求改变”所&b&必需&/b&的“灵活性”,才是大多数面向对象项目代码量暴增的元凶。&br&&br&2、没有真正的抓到一类事物(&b&在当前应用场景下&/b&)的根本,就去设计继承结构,是必不会有所得的。&br&&br&不仅如此,请注意我强调了&b&在当前应用场景下&/b&。&br&这是因为,分类是一个极其主观的东西,&b&不存在普适的分类法&/b&。&br&&br&举例来说,我要研究种族歧视,那么必然以肤色分类;换到法医学,那就按死因分类;生物学呢,则搞门科目属种……&br&&br&想象下,需求是“时尚女装”,你却按“窒息死亡/溺水死亡/中毒死亡之体征”来了个分类……你说后面这软件还能写吗?&br&&br&&br&&br&类似的,我遇到过写游戏的却去纠结“武器装备该不该从游戏角色继承”的神人。你觉得呢?&br&&br&事实上,游戏界真正的抽象方法之一是:一切都是个有位置能感受时间流逝的精灵;而某个“感受到时间流逝显示不同图片的对象”,其实就是游戏主角;而“当收到碰撞事件时,改变主角下一轮显示的图片组的”,就是游戏逻辑。&br&&br&&br&看看它和“武器装备该不该从游戏角色继承”能差多远。想想到得后来,以游戏角色为基类的方案会变成什么样子?为什么会这样?&br&&br&&br&&br&&br&&br&最具重量级的炸弹则是:正方形是不是一个矩形?它该不该从矩形继承?如果可以从矩形继承,那么什么是正方形的长和宽?在这个设计里,如果我修改了正方形的长,那么这个正方形类还能不能叫正方形?它不应该自然转换成长方形吗?如果我有两个List,一个存长方形,一个存正方形,自动转换后的对象能否自动迁移到合适的list?什么语言能提供这种机制?如果不能,“一视同仁的处理某个容器中的所有元素”岂不变成了一句屁话?&br&&br&造成这颗炸弹的根本原因是,面向对象中的“类”,和我们日常语言乃至数学语言中的“类”根本就不是一码事。&br&&br&面向对象中的“类”,意思是“接口上兼容的一系列对象”,关注的只不过是接口的兼容性而已(可搜索 里氏代换);关键放在“可一视同仁的处理”上(学术上叫is-a)。&br&&br&显然,这个定义完全是且只是为了应付归一化的需要。&br&&br&这个定义经常和我们日常对话中提到的类概念上重合;但,如前所述,根本上却彻彻底底是八杆子打不着的两码事。&br&&br&就着生活经验滥用“类”这个术语,甚至依靠这种粗浅认识去做设计,必然会导致出现各种各样的偏差。这种设计实质上就是在胡说八道。&br&就着这种胡说八道来写程序——有人觉得这种人能有好结果吗?&br&&br&——但,几乎所有的面向对象语言、差不多所有的面向对象方法论,却就是在鼓励大家都这么做,完全没有意识到它们的理论基础有多么的不牢靠。&br&——如此作死,焉能不死?!&br&&br&&br&——你还敢说面向对象无害吗?&br&&br&——在真正明白何谓封装、何谓归一化之前,每一次写下class,就在错误的道路上又多走了一步。&br&——设计真正需要关注的核心其实很简单,就是封装和归一化。&b&一个项目开始的时候,“class”写的越早,就离这个核心越远&/b&。&br&——过去鼓吹的各种面向对象方法论、甚至某些语言本身,恰恰正是在怂恿甚至逼迫开发者尽可能早、尽可能多的写class。&br&&br&&br&重复一遍:封装可(通过固定接口而)应付需求变更、归一化可简化(类的使用者的)设计:以上,就是面向对象最最基本的好处。&br&——其它一切,都不过是在这两个基础上的衍生而已。&br&&br&换言之,&b&如果得不到这两个基本好处,那么也就没有任何衍生好处&/b&——应付需求变更/简化设计并不是打打嘴炮就能做到的。&br&&br&&br&误解四、只有面向对象语言写的程序才是面向对象的。&br&&br&事实上,unix系统提出泛文件概念时,面向对象语言根本就不存在;游戏界的精灵这个基础抽象,最初是用C甚至汇编写的;……。&br&&br&面向对象其实是汲取以上各种成功设计的经验才提出来的。&br&&br&所以,面向对象的设计,不必非要c++/java之类支持面向对象的语言才能实现;它们不过是在你做出了面向对象的设计之后,能让你写得更惬意一些罢了——但,如果一个项目无需或无法做出面向对象的设计,某些面向对象语言反而会让你很难受。&br&&br&&b&用面向对象语言写程序,和一个程序的设计是面向对象的,两者是八杆子打不着的两码事&/b&。纯C写的linux kernel事实上比c++/java之类语言搞出来的大多数项目更加面向对象——只是绝大部分人都自以为自己到处瞎写class的面条代码才是面向对象的正统、而死脑筋的linus搞的泛文件抽象不过是过程式思维搞出来的老古董。&br&&br&——这个误解之深,甚至达到连wiki词条里面,都把OOP定义为“用支持面向对象的语言写程序”的程度。&br&——我们提及面向对象时,明明在谈论战略、谈论软件总体设计;但总有人把它歪曲成战术方面的、漫无目标却还自我感觉良好的、琐碎的投机。&br&——恐怕这也是没有人说泛文件设计思想是个骗局、而面向对象却被业界大牛们严厉抨击的根本原因了:真正的封装、归一化精髓被抛弃,浮于表面的、喋喋不休的class/设计模式却成了”正统“!&br&&br&借用楼下PeytonCai朋友的链接:&br&&a href=&//link.zhihu.com/?target=http%3A//dev.yesky.com/405/.shtml& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&名家吐槽:面向对象编程从骨子里就有问题&i class=&icon-external&&&/i&&/a&&br&&br&————————————————————————————&br&&br&总结: 面向对象其实是对过去成功的设计经验的总结。但那些成功的设计,不是因为用了封装/归一化而成功,而是&b&切合自己面对的问题,给出了恰到好处的设计&/b&。&br&&br&让一个初学者知道自己应该向封装/归一化这个方向前进,是好的;用一个面向对象的条条框框把他们框在里面、甚至使得他们以为写下class是完全无需思索的、真正应该追求的是设计模式,则是罪恶的——它实质上是把初学者的注意力从真正应该注意的封装、归一化方向引开,欺骗他们陷入“近乎恶意的全方位滥用多态”的泥潭。&br&&br&事实上,class写的越随意,才越需要设计模式;就着错误的实现写得越多、特性用得越多,它就越发的死板,以至于必须更加多得多的特性、模式、甚至语法hack,才能勉强完成需求。&br&&br&只有经过真正的深思熟虑,才有可能做到KISS。&br&&br&&br&到处鼓噪的面向对象编程的最大弊端,是把软件设计工作偷换概念,变成了“就着class及相关教条瞎胡闹,不管有没有好处先插一杠子”,甚至使得人们忘记去关注“抽象是否真正简化了面对的问题”——这是猥琐的投机,不是设计。&br&&br&&b&一言以蔽之:没有银弹。&/b&任何寄希望于靠着某种“高大上”的技术——无论是面向对象、数据驱动、消息驱动还是lambda、协程等等等等——就能一劳永逸的使得任何现实问题“迎刃而解”的企图都是注定要失败的,都不过是外行的意淫而已;靠意淫来做设计,不掉沟里才怪。&br&&br&想要做出KISS的方案,就必须对面对的问题有透彻的了解,有足够的经验和能力,并经过深思熟虑,这才能做出简洁的抽象:至于最终的抽象是面向对象的、面向过程的还是数据驱动/消息驱动的,甚至是大杂烩的,那都无所谓。只要这个设计能做到最重要、也是最难的KISS,它就是个好设计。&br&&br&在特定领域、特定场景下,的确有成功的经验、正确/合理的方向:技术无罪,但,没有银弹。&br&&br&&br&————————————————————————————————————————&br&:&br&&br&嗯,这个是我很久很久以前在CU上发过的一系列帖子……&br&&br&当时很多鼓吹“面向对象就是好来就是好的”就着一知半解胡搅蛮缠,这系列帖子是驳斥他们的。所以很多词句挖苦意味很浓,见谅。&br&&br&&blockquote&再比如,传说中的面向对象本该大显神威的游戏领域——就说流行的WOW吧。&br&&br&这个游戏有10个职业,10个种族,每个种族都有自己的几个特有种族天赋(这个种族天赋还可能根据职业有所不同,比如血精灵);每个职业有几十甚至上百种不同的技能/法术,这些技能有近战技能,有远程技能;有的技能会对敌方造成伤害或不良状态,有的技能能给己方队友加上好的状态或治疗队友;而且很多这类技能还会根据目标的状态切换不同的效果;有些技能是单体效果,有些技能是光环效果(又分为对敌方造成光环效果还是对己方两种,也可能两者兼备),而另一些技能是地图范围效果(如烈焰风暴是一个圆形区域;冰锥术是一个锥形区域;特别的,顺劈斩是在当前攻击目标旁边不超过5码的另一个敌对目标——某个boss的顺劈斩更强,它会从第一个目标传递几十个目标,总传递距离可以达到夸张的几百码;并且这个伤害也是各有特色的:战士的顺劈斩是每个目标伤害固定,有些boss的则是同时挨打的人越多伤害越低,但还有个变态boss却是被打的人越多伤害越高……);大多数技能还可以通过天赋雕文强化/改变的面目全非(比如插一个雕文,法师的火球就不会造成持续伤害但施法速度增加;点一个天赋,法师的冰冷减速效果就会降低对方受到的治疗效果;点某个天赋,盗贼的某些技能攻击就会延长自身提升攻击速度这个状态的持续时间,等等);还有很多技能是因为学习了某个专业或装备/持有某个物品而得到(比如,学了采药,就可以得到生命之血这个技能,每3分钟可用,能够在若干秒内回复你若干生命值——这个技能和采药技能等级挂钩,但很可能接下来的某个版本,就会再和玩家的生命上限值挂钩,以避免它像现在一样,被玩家斥为废柴技能);另外,不同等级的技能可能有施法时间甚至额外特效方面的差别;此外,每个技能会造成不同属性的伤害/效果(神圣、暗影、元素、物理等等),甚至一个技能同时造成多种类型伤害效果,更有冰火球这样根据目标抵抗力而智能选择更大杀伤效果类型的变态魔法……&br&&br&最后,最最重要的是,这所有职业上千个技能(或许加上NPC特有的一些技能,数目会达到几千种)并不稳定,常常会因为某个技能或某些技能的组合过于强大/弱小而加以修改(比如加一个额外的负面状态如无敌/圣疗;甚至全面修改“抗性”“破甲”概念的定义)——玩过wow的都知道,这事几乎每个月都有发生。&br&&br&好吧,你打算怎么设计这数千个技能/效果?&br&或者,你就这样把这些概念用class这个筐一装,然后到处开特例、特例都解决不了就搞23个模式使劲往一块粘,管他整体结构如何,淌哪算哪?&br&&br&扯淡。&br&&br&&br&有个故事说的好:&br&有人送几个瞎子一条鱼,瞎子们高兴坏了,决定熬鱼汤喝。鱼汤熬好了,瞎子甲尝了一口,真鲜啊;瞎子乙赶紧也喝一口,太鲜了,太好喝了。几个瞎子一边喝一边赞美——忽然瞎子丙叫了起来:鱼跳我脚上了,它不在锅里!&br&众瞎子大惊:这鱼都没放到锅里,汤就鲜成这样了;要是放进锅里,还不得把我们都鲜死啊!&br&&br&众面向对象原教旨主义者把事情搅得一团糟,同样也会大惊:天哪,用了面向对象都复杂成这样,这要不用面向对象,这软件就不能写了吧!&/blockquote&&br&&br&&blockquote&想想看,假如让那些面向对象原教旨主义者来设计,会出现什么情况:&br&&br&定义一个基类叫技能;然后一个继承类叫法术技能,另一个叫物理技能;然后神圣法术从法术技能继承,疾病法术也从法术技能继承;由于圣骑士一个技能同时具备物理和法术两种效果,于是必须多重继承神圣法术和物理技能;多重继承太危险,于是不得不把神圣法术搞成接口类,引入接口继承甚至带实现的纯虚函数等等高端概念;然后,活该枪毙的暴雪设计师又想出了让某个技能同时对目标加上神圣持续伤害效果的奇怪点子——于是不得不再加个继承层次,使得神圣法术是神圣持续伤害法术的子集:仅立刻造成一次持续伤害的DOT(damage of time)技能……&br&&br&那么,点一个天赋,一个技能就会有dot,否则就没有怎么办?&br&&br&设计模式是灵丹妙药,不是吗 ^_^&br&&br&&br&等到把这所有几千个技能全部搞定,起码也是一个数万个类、几十层的恐怖继承树,并且会用完23个设计模式(甚至再发明几个新模式出来,我也不会感到奇怪),精巧复杂到没有任何人愿意去碰它。&br&&br&&br&但,请注意,天杀的暴雪设计师,在最开始的设计方案里规定DOT不能暴击;后来又添加约定说某某某职业的某个dot可以暴击;另一个职业的某个dot在点天赋后可暴击;至于死亡骑士,在他穿了T9套装中的其中四件装备时,他的某个瘟疫类型的dot可以暴击——但另一个瘟疫dot永远不能暴击。&br&&br&&br&嗯嗯嗯,太好解决了——这不就是策略模式吗?&br&&br&好吧,你再填几十几百个类体系,然后把旧的几十层继承树中的数万个类一个个都策略化吧。反正不是我在维护……&br&&br&&br&&br&哎呀不好,那个枪毙了几百次都还没死的暴雪设计师又出馊主意了,他要求:当死亡骑士点了邪恶系的某个天赋时,不光给他增加一个新的dot、并且在这个新dot的存在期间,还要保护他的两个dot性疾病和1个debuf性疾病不被驱散!&br&&br&&br&继续补充:在WLK里面,那个脑袋都被子弹打成筛子了的暴雪设计师又跳出来了,用他满是漏洞的脑子出了个该杀的主意:他要求添加载具概念,当玩家坐上载具时,临时删除他的所有技能,替换为载具的技能;或者当他坐在特定载具的特定位置时,防止他受到任何伤害、并且允许他释放自己的所有技能!&br&更该死的是,他要求,一些技能本来不允许在移动中施放;但现在,当玩家坐在载具上某个位置时,要临时允许他移动施法!&br&&br&还有,为了平衡某个野外战场,他还要求,在某方人数较少时,临时根据提高他们的生命值和所有技能的攻击力和治疗能力——这个改变必须根据进入战场的人数实时进行;在一方连续在某个战场失败时,同样要给他们一定补偿!&br&&br&&br&&br&嗯嗯,看看这些不断改变的刁钻需求吧,如果没有面向对象,没有以策略模式为首的28个设计模式(我有理由相信你们需要至少28个设计模式而不是23个)的英明领导,我们这些没接触过大项目、不懂面向对象的傻B们,就是哭的拿眼泪把长城溶解掉都没办法吧?——我当然知道搭建长城的材料极难溶与水。&br&&br&可怜的瞎子,你们的鱼汤很鲜吧?&/blockquote&&br&&br&&br&&br&嗯,到这里,希望读者们也能停下来,好好思考一下,看看这个问题该如何解决。&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&想到了没有?&br&这里是答案,看看你的想法是否能不谋而合吧:&br&&br&&blockquote&这个问题暴雪在Diablo 2时代已经完美解决了: 法术/技能数据库化&br&&br&&br&所谓数据库化,其实等同于表格化,例如这个随便杜撰出来的简化方案,是设计一个有如下字段的数据表格:&br&&br&法术ID 动画效果 作用范围 作用类型 属性 特殊限制 强化类型 特殊设定&br&&br&&br&其中,特殊设定字段可以是一段LUA代码,可以在其中搜索、设置极其特殊的伤害类型,或者查询顺劈斩/治疗链等奇特技能的传递目标等等。&br&&br&特殊限制字段设定法术的施法或/和生效条件,如驱散限定为只能作用于魔法性buf/debuf(根据职业不同,可能有进攻性驱散和防守性驱散之一,也可能同时具备——这就体现在可否驱散敌方/友方目标的debuf)&br&&br&&br&&br&在这个方案下,释放一个法术/技能,就成为一种查表运算——找到此法术ID,找到它的作用类型和伤害属性,计算特殊设定(包括但不限于顺劈斩模式的判断、天赋加成和天赋效果、雕文加成和雕文效果等等)。&br&&br&于是,到最后,整个法术体系被分为一组组的魔法buf/debuf、物理buf/debuf,这些buf/debuf会影响伤害公式中的某个因子或者造成伤害效果;而伤害效果又分为立即伤害/立即治疗和持续伤害/持续治疗;最后则是一套影响范围判定机制。&br&&br&&br&举例来说,骑士开圣盾,他同时得到一个buf和一个debuf。&br&buf是“无敌”,效果相当于设置伤害公式 a*(....) 前面的a因子为0(没有无敌时此因子为1),于是所有伤害无效。&br&debuf则是“自律”,因为他的圣盾、圣疗技能判断条件里都有“有自律debuf,则不允许使用”的设定,于是禁止他在短时间内再次使用这些无赖技能。&br&&br&敌方法师对他释放寒冰箭,系统受理,但查询骑士状态,发现他处于无敌状态,返回大大的两个字“免疫”。&br&&br&然后,有一个敌方牧师对他使用驱散,查询牧师的驱散术发现,在驱散术的可驱散列表里没有圣盾术,于是提示无法驱散或驱散了另外的可驱散(魔法)效果。&br&敌方牧师迅速反应过来,再次对他使用强力驱散;查询牧师强力驱散术,发现该牧师在不久前使用过强力驱散,提示无法施法。&br&等待3秒后,敌方牧师发现自己的强力驱散冷却(cool down),再次使用强力驱散,查询发现强力驱散可驱散圣盾术,于是成功移除骑士的无敌状态。&br&&br&现在,敌方法师再次对他释放寒冰箭,骑士切换冰抗光环,系统查询骑士状态,发现冰抗光环,又查询法师穿透等级,和暴击等级,根据公式计算能否命中、能否造成全额伤害以及能否暴击;然后提取法师和骑士双方装备、天赋数据代入公式计算伤害加成、减免数据,最后给出骑士受到的伤害数字(包括部分抵抗了多少)。&br&&br&&br&&br&在暴雪设计师的整理之下,如上种种最终构成了几个表格;只要查询并代入相应的数据,即可计算出伤害/治疗数值以及类型;特殊效果可以用存储在数据库中的LUA代码补充完成。&br&&br&最终的设计效果就好像内嵌了一个解释器,这个解释器会根据法术ID解释执行数据库内部的相关内容。&br&&br&&br&这样一来,只要伤害公式、伤害/buf类型、动画效果等等就位,那么新增一个法术就只不过是在数据库中新增一条记录;让某个角色学会一个新法术,则只需在它的可使用法术列表里添加法术名称(或法术ID);释放法术是根据法术ID在数据库中提取动画;计算伤害是根据法术ID读取伤害公式,然后代入相关字段并求值。&br&&br&而这一切,甚至可以通过内部实现的编辑器,用图形界面完成。&br&&br&&br&如何?无与伦比的扩展性和便利性,是吧?&/blockquote&&br&&br&这么一整套东西,核心代码很可能只有数千甚至数百行。这是因为看似复杂的光环、buf等等东西,其实都已经抽象到和其他法术同样的流程上来了。最终,所有这些全部归一为解释执行伤害公式、提取执行指定动画之类寥寥几个通用过程——&b&这显然同样是封装和归一化思想结出的另一颗果实。但为什么你就是想不到封装和归一化还能这样用?很简单,因为你被那些只会就着浅显的多态喋喋不休的笨蛋彻底引偏方向了。&/b&&br&&br&我并没有亲自实现过这个,所以不敢断定这玩意儿靠几百行代码真的就能全部实现;但根据我在其它项目上的经验,这套东西应该就是数百行代码就可以写出来的——但写出并调试好这数百行代码所需的时间可能是一个星期甚至一个月。&br&&br&相比于不假思索的写下class所必然导致的庞大、复杂的类层次,以及扯来扯去蛋疼无比的复杂的设计模式大网,这玩意儿的实现、维护、修改、扩展的便利程度,显然不是一个量级的:前者可能数百人努力数年、弄出几百万行代码都不能正确实现需求,而且必然bug满天飞;而后者,一个人,个把月,千把行代码,完成。如果实现水平足够的话,写完就再不用碰代码,而是去写图形编辑工具了。之后,扩展、维护都不过是用自己实现的工具拖来拖去再改改属性、数值,然后点存盘写入数据库,完事。&br&&br&&br&所以说,万不可死板的傻抱着面向对象不放。你所面对的问题才是最重要的。&br&你必须随机应变给出合适的方案——至于最后的设计方案会是什么流派,那玩意儿根本无关紧要。拿出一个简单、有效、可靠的方案,比什么都重要。&br&&br&最后,还是我在前文总结的那句话:&br&&blockquote&&p&封装可(通过固定接口而)应付需求变更、归一化可简化(类的使用者的)设计:以上,就是面向对象最最基本的好处。其它一切,都不过是在这两个基础上的衍生而已。&/p&&br&&p&换言之,如果得不到这两个基本好处,那么也就没有任何衍生好处——应付需求变更/简化设计并不是打打嘴炮就能做到的。&/p&&/blockquote&&br&再强调一遍,应付需求变更/简化设计并不是空洞的宣传口号。&br&&br&&b&封装和归一化类似军队制度建设,目标是搞出一个标准化、立体、多变、高效的指挥体系,从而获得打大战、打硬战的能力&/b&,然后再去轻松碾压问题。此所谓战略。&br&&br&而那些堆砌无用的所谓“设计模式”的家伙,其实是在每个零件表面粘上挂钩——据他们说,这样会增加灵活性、应对需求变更、简化设计:比如说你带了个包,就可以挂他们在飞轮上粘的那个勾子上。&br&&br&但实际上,你永远不会把包挂飞轮上(但你还是不得不为那些”聪明绝顶“的家伙“为了避免飞轮上的钩子脱落、挂住其它零件、离心力太大破坏挂在上面的包”等等而衍生出的”杰出“设计买单)。&br&幸运的是,除了某些企业项目(或其他类似性质的项目),你并不会用到这么烂的东西。因为这些笨蛋到处乱粘的钩子会不可避免的导致整个项目变成黏糊糊的一团,从而在旷日持久的拖延后自杀。&br&&br&这种做法,显然是和面向对象的初心——通过封装和归一化获得高效指挥体系——背道而驰,从而使得每个中了这种毒的家伙参与的项目不可避免的成为一滩稀屎。&br&&br&所以,很遗憾,只有杀马特设计师才会这样做。真正的设计师压根不会在设计发动机时考虑“飞轮上挂包”这样的需求(这就叫“以不知所谓的战术投机代替战略布局”)。他会干净利落的在整车设计时加个后备箱。&br&&br&&br&&br&&br&请注意,这并不是个比喻。&br&&br&如你所见,在”每个零件上粘上挂钩“这种事情实在太过疯狂,所以在其他行业连玩笑都不是,因为再傻的人都不会这么做。&br&&br&然而在软件设计时……这种事情是如此多见,多见到面向对象的领军人物会推荐别人这样做(&a href=&//link.zhihu.com/?target=http%3A//coolshell.cn/articles/8745.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&如此理解面向对象编程&i class=&icon-external&&&/i&&/a&);多见到业内很多大佬都不得不站出来,怒斥”面向对象是个骗局“。&br&&br&&a href=&//link.zhihu.com/?target=http%3A//dev.yesky.com/405/.shtml& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&名家吐槽:面向对象编程从骨子里就有问题&i class=&icon-external&&&/i&&/a&&br&“面向对象编程是一个极其糟糕的主意,只有硅谷里的人能干出这种事情。” — Edsger Dijkstra(图灵奖获得者)&br&&br&&a href=&//link.zhihu.com/?target=http%3A//en.wikipedia.org/wiki/Edsger_W._Dijkstra& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Edsger W. Dijkstra&i class=&icon-external&&&/i&&/a&&br&&br&如此沉重的心智负担,这显然是面向对象的原罪。
弊端是,没有人还记得面向对象原本要解决的问题是什么。 1、面向对象原本要解决什么(或者说有什么优良特性) 似乎很简单,但实际又很不简单:面向对象三要素封装、继承、多态 (警告:事实上,从业界如此总结出这面向对象三要素的一刹那开始,就已经开始犯…
&p&归纳题主的问题:&/p&&p&这个世界上有各种各样的框架,设计这些五花八门框架的初衷到底是什么?我们该不该学习框架,该如何学习使用这些框架?&/p&&br&&p&回答题主的问题:&/p&&p&&b&一、首先,到底什么是框架?&/b&&/p&&p&想要回答这个问题,我们要慢慢来。&/p&&br&&p&&b&①&br&首先从DRY原则开始说起&/b&&/p&&p&Don't Repeat Yourself,不要重复你的代码。&/p&&p&DRY原则的重要性怎么提都不过分,很多人说编程是种机械性的工作,而有很多程序员也自嘲为码农,意为编程成了一种没有技术含量的体力性工作。如果不想沦为这个境界,首先需要的就是将DRY原则融入你的血液,在今后的编码工作中加以运用。&/p&&br&&p&1)最初级的DRY:语法级别&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&System.out.println(1);
System.out.println(2);
System.out.println(10);
&/code&&/pre&&/div&&p&我想只要学过基础语法,都会采用下面的形式。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&for (int i = 1; i &= 10; i++) {
System.out.println(i);
&/code&&/pre&&/div&&p&如果发现有任何人采用上面一种形式的编码形式,那么不用怀疑,他对于编程绝对还没有入门。&/p&&p&我们当然会选择省力的做法,这种做法不但省力,还会有利于我们后续修改或扩展这组代码,如:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&for (int i = 1; i &= 10; i++) {
System.out.println(i * 2 + 1);
&/code&&/pre&&/div&&p&我们进行这样的修改,只需要修改一处,而上面的形式却需要修改10处,当然会更麻烦且更容易出错,所以请记住能不重复就不重复。&/p&&br&&p&2)进阶的DRY原则:方法级别&/p&&p&当我们经常写一些重复性代码时,我们就要注意看能否将其抽取出来成为一个方法,如:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
&/code&&/pre&&/div&&p&让我们将其抽取到一个方法 threadSleep() 中,这样我们只需要调用 threadSleep() 就可以实现原来的功能,不但所需敲击的代码更少,而且代码看起来更加清楚明白。而为了增加这个方法的复用性,我们还可以将其中固定的数字抽取成为参数,如:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&private static void threadSleep(int millis) {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
&/code&&/pre&&/div&&p&这样我们就可以利用这个方法实现不同时间的sleep了。要注意提高代码的复用性也是实践DRY原则的一个重要方法,在后面我们也可以看到框架为了提高所谓的灵活性进行的一些设计,如在适当的位置增加扩展点。&/p&&br&&p&3)继续进阶的DRY原则:类型级别&/p&&p&现在我们看一个类&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&public class Person {
// Setter & Getter ...
&/code&&/pre&&/div&&p&我们新建一些Person类实例,并进行一些操作:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&Person person = new Person();
person.setName(&jack&);
person.setAge(18);
Person person2 = new Person();
person2.setName(&rose&);
person2.setAge(17);
System.out.printf(&Name: %s, Age:%d\n&, person.getName(), person.getAge());
System.out.printf(&Name: %s, Age:%d\n&, person2.getName(), person2.getAge());
&/code&&/pre&&/div&&p&观察这些代码,其实有很大的DRY改造空间,首先可以添加一个构造方法&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&public Person(String name, int age) {
this.name =
this.age =
&/code&&/pre&&/div&&p&其次,可以添加一个toString()方法&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&public String toString() {
return String.format(&Name: %s, Age: %d&, name, age);
&/code&&/pre&&/div&&p&这样的话,上面的代码就可以改成下面的形式。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&Person person = new Person(&jack&, 18);
Person person2 = new Person(&rose&, 17);
System.out.println(person.toString());
System.out.println(person2.toString());
&/code&&/pre&&/div&&br&&p&4)继续继续进阶的DRY原则:多个类组合级别&/p&&p&上面的代码我们其实还是有改善空间,就是利用容器类&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&List&Person& list = new ArrayList&&();
list.add(new Person(&jack&, 18));
list.add(new Person(&rose&, 17));
list.forEach(p -& System.out.println(p));
&/code&&/pre&&/div&&p&这里利用JDK8的Stream API以及Lambda表达式输出,其实可以进一步简化为 &/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&list.forEach(System.out::println);
&/code&&/pre&&/div&&p&这里我们可以看到,基本上我们写代码只写有变化的代码,而尽量不写机械性重复性的代码,其实后面我们就会知道,这就叫专注于业务逻辑,所谓业务逻辑就是你这个项目中,与别的项目都不一样的地方,必须由你亲自去编写实现的部分。&/p&&p&其实容器类很大程度上也是为了帮助我们编写代码而被设计出来的,首先让我们不必为每一个对象起名字(省去了person,person2,...等变量),然后又为批量操作提供了可能性。像是这样一系列有用的类组合起来可以称之为类库。常用的类库有Commons-Lang包等,为我们提供了一大批实用方法,我之所以提到类库,也是因为框架其实也是一种特殊的类库,但是却与一般的类库有着本质的不同。&/p&&br&&p&&b&②&br&设计模式,更高层级的DRY应用&/b&&/p&&p&上面我讲到了DRY原则的几个层次,一般情况下大家也早就这样使用了,属于入门之后很容易自己就想到得一些层次。但是设计模式不一样,设计模式是经过长时间编码之后,经过系统性的总结所提出的针对某一类问题的最佳解决方案,又称之为最佳实践。&/p&&p&而在小规模的编码工作中,其实并不需要什么设计模式,只有大型程序才有设计模式发挥的空间,所以我们需要借助一些特定领域有足够规模的问题来了解一下设计模式存在的必要性。&/p&&br&&p&1)连接数据库,进行一些操作,并安全释放数据库连接。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&public static boolean updatePassword(String username, String password, String newpassword) {
Connection conn =
PreparedStatement stmt =
ResultSet rs =
boolean success =
conn = beginTransaction();
stmt = conn.prepareStatement(&select id, password from user where username = ?&);
stmt.setString(1, username);
rs = stmt.executeQuery();
if (rs.next()) {
if (rs.getString(&password&).equals(password)) {
PreparedStatement stmt2 =
stmt2 = conn.prepareStatement(&update user set password = ? where id = ?&);
stmt2.setString(1, newpassword);
stmt2.setLong(2, rs.getLong(&id&));
success = stmt2.executeUpdate() & 0;
} finally {
safeClose(stmt2);
commitTransaction(conn);
} catch (SQLException e) {
rollbackTransaction(conn);
throw new RuntimeException(e);
} finally {
safeClose(rs);
safeClose(stmt);
safeClose(conn);
&/code&&/pre&&/div&&p&上面是一个简单的数据库事务,虽然只有一个查询和一个更新,但是想要将其继续简化却并不容易,虽然其中有关于业务逻辑的部分只是少量几行代码,但是初始化,异常,提交,回滚操作让我们很难抽取出一个合适的方法来。虽然我们已经抽取出了 begin,commit,rollback,safeClose等方法,但是仍嫌繁琐。&/p&&p&我们发现之所以我们难以抽取方法,主要是因为流程,因为里面牵扯到流程控制,而流程控制一般是由我们程序员来控制的,所以也就必然需要我们手动编码来完成。难道真的就不能继续简化了吗?这就是需要设计模式的时候了。&/p&&br&&p&2)应用设计模式「模板方法模式」&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&public static boolean updatePassword(String username, String password, String newpassword) {
return connection(conn -& statement(conn, &select id, password from user where username = ?&, stmt -& {
stmt.setString(1, username);
return resultSet(stmt, rs -& {
if (rs.next()) {
if (rs.getString(&password&).equals(password)) {
long id = rs.getLong(&id&);
return statement(conn, &update user set password = ? where id = ?&, stmt2 -& {
stmt2.setString(1, newpassword);
stmt2.setLong(2, id);
return stmt2.executeUpdate() == 1;
&/code&&/pre&&/div&&p&可以看到,所有的conn,stmt,rs的开启和关闭,事务的提交和回滚都不用自己手动编写代码进行操作了,之所以可以达到这个效果,就是因为使用了模板方法设计模式,核心就是通过回调方法传递想对资源进行的操作,然后将控制权交给另一个方法,让这个方法掌握流程控制,然后适当的时候回调我们的代码(也就是我们自己写的业务逻辑相关的代码)。&/p&&p&这是需要额外写的几个方法&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&public interface ConnectionCallback&T& {
T doConnection(Connection conn) throws SQLE
public interface StatementCallback&T& {
T doStatement(PreparedStatement stmt) throws SQLE
public interface ResultSetCallback&T& {
T doResultSet(ResultSet rs) throws SQLE
public static &T& T connection(ConnectionCallback&T& callback) {
Connection conn =
T result =
conn = beginTransaction();
result = callback.doConnection(conn);
commitTransaction(conn);
} catch (SQLException e) {
rollbackTransaction(conn);
throw new RuntimeException(e);
} finally {
safeClose(conn);
public static &T& T statement(Connection conn, String sql, StatementCallback&T& callback) throws SQLException {
PreparedStatement stmt =
T result =
stmt = conn.prepareStatement(sql);
result = callback.doStatement(stmt);
} finally {
safeClose(stmt);
public static &T& T resultSet(PreparedStatement stmt, ResultSetCallback&T& callback) throws SQLException {
ResultSet rs =
T result =
rs = stmt.executeQuery();
result = callback.doResultSet(rs);
} finally {
safeClose(rs);
&/code&&/pre&&/div&&p&你们可能会疑惑,这些代码加上我们写的业务逻辑的代码,比原来的代码还要长,有什么必要使用这个设计模式。这正是我前面已经指出的一个问题,那就是要你的程序规模足够大才有必要应用设计模式,试想如果你有上百个乃至上千个数据库操作方法需要写,那么是不是写这几个额外的方法,就不算什么了呢。&/p&&p&其实这正是DRY原则在更高层次上的应用,即结合设计模式来达到更高层次的代码复用效果,进而应用DRY原则。而想要在这个层次继续向上攀升,那就必须是结合众多设计模式以及一些高层架构设计,能够帮助我们实现这一目的的就是框架。&/p&&br&&p&3)框架,是设计模式的集大成者,是DRY原则的最高应用&/p&&p&先让我们来看一下,使用框架会是什么样的一种体验?&/p&&p&这里以Hibernate + Spring声明式事务为例&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&@Transactional
public boolean updatePassword(String username, String password, String newpassword) {
User user = (User) session().createQuery(&from User where username = :username&)
.setString(&username&, username)
.uniqueResult();
if (user != null && user.getPassword().equals(password)) {
user.setPassword(newpassword);
&/code&&/pre&&/div&&p&可以发现令人惊讶的简洁,而且代码逻辑异常清晰,完全不需要考虑conn,stmt,rs等资源的释放,以及事务的提交和回滚,但是这些事情其实框架已经默默的帮我们做到了。这才叫真正的专注于业务逻辑,尽最大可能的只写与业务逻辑有关的代码。&/p&&p&当然这些框架的效果虽然神奇,其实只要细细探究其内部原理,是完全可以理解并掌握的。&/p&&br&&p&&b&二、那么问题就来了,框架到底是什么?要不要学,怎么学?&/b&&/p&&p&上面我说过了,框架其实就是一个或一组特殊的类库,特殊在什么地方?特殊在控制权转移!&/p&&p&框架与一般类库不同的地方是,我们调用类库,而框架调用我们。也就是说框架掌握整个程序的控制权,我们必须一定程度上把程序流程的控制权交给框架,这样框架才能更好的帮助我们。&/p&&p&下面以JavaWeb开发为例再进行一些说明,并顺便简单介绍一下JavaWeb的一些脉络。&/p&&br&&p&①&br&静态网页时代&/p&&p&本来网站都是一个个静态HTML组成的,或许这些网页还是用Dreamweaver写的,但是这样的静态页面显然不能满足我们,很快我们就迎来了动态网页的时代。&/p&&br&&p&②&br&Servlet时代&/p&&p&如果熟悉HTTP协议的话,我们就知道其实访问网页的过程不过是一次TCP连接罢了。浏览器发起TCP连接到服务器,服务器接受请求,然后返回HTML代码作为响应。那么我们完全可以等到接受到请求之后,再动态生成HTML代码返回给客户端。&/p&&p&Servlet就是这么做的,其主要代码不过是利用out.write()一点一点的输出HTML代码罢了。当然我们可以在其中掺杂一点动态的东西,如返回当前的时间。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&out.write(&&!DOCTYPE html&\r\n&);
out.write(&&html&\r\n&);
out.write(&&head&\r\n&);
out.write(&&title&Index Page&/title&\r\n&);
out.write(&&/head&\r\n&);
out.write(&&body&\r\n&);
out.write(&Hello, & + new Date() + &\r\n&);
out.write(&&/body&\r\n&);
out.write(&&/html&\r\n&);
&/code&&/pre&&/div&&br&&p&③ JSP包打天下的时代&/p&&p&纯粹的Servlet很是丑陋,给前端程序员理解和修改这样的代码带来了很多困难。因此JSP技术被发明了出来,原理也不复杂,就是不直接写Servlet,而是先写好JSP文件,再由服务器将JSP文件编译成Servlet。而JSP中是以常见的HTML标签为主,这样前端程序员就能方便的修改这些代码了。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&!DOCTYPE html&
&title&Index Page&/title&
Hello, &%=new Date()%&
&/code&&/pre&&/div&&p&PS:由只使用 Servlet到使用JSP,虽然是一个简单的变化,但这迎合了前后端专业分工的大趋势,让前段人员只需要懂得HTML/CSS/JavaScrip代码就可以开始工作,而不需要学习Servlet那枯燥无味的用法,因此借着JSP技术的东风,JavaWeb技术迅速的扩展开来了。&/p&&br&&p&④ Servlet + JSP 时代&/p&&p&随着JSP技术的发展,用它写成的网站也越来越大,业务逻辑也越来越复杂。开发人员渐渐发现整个网站渐渐的再次变成了一团乱麻,不仅仅是JSP中夹杂了大量的Java代码,页面之间的耦合关系也越来越紧密。&/p&&p&即便是要修改一个简单的按钮文本,或者是引入一段静态的内容,也需要打开越来越庞大的JSP页面,艰难到找到需要修改的部分,有时还不仅仅是一处,这种修改是有很大的风险的,完全有可能引入新的错误。&/p&&p&这时候开发者渐渐意识到,仅仅使用JSP是不行的,JSP承担了太多的责任。这时人们又想起了Servlet,Servlet中主要使用Java代码,处理业务逻辑非常轻松。如果JSP只使用HTML代码,而将业务逻辑的代码转移到Servlet中,就可以大大的减轻JSP的负担,并且让前后端分工更加明确。&/p&&br&&p&⑤&br&MVC模式时代&/p&&p&在&br&Servlet + JSP模式的基础上,Java阵营进一步发展出了一种适合JavaWeb应用的设计模式,MVC设计模式,即将程序分为显示层(Viewer),控制层(Controller),模型层(Model)。如下图所示:&/p&&p&&figure&&img src=&https://pic4.zhimg.com/3cc75d6efa23b6269acb_b.png& data-rawwidth=&744& data-rawheight=&325& class=&origin_image zh-lightbox-thumb& width=&744& data-original=&https://pic4.zhimg.com/3cc75d6efa23b6269acb_r.png&&&/figure&一次典型的访问是这样的流程:&/p&&p&1. 用户输入网址或点击链接或提交表单,浏览器发起请求&/p&&p&2. --& 通过互联网,通过HTTP协议 --&&/p&&p&3. Tomcat接受到HTTP请求,生成HttpServletRequest对象,根据Web.xml的配置,调用开发者编写的HttpServlet,HttpServlet根据请求内容,调用JavaBean获取数据,JavaBean从数据库获取数据,返回HttpServlet,HttpServlet将数据转发给JSP,JSP负责将数据渲染为HTML,由Tomcat负责将HTML转化为HTTP响应,返回客户端。&/p&&p&4. --& 通过互联网,通过HTTP协议 --&&/p&&p&5. 客户端浏览器接收到HTTP响应,浏览器将HTML渲染为页面,并运行其中可能存在的JavaScript进一步调整界面。&/p&&br&&p&整个流程必须由开发者精确设计才能运作流畅,其中客户端HTML和JavaScript属于前端设计,服务器运行的其他内容属于后端设计。虽然符合J2EE规范的Tomcat等应用服务器已经帮我们实现了最复杂的一块,即HTTP协议部分,还给我们提供了JSP这个模板引擎,以及自定义标签等手段。但是在控制层,在模型层,J2EE能给我们的帮助少之甚少。&/p&&br&&p&就拿用户提交一个表单为例,而我们在Servlet中获取参数为例,虽然不用我们解析HTTP报文,应该已经是要谢天谢地了,但是我们要做的事情仍然很多,分析一下:&/p&&br&&p&1. 客户端传过来的数据全是文本,而我们需要的是Java对象。&/p&&p&2. 凡是文本就有编码问题,而这需要前后端配合解决。&/p&&p&3. 客户端的输入是不可信的,我们必须校验参数的合法性。&/p&&p&4. 我们还必须将校验结果反馈给客户,并且最好不要让客户全部重新输入。&/p&&p&5. 我们往往不是只有一个参数需要,而是有几个甚至更多参数,要妥善的处理各种情况组合。&br&&/p&&br&&p&这些事情几乎全部都需要我们手动编码来完成,几乎每一个 Servlet 都充斥着这样的代码,设置编码,获取参数,校验参数,校验通不过返回错误信息,校验通过则进行业务处理。而更重要的是,获取参数仅仅是整个流程中的一小步,我们的Servlet中存在着大量的重复性,机械性代码,而处理业务逻辑的代码可能只有一两行。&/p&&br&&p&⑥&br&JavaWeb框架&/p&&p&既然存在着大量的重复,我们当然不能忍,必须请出DRY大法。显然JavaWeb应用是一个规模庞大,流程复杂的应用,我们正需要JavaWeb框架的帮助。以Struts2框架为例,他能给我们什么帮助呢?&/p&&br&&p&1. 在控制层,由Struts2的核心控制器接管控制权,将本来在Web.xml进行配置的一些工作,转移到自定义的struts.xml文件中,这个文件的配置形式更友好。&/p&&p&2. Struts2封装了Serlvet Api,使用POJO对象作为控制器(Action),大量使用反射,不要求继承特定类,有利于复用及单元测试。提供ActionSupport类,结合struts2标签,能很方面实现的校验信息的收集及反馈。&br&&/p&&p&3. 提供国际化支持,在显示层有国际化相关的标签,在控制层由国际化相关的API。提供基于配置的校验及JS生成技术。智能化的参数类型转换,支持自定义转换器。提供Action拦截器,方便实现AOP模式。&/p&&p&4. 提供了基于OGNL表达式的数据共享模式,前后端数据交流更简单,提供了Struts2标签库,简单好用,支持多种模板,如FreeMarker,支持各种插件,如JSON,支持整合多种框架,如Spring。总之一句话,能在各方各面给我们强大的帮助。&/p&&br&&p&⑦&br&所以当然要学框架,要用框架,那么要怎么学?&/p&&p&1. 用框架要知其然,还要知其所以然,要大体明白框架实现一个功能特性的原理,不能只是会用,只是觉得很神奇就可以了。就拿前面的Hibernate + Spring声明式事务为例,要弄明白框架这部分是怎么实现的。&/p&&p&2. 首先要夯实你的语言基础,如JavaSE基础,语法掌握,用法掌握,有些同学语法还不熟练就开始学框架,等于地基没打就起高楼,你可能会快一步,但是迟早要遇到瓶颈,甚至摔跟头。&/p&&p&3. 那么何时开始学习框架?我不建议新手一开始就直接使用框架。&/p&&p&就好像一开始学习编程语言,大家都不推荐直接使用IDE,一定要用命令行自己编译运行几个文件之后,了解清楚了之后才可以使用IDE,要不然对于底层原理不了解,遇到问题没法自己手动排查。&/p&&p&4. 使用框架也是一样,如果不是自己写多了重复性的代码,就很难理解框架为什么要这么设计。如果不尝试几种不同的实现,就很难理解框架为了灵活性而做出的设计和扩展点。如果不写几十个权限检查语句,就很难理解AOP到底有什么好处。&/p&&p&5. 框架这么好,我该全部使用框架吗?首先只有在规模以上的程序中,才有应用框架的必要,一个简单的程序没必要使用框架,当然如果你很熟练,使用也无所谓。&/p&&p&6. 要学习一下框架的核心源代码,要为扩展框架做好准备,因为虽然框架基本上还算灵活,但是面对错综复杂的业务需求,永远不可能面面俱到,而你不了解框架的话,可能会给你实现业务需求造成麻烦。这也是有些人坚持使用Servlet+JSP原生开发,而不是用框架的理由。&/p&&p&7. 只要程序大了,归根究底还是要使用框架的,不是用别人写好的,就是自己写一套。这里我不建议自己写,不要重复造轮子,总有专业造轮子的。你草草写就的往往不如别人已经千锤百炼的代码。除非你是为了学习与研究的目的,自己写,那就是一件很好的事情。&/p&
归纳题主的问题:这个世界上有各种各样的框架,设计这些五花八门框架的初衷到底是什么?我们该不该学习框架,该如何学习使用这些框架? 回答题主的问题:一、首先,到底什么是框架?想要回答这个问题,我们要慢慢来。 ① 首先从DRY原则开始说起Don't Repeat…
C++是面向内存编程,Java是面向数据结构编程。&br&&br&C++里,内存是裸露的,可以拿到地址,随意徜徉,增了删了,没人拦你,等到跑的时候再崩给你看。&br&&br&Java里,能操作的都是设计好的数据结构,array有长度,String不可变,每一个都是安全的,在内存和程序员之间,隔着JVM,像是包住了边边角角的房间,随便小孩折腾,不会受伤。&br&&br&Java程序员是孩子,嚷嚷要这个那个,玩完了就丢,JVM是家长,买买买,还要负责收拾。有的孩子熊点,屋子很乱,收拾起来费劲,但房子还在。&br&&br&C++程序员是神,操纵着江河湖海,日月星辰,但能力越大,责任越大,万一新来的神比较愣,手一滑,宇宙就退出了。&br&&br&新手写C++,像是抱着一捆指针,在浩瀚的内存中裸奔。跑着跑着,有的针掉了,不知踪影,内存就泄露了;跑着跑着,突然被人逮住,按在地上打的error纷飞,内存就越界了;终于到了,舒了口气,把针插在脚下,念出咒语,&br&&br&“delete”&br&&br&系统就崩溃了。
C++是面向内存编程,Java是面向数据结构编程。 C++里,内存是裸露的,可以拿到地址,随意徜徉,增了删了,没人拦你,等到跑的时候再崩给你看。 Java里,能操作的都是设计好的数据结构,array有长度,String不可变,每一个都是安全的,在内存和程序员之间,…
“阻塞”与&非阻塞&与&同步&与“异步&不能简单的从字面理解,提供一个从分布式系统角度的回答。&br&&b&1.同步与异步&/b&&br&同步和异步关注的是&b&消息通信机制&/b& (synchronous communication/ asynchronous communication)&br&所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。&br&换句话说,就是由*调用者*主动等待这个*调用*的结果。&br&&br&而异步则是相反,&b&*调用*在发出之后&/b&&b&,这个调用就直接返回了,所以没有返回结果&/b&。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。&br&&br&典型的异步编程模型比如Node.js&br&&br&举个通俗的例子:&br&你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下&,然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。&br&而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。&br&&br&2. 阻塞与非阻塞&br&阻塞和非阻塞关注的是&b&程序在等待调用结果(&/b&&b&消息,&/b&&b&返回值)时的状态.&/b&&br&&br&阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。&br&非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。&br&&br&还是上面的例子,&br&你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。&br&在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。&br&&br&&br&如果是关心blocking IO/ asynchronous IO, 参考
Unix Network Programming &a href=&//link.zhihu.com/?target=http%3A//english.tebyan.net/newindex.aspx%3Fpid%3D31159%26BookID%3D23760%26PageIndex%3D92%26Language%3D3& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&View Book&i class=&icon-external&&&/i&&/a&
“阻塞”与"非阻塞"与"同步"与“异步"不能简单的从字面理解,提供一个从分布式系统角度的回答。 1.同步与异步 同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication) 所谓同步,就是在发出一个*调用*时,在没有得到结果…
为啥我要牺牲陪老婆孩子的时间来替你省学英语的时间?&br&&br&---&br&&br&没想到这个答案居然这么多赞,这真不是段子... 借此机会啰嗦一些吧:&br&&br&1. 英语好好学,这是为你自己好。英语是如今当程序员的必备技能,“英语不好也能成为好程序员” 是自欺欺人,不要为自己的懒惰找借口,更不要把这种责任转嫁到别人身上。&br&&br&2. 开源项目,不管人多人少,总是精力有限的。翻译是个体力活,不要想当然地觉得作者顺手翻译一下工作量不大... 你连看英文都嫌麻烦呢,你的麻烦就是麻烦,我的麻烦就不是麻烦了?感情我已经免费提供了一个框架还得劳心劳力把您伺候周到了才算是有责任感... 用开源项目的时候,请千万不要有这种错把自己当客户的想法。觉得缺了什么的话,先想想自己能不能出一份力,而不是作者能为我再做些什么。事无巨细地同步多语言文档,对于项目的核心开发者而言,绝对是一个高投入低回报的行为,所以绝大部分开源项目都只有英文文档。Vue 的中文、日文文档也都是由社区成员主导和维护的。对于社区成员来说,参与翻译一来锻炼英语,二来加深理解,三来可以作为自己的开源贡献资历,其实好处很多。&br&&br&3. 评论里有人说我原始答案的态度让人失望,请别搞错了:我对于为了自己方便理直气壮地向开源项目索求更多的人从来都是这种态度,但这不代表我对所有用户都是这种态度。另外,我最讨厌的就是那种在别人微博/回答下面教别人注意说话口气,说什么取关/粉转路人的人,你要转路人就转,不用特意让我知道。
为啥我要牺牲陪老婆孩子的时间来替你省学英语的时间? --- 没想到这个答案居然这么多赞,这真不是段子... 借此机会啰嗦一些吧: 1. 英语好好学,这是为你自己好。英语是如今当程序员的必备技能,“英语不好也能成为好程序员” 是自欺欺人,不要为自己的懒惰…
&b&编程范式&/b&&br&函数式编程是一种编程范式,我们常见的编程范式有&b&命令式编程(Imperative programming)&/b&,&b&函数式编程&/b&,&b&逻辑式编程&/b&,常见的面向对象编程是也是一种命令式编程。&br&&br&命令式编程是面向&b&计算机硬件&/b&的抽象,有&b&变量&/b&(对应着存储单元),&b&赋值语句&/b&(获取,存储指令),&b&表达式&/b&(内存引用和算术运算)和&b&控制语句&/b&(跳转指令),一句话,命令式程序就是一个&b&冯诺依曼机&/b&的&b&指令序列&/b&。&br&&br&而函数式编程是面向数学的抽象,将计算描述为一种&b&表达式求值&/b&,一句话,函数式程序就是一个&b&表达式&/b&。&br&&br&&b&函数式编程的本质&/b&&br&函数式编程中的&b&函数&/b&这个术语不是指计算机中的函数(实际上是&b&Subroutine&/b&),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。&br&&br&在函数式语言中,&b&函数作为一等公民&/b&,可以在任何地方定义,在函数内或函数外,可以作为函数的参数和返回值,可以对函数进行组合。&br&&br&纯函数式编程语言中的&b&变量&/b&也不是命令式编程语言中的变量,即存储状态的单元,而是代数中的变量,即一个值的名称。变量的值是&b&不可变&/b&的&b&(immutable)&/b&,也就是说不允许像命令式编程语言中那样多次给一个变量赋值。比如说在命令式编程语言我们写“x = x + 1”,这依赖可变状态的事实,拿给程序员看说是对的,但拿给数学家看,却被认为这个等式为假。&br&&br&函数式语言的如条件语句,循环语句也不是命令式编程语言中的&b&控制语句&/b&,而是函数的语法糖,比如在Scala语言中,&b&if else&/b&不是语句而是三元运算符,是有返回值的。&br&&br&严格意义上的函数式编程意味着不使用可变的变量,赋值,循环和其他命令式控制结构进行编程。&br&&br&从理论上说,函数式语言也不是通过&b&冯诺伊曼体系结构&/b&的机器上运行的,而是通过&b&λ演算&/b&来运行的,就是通过&b&变量替换&/b&的方式进行,变量替换为其值或表达式,函数也替换为其表达式,并根据运算符进行计算。λ演算是&b&图灵完全(Turing completeness)&/b&的,但是大多数情况,函数式程序还是被编译成(冯诺依曼机的)机器语言的指令执行的。&br&&br&&b&函数式编程的好处&/b&&br&由于命令式编程语言也可以通过类似函数指针的方式来实现高阶函数,函数式的最主要的好处主要是不可变性带来的。没有可变的状态,函数就是&b&引用透明(Referential transparency)&/b&的和&b&没有副作用(No S&/b&&b&ide Effect&/b&&b&)&/b&。&br&&br&一个好处是,函数即不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和位置,这样写的代码容易进行推理,不容易出错。这使得单元测试和调试都更容易。&br&&br&不变性带来的另一个好处是:由于(多个线程之间)不共享状态,不会造成&b&资源争用(Race condition)&/b&,也就不需要用&b&锁&/b&来保护可变状态,也就不会出现&b&死锁&/b&,这样可以更好地并发起来,尤其是在&b&对称多处理器&/b&(SMP)架构下能够更好地利用多个处理器(核)提供的并行处理能力。&br&&br&2005年以来,计算机计算能力的增长已经不依赖CPU主频的增长,而是依赖CPU核数的增多,如图:&br&&figure&&img src=&https://pic4.zhimg.com/fbdaf_b.jpg& data-rawwidth=&871& data-rawheight=&868& class=&origin_image zh-lightbox-thumb& width=&871& data-original=&https://pic4.zhimg.com/fbdaf_r.jpg&&&/figure&(图片来源:&a href=&//link.zhihu.com/?target=http%3A//www.gotw.ca/publications/concurrency-ddj.htm& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software&i class=&icon-external&&&/i&&/a& )&br&&br&图中&b&深蓝色的曲线是时钟周期&/b&的增长,可以看到从2005年前已经趋于平缓。 在多核或多处理器的环境下的程序设计是很困难的,难点就是在于&b&共享的可变状态&/b&。在这一背景下,这个好处就有非常重要的意义。&br&&br&由于函数是引用透明的,以及函数式编程不像命令式编程那样关注执行步骤,这个系统提供了优化函数式程序的空间,包括&b&惰性求值&/b&和并性处理。&br&&br&还有一个好处是,由于函数式语言是面向数学的抽象,更接近人的语言,而不是机器语言,代码会比较简洁,也更容易被理解。&br&&br&&b&函数式编程的特性&/b&&br&由于变量值是不可变的,对于值的操作并不是修改原来的值,而是修改新产生的值,原来的值保持不便。例如一个Point类,其moveBy方法不是改变已有Point实例的x和y坐标值,而是返回一个新的Point实例。&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&class&/span& &span class=&nc&&Point&/span&&span class=&o&&(&/span&&span class=&n&&x&/span&&span class=&k&&:&/span& &span class=&kt&&Int&/span&&span class=&o&&,&/span& &span class=&n&&y&/span&&span class=&k&&:&/span& &span class=&kt&&Int&/span&&span class=&o&&){&/span&
&span class=&k&&override&/span& &span class=&k&&def&/span& &span class=&n&&toString&/span&&span class=&o&&()&/span& &span class=&k&&=&/span& &span class=&s&&&Point (&&/span& &span class=&o&&+&/span& &span class=&n&&x&/span& &span class=&o&&+&/span& &span class=&s&&&, &&/span& &span class=&o&&+&/span& &span class=&n&&y&/span& &span class=&o&&+&/span& &span class=&s&&&)&&/span&
&span class=&k&&def&/span& &span class=&n&&moveBy&/span&&span class=&o&&(&/span&&span class=&n&&deltaX&/span&&span class=&k&&:&/span& &span class=&kt&&Int&/span&&span class=&o&&,&/span& &span class=&n&&deltaY&/span&&span class=&k&&:&/span& &span class=&kt&&Int&/span&&span class=&o&&)&/span& &span class=&k&&=&/span& &span class=&o&&{&/span&
&span class=&k&&new&/span& &span class=&nc&&Point&/span&&span class=&o&&(&/span&&span class=&n&&x&/span& &span class=&o&&+&/span& &span class=&n&&deltaX&/span&&span class=&o&&,&/span& &span class=&n&&y&/span& &span class=&o&&+&/span& &span class=&n&&deltaY&/span&&span class=&o&&)&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&(示例来源:Anders Hejlsberg在echDays 2010上的演讲)&br&&br&同样由于变量不可变,纯函数编程语言无法实现循环,这是因为&b&For循环&/b&使用可变的状态作为计数器,而&b&While循环&/b&或&b&DoWhile循环&/b&需要可变的状态作为跳出循环的条件。因此在函数式语言里就只能使用&b&递归&/b&来解决迭代问题,这使得函数式编程严重依赖递归。&br&&br&通常来说,算法都有&b&递推(iterative)&/b&和&b&递归(recursive&/b&&b&)&/b&两种定义,以阶乘为例,阶乘的递推定义为:&br&&figure&&img src=&https://pic3.zhimg.com/6c98abeda5aee_b.jpg& data-rawwidth=&82& data-rawheight=&49& class=&content_image& width=&82&&&/figure&而阶乘的递归定义&br&&figure&&img src=&https://pic1.zhimg.com/28e7d8a86b496d6181cc7c_b.jpg& data-rawwidth=&251& data-rawheight=&60& class=&content_image& width=&251&&&/figure&递推定义的计算时需要使用一个累积器保存每个迭代的中间计算结果,Java代码如下:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&n&&public&/span& &span class=&n&&static&/span& &span class=&n&&int&/span& &span class=&n&&fact&/span&&span class=&o&&(&/span&&span class=&n&&int&/span& &span class=&n&&n&/span&&span class=&o&&){&/span&
&span class=&n&&int&/span& &span class=&n&&acc&/span& &span class=&k&&=&/span& &span class=&mi&&1&/span&&span class=&o&&;&/span&
&span class=&k&&for&/span&&span class=&o&&(&/span&&span class=&n&&int&/span& &span class=&n&&k&/span& &span class=&k&&=&/span& &span class=&mi&&1&/span&&span class=&o&&;&/span& &span class=&n&&k&/span& &span class=&o&&&=&/span& &span class=&n&&n&/span&&span class=&o&&;&/span& &span class=&n&&k&/span&&span class=&o&&++){&/span&
&span class=&n&&acc&/span& &span class=&k&&=&/span& &span class=&n&&acc&/span& &span class=&o&&*&/span& &span class=&n&&k&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&k&&return&/span& &span class=&n&&acc&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&而递归定义的计算的Scala代码如下:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&def&/span& &span class=&n&&fact&/span&&span class=&o&&(&/span&&span class=&n&&n&/span&&span class=&k&&:&/span& &span class=&kt&&Int&/span&&span class=&o&&)&/span&&span class=&k&&:&/span&&span class=&kt&&Int&/span&&span class=&o&&=&/span& &span class=&o&&{&/span&
&span class=&k&&if&/span&&span class=&o&&(&/span&&span class=&n&&n&/span& &span class=&o&&==&/span& &span class=&mi&&0&/span&&span class=&o&&)&/span& &span class=&k&&return&/span& &span class=&mi&&1&/span&
&span class=&n&&n&/span& &span class=&o&&*&/span& &span class=&n&&fact&/span&&span class=&o&&(&/span&&span class=&n&&n&/span&&span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&o&&)&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&我们可以看到,没有使用循环,没有使用可变的状态,函数更短小,不需要显示地使用累积器保存中间计算结果,而是使用参数n(在栈上分配)来保存中间计算结果。}

我要回帖

更多关于 面向对象编程概念 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信