由于机器语言实在是太难编写了,于是就发展出了汇编语言。汇编语言亦称符号语言,用助记符代替机器指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数的地址,。汇编语言由于是采用了助记符号来编写程序,比用机器语言的二进制代码编程要方便些,在一定程度上简化了编程过程。例如使用LOAD来代替0000,使用STORE来代替0001。
即使汇编语言相比机器语言提升了可读性,但其本质上还是一种面向机器的语言,编写同样困难,也很容易出错。相信很多计算机毕业的学生至今都对学校的汇编课程中的练习程序心有余悸。
典型的面向过程的语言有:COBOL、FORTRAN、BASIC、C语言等。
第一次软件危机:结构化程序设计
软件危机最典型的例子莫过于IBM的System/360的操作系统开发。佛瑞德·布鲁克斯(FrederickP.Brooks,Jr.)作为项目主管,率领2000多个程序员夜以继日的工作,共计花费了5000人一年的工作量,写出将近100万行的源码,总共投入5亿美元,是美国的“曼哈顿”原子弹计划投入的1/4。尽管投入如此巨大,但项目进度却一再延迟,软件质量也得不到保障。布鲁克斯后来基于这个项目经验而总结的《人月神话》一书,成了史上最畅销的软件工程书籍。
为了解决问题,在1968、1969年连续召开两次著名的NATO会议,会议正式创造了“软件危机”一词,并提出了针对性的解决方法“软件工程”。虽然“软件工程”提出之后也曾被视为软件领域的银弹,但后来事实证明,软件工程同样无法解决软件危机。
结构化程序设计的主要特点是抛弃goto语句,采取“自顶向下、逐步细化、模块化”的指导思想。结构化程序设计本质上还是一种面向过程的设计思想,但通过“自顶向下、逐步细化、模块化”的方法,将软件的复杂度控制在一定范围内,从而从整体上降低了软件开发的复杂度。结构化程序方法成为了1970年代软件开发的潮流。
科学研究证明,人脑存在人类短期记忆一般一次只能记住5-9个事物,这就是著名的7+-2原理。结构化程序设计是面向过程设计思想的一个改进,使得软件开发更加符合人类思维的7+-2特点。
第二次软件危机:面向对象程序设计
结构化编程的风靡在一定程度上缓解了软件危机,然而好景不长,随着硬件的快速发展,业务需求越来越
复杂,以及编程应用领域越来越广泛,第二次软件危机很快就到来了。
第二次软件危机的根本原因还是在于软件生产力远远跟不上硬件和业务的发展,相比第一次软件危机主要体现在“复杂性”,第二次软件危机主要体现在“可扩展性”、“可维护性”上面。传统的面向过程(包括结构化程序设计)方法已经越来越不能适应快速多变的业务需求了,软件领域迫切希望找到新的银弹来解决软件危机,在这种背景下,面向对象的思想开始流行起来。
面向对象的思想并不是在第二次软件危机后才出现的,早在1967年的Simula语言中就开始提出来了,但第二次软件危机促进了面向对象的发展。面向对象真正开始流行是在1980s年代,主要得益于C++的功劳,后来的Java、C#把面向对象推向了新的高峰。到现在为止,面向对象已经成为了主流的开发思想。
虽然面向对象开始也被当做解决软件危机的银弹,但事实证明,和软件工程一样,面向对象也不是银弹,而只是一种新的软件方法而已。
虽然面向对象并不是解决软件危机的银弹,但和面向过程相比,面向对象的思想更加贴近人类思维的特点,更加脱离机器思维,是一次软件设计思想上的飞跃。
1940之前:
第一个编程语言比现代的计算机还早诞生。首先,这种语言是种编码(en:code)。
于1801年发明的提花织布机(或称甲卡提花织布机),运用打孔卡上的坑洞来代表缝纫织布机的手臂动作,以便自动化产生装饰的图案。
HermanHollerith在观察列车长对乘客票根在特定位置打洞的方式后,意识到他可以把信息编码记载到打孔卡上,随后根据这项发现使用打孔卡来编码并纪录1890年的人口统计数据。
就像许多历史上的"第一次"一样,第一个现代编程语言也很难界定。最一开始是因为硬件限制而限定了语言,打孔卡允许80行(column)的长度,但某几行必须用来记录卡片的顺序。FORTRAN则纳入了一些与英文字词相同的关键字,像是"IF"、"GOTO"(原字词为goto),以及"CONTINUE"。之后采用磁鼓(magneticdrum)作为存储器使用,也代表计算机程序也必须插入(interleave)到磁鼓的转动(rotation)中。和现今比较起来,这也让编程语言必须更加依赖硬件(hardware-dependent)。
对部分的人认为必须在"编程语言"的状态确立之前,根据能力(power)以及可读性(human-readability)的程度来决定历史上第一个编程语言是什么语言。提花织布机和查尔斯·巴贝奇所制作的差分机(en:DifferenceEngine)都具备在大量限制下,简单描述机器应运行行为的语言。也有种并非设计给人类运用的受限特定领域语言(en:domain-specificlanguage),是将打孔卡运用到自动演奏钢琴(en:playerpiano)上。
1940年代:
最早被确认的现代化、电力引导(electricallypowered)的计算机约在1940年代被创造出来。程序员在有限的速度及存储器容量限制之下,撰写人工调整(handtuned)过的汇编语言程序。而且很快就发现到使用汇编语言的这种撰写方式需要花费大量的脑力(intellectualeffort)而且很容易出错(error-prone)。
康拉德·楚泽于1948年发表了他所设计的Plankalkül编程语言的论文[1]。但是在他有生之年却未能将该语言实现,而他原本的贡献也被其他的发展所孤立。
在这段期间被开发出来的重要语言包括有:
1950与1960年代:
有三个现代编程语言于1950年代被设计出来,这三者所派生的语言直到今日仍旧广泛地被采用:
另一个1950年代晚期的里程碑是由美国与欧洲计算机学者针对"算法的新语言"所组成的委员会出版的ALGOL60报告(名称取自"ALGOrithmicLanguage"(算法语言))。这份报告强化了当时许多关于计算的想法,并提出了两个语言上的创新功能:
另一个创新则是关于语言的描述方式:
Algol60对之后语言的设计上带来了特殊的影响,部分的语言很快的就被广泛采用。后续为了开发Algol的扩充子集合,设计了一个名为Burroughs(en:Burroughslargesystems)的大型系统。
延续Algol的关键构想所产生的成果就是ALGOL68:
Algol68一些较少被使用到的语言功能(如同步与并行区块)、语法快捷方式的复杂系统,以及类型自动强制转换(coercions),使得实现者兴趣缺缺,也让Algol68获得了很难用(diffcult)的名声。尼克劳斯·维尔特就干脆离开该设计委员会,另外再开发出更简单的Pascal语言。
1967-1978:确立了基础范式
1960年代晚期至1970年代晚期的期间中,编程语言的发展也有了重大的成果。大多数现在所使用的主要语言范式都是在这段期间中发明的:
这些语言都各自演展出自己的家族分支,现今多数现代编程语言的祖先都可以追溯他们其中至少一个以上。
在1960年代以及1970年代中结构化程序设计的优点也带来许多的争议,特别是在程序开发的过程中完全不使用GOTO。这项争议跟语言本身的设计非常有关系:某些语言并没有包含GOTO,这也强迫程序员必须结构化地编写程序。尽管这个争议在当时吵翻了天,但几乎所有的程序员都同意就算语言本身有提供GOTO的功能,在除了少数罕见的情况下去使用GOTO是种不良的程序风格。结果是之后世代的编程语言设计者发觉到结构化编程语言的争议实在既乏味又令人眼花撩乱。
1980年代:增强、模块、性能
1980年代的编程语言与之前相较显得更为强大。C++合并了面向对象以及系统程序设计。美国政府标准化一种名为Ada的系统编程语言并提供给国防承包商使用。日本以及其他地方运用了大量的资金对采用逻辑编程语言结构的第五代语言进行研究。函数编程语言社区则把焦点转移到标准化ML及Lisp身上。这些活动都不是在开发新的范式,而是在将上个世代发明的构想进一步发扬光大。
然而,在语言设计上有个重大的新趋势,就是研究运用模块或大型组织化的程序单元来进行大型系统的开发。Modula、Ada,以及ML都在1980年代发展出值得注意的模块化系统。模块化系统常拘泥于采用泛型程序设计结构:泛型存在(genericsbeing)、本质(essence),参数化模块(parameterizedmodules)。(参阅多态)
尽管没有出现新的主要编程语言范式,许多研究人员仍就扩充之前语言的构想并将它们运用到新的内容上。举例来说,Argus以及Emerald系统的语言配合面向对象语言运用到分布式系统上。
语言技术持续这些发展并迈入了1990年代。
1990年代:互联网时代
现今的趋势
编程语言持续在学术及企业两个层面中发展进化,目前的一些趋势包含有:
编程语言发展史上的杰出人物
面向过程的程序设计:核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么......面向过程的设计就好比精心设计好一条流水线,是一种机械式的思维方式。
优点是:复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)
缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及ApacheHTTPServer等。
面向对象的程序设计:核心是对象二字,(要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的数据属性和方法属性),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙交互着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取),对象是特征与技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界的模拟,是一种“上帝式”的思维方式。
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:
1.编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
2.无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。
类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体
那么问题来了,先有的一个个具体存在的对象(比如一个具体存在的人),还是先有的人类这个概念,这个问题需要分两种情况去看
在现实世界中:先有对象,再有类
世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如人类、动物类、植物类等概念
也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在
在程序中:务必保证先定义类,后产生对象
这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类
不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象
按照上述步骤,我们来定义一个类(我们站在老男孩学校的角度去看,在座的各位都是学生)
1.站的角度不同,定义出的类是截然不同的,详见面向对象实战之需求分析
2.现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类......
3.有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类
#python为类内置的特殊属性类名.__name__#类的名字(字符串)类名.__doc__#类的文档字符串类名.__base__#类的第一个父类(在讲继承时会讲)类名.__bases__#类所有父类构成的元组(在讲继承时会讲)类名.__dict__#类的字典属性类名.__module__#类定义所在的模块类名.__class__#实例对应的类(仅新式类中)类的特殊属性(了解即可)!!!补充说明:从代码级别看面向对象!!!
1.类的数据属性是所有对象共享的
2.类的函数属性是绑定给对象用的
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
练习:编写一个学生类,产生一堆学生对象,要求有一个计数器(属性),统计总共实例了多少个对象
#改写classOldboyStudent:school='oldboy'def__init__(self,name,age,sex):self.name=nameself.age=ageself.sex=sexdeflearn(self):print('%sislearning'%self.name)#新增self.namedefeat(self):print('%siseating'%self.name)defsleep(self):print('%sissleeping'%self.name)s1=OldboyStudent('李坦克','男',18)s2=OldboyStudent('王大炮','女',38)s3=OldboyStudent('牛榴弹','男',78)类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数
OldboyStudent.learn(s1)#李坦克islearningOldboyStudent.learn(s2)#王大炮islearningOldboyStudent.learn(s3)#牛榴弹islearning类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法
强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)
s1.learn()#等同于OldboyStudent.learn(s1)s2.learn()#等同于OldboyStudent.learn(s2)s3.learn()#等同于OldboyStudent.learn(s3)注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。
类即类型
提示:python的class术语与c++有一定区别,与Modula-3更像。
python中一切皆为对象,且python3中类与类型是一个概念,类型就是类
classRiven:camp='Noxus'#所有玩家的英雄(锐雯)的阵营都是Noxus;def__init__(self,nickname,aggressivity=54,life_value=414):#英雄的初始攻击力54;self.nickname=nickname#为自己的锐雯起个别名;self.aggressivity=aggressivity#英雄都有自己的攻击力;self.life_value=life_value#英雄都有自己的生命值;defattack(self,enemy):#普通攻击技能,enemy是敌人;enemy.life_value-=self.aggressivity#根据自己的攻击力,攻击敌人就减掉敌人的生命值。实例出俩英雄
>>>g1=Garen('草丛伦')>>>r1=Riven('锐雯雯')交互:锐雯雯攻击草丛伦,反之一样
>>>g1.life_value455>>>r1.attack(g1)>>>g1.life_value401补充:
garen_hero.Q()称为向garen_hero这个对象发送了一条消息,让他去执行Q这个功能,类似的有:
garen_hero.W()
garen_hero.E()
garen_hero.R()
classRiven:camp='Noxus'def__init__(self,nickname,aggressivity=54,life_value=414,money=1001,armor=3):self.nickname=nicknameself.aggressivity=aggressivityself.life_value=life_valueself.money=moneyself.armor=armordefattack(self,enemy):damage_value=self.aggressivity-enemy.armorenemy.life_value-=damage_value定义盖文类:
classGaren:camp='Demacia'def__init__(self,nickname,aggressivity=58,life_value=455,money=100,armor=10):self.nickname=nicknameself.aggressivity=aggressivityself.life_value=life_valueself.money=moneyself.armor=armordefattack(self,enemy):damage_value=self.aggressivity-enemy.armorenemy.life_value-=damage_value定义装备:
classBlackCleaver:def__init__(self,price=475,aggrev=9,life_value=100):self.price=priceself.aggrev=aggrevself.life_value=life_valuedefupdate(self,obj):obj.money-=self.price#减钱obj.aggressivity+=self.aggrev#加攻击obj.life_value+=self.life_value#加生命值deffire(self,obj):#这是该装备的主动技能,喷火,烧死对方obj.life_value-=1000#假设火烧的攻击力是1000测试交互
r1=Riven('草丛伦')g1=Garen('盖文')b1=BlackCleaver()print(r1.aggressivity,r1.life_value,r1.money)#r1的攻击力,生命值,护甲ifr1.money>b1.price:r1.b1=b1b1.update(r1)print(r1.aggressivity,r1.life_value,r1.money)#r1的攻击力,生命值,护甲print(g1.life_value)r1.attack(g1)#普通攻击print(g1.life_value)r1.b1.fire(g1)#用装备攻击print(g1.life_value)#g1的生命值小于0就死了按照这种思路一点一点的设计类和对象,最终你完全可以实现一个对战类游戏。
什么是继承
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
子类会“”遗传”父类的属性,从而解决代码重用问题(比如练习7中Garen与Riven类有很多冗余的代码)
python中类的继承分为:单继承和多继承
classParentClass1:#定义父类passclassParentClass2:#定义父类passclassSubClass1(ParentClass1):#单继承,基类是ParentClass1,派生类是SubClasspassclassSubClass2(ParentClass1,ParentClass2):#python支持多继承,用逗号分隔开多个继承的类pass查看继承
>>>SubClass1.__bases__#__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类(
>>>ParentClass1.__bases__(
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
classHero:def__init__(self,nickname,aggressivity,life_value):self.nickname=nicknameself.aggressivity=aggressivityself.life_value=life_valuedefmove_forward(self):print('%smoveforward'%self.nickname)defmove_backward(self):print('%smovebackward'%self.nickname)defmove_left(self):print('%smoveforward'%self.nickname)defmove_right(self):print('%smoveforward'%self.nickname)defattack(self,enemy):enemy.life_value-=self.aggressivityclassGaren(Hero):passclassRiven(Hero):passg1=Garen('草丛伦',100,300)r1=Riven('锐雯雯',57,200)print(g1.life_value)r1.attack(g1)print(g1.life_value)'''运行结果243'''ViewCode提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大生了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.
注意:像g1.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。
重点!!!:再看属性查找
classFoo:deff1(self):print('Foo.f1')deff2(self):print('Foo.f2')self.f1()classBar(Foo):deff1(self):print('Foo.f1')b=Bar()b.f2()ViewCode8.4派生当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
classRiven(Hero):camp='Noxus'defattack(self,enemy):#在自己这里定义新的attack,不再使用父类的attack,且不会影响父类print('fromriven')deffly(self):#在自己这里定义新的print('%sisflying'%self.nickname)在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值
classRiven(Hero):camp='Noxus'def__init__(self,nickname,aggressivity,life_value,skin):Hero.__init__(self,nickname,aggressivity,life_value)#调用父类功能self.skin=skin#新属性defattack(self,enemy):#在自己这里定义新的attack,不再使用父类的attack,且不会影响父类Hero.attack(self,enemy)#调用功能print('fromriven')deffly(self):#在自己这里定义新的print('%sisflying'%self.nickname)r1=Riven('锐雯雯',57,200,'比基尼')r1.fly()print(r1.skin)'''运行结果锐雯雯isflying比基尼'''8.5组合与重用性软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
>>>classEquip:#武器装备类...deffire(self):...print('releaseFireskill')...>>>classRiven:#英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类...camp='Noxus'...def__init__(self,nickname):...self.nickname=nickname...self.equip=Equip()#用Equip类产生一个装备,赋值给实例的equip属性...>>>r1=Riven('锐雯雯')>>>r1.equip.fire()#可以使用组合的类产生的对象所持有的方法releaseFireskill组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
1.继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...
classPeople:def__init__(self,name,age,sex):self.name=nameself.age=ageself.sex=sexclassCourse:def__init__(self,name,period,price):self.name=nameself.period=periodself.price=pricedeftell_info(self):print('<%s%s%s>'%(self.name,self.period,self.price))classTeacher(People):def__init__(self,name,age,sex,job_title):People.__init__(self,name,age,sex)self.job_title=job_titleself.course=[]self.students=[]classStudent(People):def__init__(self,name,age,sex):People.__init__(self,name,age,sex)self.course=[]egon=Teacher('egon',18,'male','沙河霸道金牌讲师')s1=Student('牛榴弹',18,'female')python=Course('python','3mons',3000.0)linux=Course('python','3mons',3000.0)#为老师egon和学生s1添加课程egon.course.append(python)egon.course.append(linux)s1.course.append(python)#为老师egon添加学生s1egon.students.append(s1)#使用forobjinegon.course:obj.tell_info()例子:继承与组合当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
1.什么是接口
2.为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处在于:
1.归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2.归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
2.1:就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
2.2:再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
3.模仿interface
在python中根本就没有一个叫做interface的关键字,如果非要去模仿接口的概念
可以借助第三方模块:
也可以使用继承:
继承的两种用途
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
classInterface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。defread(self):#定接口函数readpassdefwrite(self):#定义接口函数writepassclassTxt(Interface):#文本,具体实现read和writedefread(self):print('文本数据的读取方法')defwrite(self):print('文本数据的读取方法')classSata(Interface):#磁盘,具体实现read和writedefread(self):print('硬盘数据的读取方法')defwrite(self):print('硬盘数据的读取方法')classProcess(Interface):defread(self):print('进程数据的读取方法')defwrite(self):print('进程数据的读取方法')ViewCode上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口,这就用到了抽象类
1什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
2为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
3.在python中实现抽象类
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
1继承顺序
在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)
如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性
如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先
classA(object):deftest(self):print('fromA')classB(A):deftest(self):print('fromB')classC(A):deftest(self):print('fromC')classD(B):deftest(self):print('fromD')classE(C):deftest(self):print('fromE')classF(D,E):#deftest(self):#print('fromF')passf1=F()f1.test()print(F.__mro__)#只有新式才有这个属性可以查看线性列表,经典类没有这个属性#新式类继承顺序:F->D->B->E->C->A#经典类继承顺序:F->D->B->A->E->C#python3中统一都是新式类#pyhon2中才分新式类与经典类继承顺序2继承原理(python如何实现的继承)
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>>F.mro()#等同于F.__mro__[
方法一:指名道姓,即父类名.父类方法()
#_*_coding:utf-8_*___author__='Linhaifeng'classVehicle:#定义交通工具类Country='China'def__init__(self,name,speed,load,power):self.name=nameself.speed=speedself.load=loadself.power=powerdefrun(self):print('开动啦...')classSubway(Vehicle):#地铁def__init__(self,name,speed,load,power,line):Vehicle.__init__(self,name,speed,load,power)self.line=linedefrun(self):print('地铁%s号线欢迎您'%self.line)Vehicle.run(self)line13=Subway('中国地铁','180m/s','1000人/箱','电',13)line13.run()ViewCode方法二:super()
classVehicle:#定义交通工具类Country='China'def__init__(self,name,speed,load,power):self.name=nameself.speed=speedself.load=loadself.power=powerdefrun(self):print('开动啦...')classSubway(Vehicle):#地铁def__init__(self,name,speed,load,power,line):#super(Subway,self)就相当于实例本身在python3中super()等同于super(Subway,self)super().__init__(name,speed,load,power)self.line=linedefrun(self):print('地铁%s号线欢迎您'%self.line)super(Subway,self).run()classMobike(Vehicle):#摩拜单车passline13=Subway('中国地铁','180m/s','1000人/箱','电',13)line13.run()ViewCode强调:二者使用哪一种都可以,但最好不要混合使用
了解部分:
即使没有直接继承关系,super仍然会按照mro继续往后查找
#A没有继承B,但是A内super会基于C.mro()继续往后找classA:deftest(self):super().test()classB:deftest(self):print('fromB')classC(A,B):passc=C()c.test()#打印结果:fromBprint(C.mro())#[
多态指的是一类事物有多种形态
动物有多种形态:人,狗,猪
importabcclassAnimal(metaclass=abc.ABCMeta):#同一类事物:动物@abc.abstractmethoddeftalk(self):passclassPeople(Animal):#动物的形态之一:人deftalk(self):print('sayhello')classDog(Animal):#动物的形态之二:狗deftalk(self):print('saywangwang')classPig(Animal):#动物的形态之三:猪deftalk(self):print('sayaoao')文件有多种形态:文本文件,可执行文件
importabcclassFile(metaclass=abc.ABCMeta):#同一类事物:文件@abc.abstractmethoddefclick(self):passclassText(File):#文件的形态之一:文本文件defclick(self):print('openfile')classExeFile(File):#文件的形态之二:可执行文件defclick(self):print('executefile')9.2多态性一什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)
多态性是指在不考虑实例类型的情况下使用实例
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同详细解释多态性分为静态多态性和动态多态性
静态多态性:如任何类型都可以用运算符+进行运算
动态多态性:如下
peo=People()dog=Dog()pig=Pig()#peo、dog、pig都是动物,只要是动物肯定有talk方法#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用peo.talk()dog.talk()pig.talk()#更进一步,我们可以定义一个统一的接口来使用deffunc(obj):obj.talk()二为什么要用多态性(多态性的好处)
其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的,这么做的好处是什么呢?
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
2.增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
>>>classCat(Animal):#属于动物的另外一种形态:猫...deftalk(self):...print('saymiao')...>>>deffunc(animal):#对于使用者来说,自己的代码根本无需改动...animal.talk()...>>>cat1=Cat()#实例出一只猫>>>func(cat1)#甚至连调用方式也无需改变,就能调用猫的talk功能saymiao'''这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)'''三鸭子类型
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用classTxtFile:defread(self):passdefwrite(self):passclassDiskFile:defread(self):passdefwrite(self):passViewCode例2:其实大家一直在享受着多态性带来的好处,比如Python的序列类型有多种形态:字符串,列表,元组,多态性体现如下
#str,list,tuple都是序列类型s=str('hello')l=list([1,2,3])t=tuple((4,5,6))#我们可以在不考虑三者类型的前提下使用s,l,ts.__len__()l.__len__()t.__len__()len(s)len(l)len(t)ViewCode十封装10.1引子从封装本身的意思去理解,封装就好像是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,然后把麻袋封上口子。照这种逻辑看,封装=‘隐藏’,这种理解是相当片面的
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
#其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形#类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式:classA:__N=0#类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__Ndef__init__(self):self.__X=10#变形为self._A__Xdef__foo(self):#变形为_A__fooprint('fromA')defbar(self):self.__foo()#只有在类内部才可以通过__foo的形式访问到.#A._A__N是可以访问到的,#这种,在外部是无法通过__x这个名字访问到。这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。
2.变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形
3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况>>>classA:...deffa(self):...print('fromA')...deftest(self):...self.fa()...>>>classB(A):...deffa(self):...print('fromB')...>>>b=B()>>>b.test()fromB#把fa定义成私有的,即__fa>>>classA:...def__fa(self):#在定义时就变形为_A__fa...print('fromA')...deftest(self):...self.__fa()#只会与自己所在的类为准,即调用_A__fa...>>>classB(A):...def__fa(self):...print('fromB')...>>>b=B()>>>b.test()fromAViewCode10.3封装不是单纯意义的隐藏封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,让外部能够间接地用到我们隐藏起来的属性,那这么做的意义何在???
1:封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
classTeacher:def__init__(self,name,age):#self.__name=name#self.__age=ageself.set_info(name,age)deftell_info(self):print('姓名:%s,年龄:%s'%(self.__name,self.__age))defset_info(self,name,age):ifnotisinstance(name,str):raiseTypeError('姓名必须是字符串类型')ifnotisinstance(age,int):raiseTypeError('年龄必须是整型')self.__name=nameself.__age=aget=Teacher('egon',18)t.tell_info()t.set_info('egon',19)t.tell_info()ViewCode2:封装方法:目的是隔离复杂度
封装方法举例:
1.你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
2.电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!
3.快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做#隔离了复杂度,同时也提升了安全性classATM:def__card(self):print('插卡')def__auth(self):print('用户认证')def__input(self):print('输入取款金额')def__print_bill(self):print('打印账单')def__take_money(self):print('取款')defwithdraw(self):self.__card()self.__auth()self.__input()self.__print_bill()self.__take_money()a=ATM()a.withdraw()隔离复杂度的例子3:了解
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么frommoduleimport*时不能被导入,但是你frommoduleimport_private_module依然是可以导入的
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点
python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__,详见面向对象进阶
什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
classPeople:def__init__(self,name,weight,height):self.name=nameself.weight=weightself.height=height@propertydefbmi(self):returnself.weight/(self.height**2)p1=People('egon',75,1.85)print(p1.bmi)ViewCode例二:圆的周长和面积
importmathclassCircle:def__init__(self,radius):#圆的半径radiusself.radius=radius@propertydefarea(self):returnmath.pi*self.radius**2#计算面积@propertydefperimeter(self):return2*math.pi*self.radius#计算周长c=Circle(10)print(c.radius)print(c.area)#可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值print(c.perimeter)#同上'''输出结果:314.159265358979362.83185307179586'''ViewCode#注意:此时的特性arear和perimeter不能被赋值c.area=3#为特性area赋值'''抛出异常:AttributeError:can'tsetattribute'''为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
除此之外,看下
ps:面向对象的封装有三种方式:【public】这种其实就是不封装,是对外公开的【protected】这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开【private】这种封装对谁都不公开python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
1.绑定到类的方法:用classmethod装饰器装饰的方法。
为类量身定制
类.boud_method(),自动将类当作第一个参数传入
(其实对象也可调用,但仍将类当作第一个参数传入)
2.绑定到对象的方法:没有被任何装饰器装饰的方法。
为对象量身定制
对象.boud_method(),自动将对象当作第一个参数传入
(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
二:非绑定方法:用staticmethod装饰器装饰的方法
1.不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
绑定给对象的方法(略)
绑定给类的方法(classmethod)
classmehtod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(即便是对象来调用也会将类当作第一个参数传入),python为我们内置了函数classmethod来把类中的函数定义成类方法
HOST='127.0.0.1'PORT=3306DB_PATH=r'C:\Users\Administrator\PycharmProjects\test\面向对象编程\test1\db'settings.pyimportsettingsclassMySQL:def__init__(self,host,port):self.host=hostself.port=port@classmethoddeffrom_conf(cls):print(cls)returncls(settings.HOST,settings.PORT)print(MySQL.from_conf)#
statimethod不与类或对象绑定,谁都可以调用,没有自动传值效果
1.对象有id、host、port三个属性
2.定义工具create_id,在实例化时为每个对象随机生成id,保证id唯一
3.提供两种实例化方式,方式一:用户传入host和port方式二:从配置文件中读取host和port进行实例化
4.为对象定制方法,save和get_obj_by_id,save能自动将对象序列化到文件中,文件路径为配置文件中DB_PATH,文件名为id号,保存之前验证对象是否已经存在,若存在则抛出异常,;get_obj_by_id方法用来从文件中反序列化出对象
2.很多人喜欢说面向对象三大特性(这是从哪传出来的,封装,多态,继承?漏洞太多太多,好吧暂且称为三大特性),那么我在基于面向对象编程时,我一定要让我定义的类中完整的包含这三种特性,这样写肯定是好的程序
好家伙,我说降龙十八掌有十八掌,那么你每次跟人干仗都要从第一掌打到第18掌这才显得你会了是么:面对敌人,你打到第三掌对方就已经倒下了,你说,不行,你给老子起来,老子还没有show完...
3.类有类属性,实例有实例属性,所以我们在定义class时一定要定义出那么几个类属性,想不到怎么办,那就使劲的想,定义的越多越牛逼
这就犯了一个严重的错误,程序越早面向对象,死的越早,为啥面向对象,因为我们要将数据与功能结合到一起,程序整体的结构都没有出来,或者说需要考虑的问题你都没有搞清楚个八九不离十,你就开始面向对象了,这就导致了,你在那里干想,自以为想通了,定义了一堆属性,结果后来又都用不到,或者想不通到底应该定义啥,那就一直想吧,想着想着就疯了。
你见过哪家公司要开发一个软件,上来就开始写,肯定是频繁的开会讨论计划,请看第八节
4.既然这么麻烦,那么我彻底解脱了,我们不要用面向对象编程了,你啊,你有大才,你能成事啊,傻叉。
抽象/实现
封装/接口
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的述,使得多个不同的类合成为一个大的类,来解决现实问题。合成述了一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为,所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。继承描述了子类属性从祖先类继承这样一种方式继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承泛化表示所有子类与其父类及祖先类有一样的特点。特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__
很多人在学完了python的class机制之后,遇到一个生产中的问题,还是会懵逼,这其实太正常了,因为任何程序的开发都是先设计后编程,python的class机制只不过是一种编程方式,如果你硬要拿着class去和你的问题死磕,变得更加懵逼都是分分钟的事,在以前,软件的开发相对简单,从任务的分析到编写程序,再到程序的调试,可以由一个人或一个小组去完成。但是随着软件规模的迅速增大,软件任意面临的问题十分复杂,需要考虑的因素太多,在一个软件中所产生的错误和隐藏的错误、未知的错误可能达到惊人的程度,这也不是在设计阶段就完全解决的。
所以软件的开发其实一整套规范,我们所学的只是其中的一小部分,一个完整的开发过程,需要明确每个阶段的任务,在保证一个阶段正确的前提下再进行下一个阶段的工作,称之为软件工程
面向对象的软件工程包括下面几个部:
1.面向对象分析(objectorientedanalysis,OOA)
软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。
建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。
2面向对象设计(objectorienteddesign,OOD)
根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。
首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。
在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述
3面向对象编程(objectorientedprogramming,OOP)
根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python
4面向对象测试(objectorientedtest,OOT)
在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。
面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。
5面向对象维护(objectoriendtedsoftmaintenance,OOSM)
正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。
由于使用了面向对象的方法开发程序,使用程序的维护比较容易。
因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。
在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。
现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。