关于面向对象设计模式:面向对象设计原则

开闭准则(Open Closed Principle,OCP):当利用的需要扭转时,在不批改软件实体的源代码或者二进制代码的前提下,能够扩大模块的性能,使其满足新的需要。 里氏替换准则(Liskov Substitution Principle,LSP):子类能够扩大父类的性能,但不能扭转父类原有的性能。也就是说:子类继承父类时,除增加新的办法实现新增性能外,尽量不要重写父类的办法。 依赖倒置准则(Dependence Inversion Principle,DIP):高层模块不应该依赖低层模块,两者都应该依赖其形象;形象不应该依赖细节,细节应该依赖形象。其核心思想是:要面向接口编程,不要面向实现编程。 违反依赖倒置准则代码(顾客每更换一家商店,都要批改一次代码,这显著违反了开闭准则。存在以上毛病的起因是:顾客类设计时同具体的商店类绑定了,这违反了依赖倒置准则): class Customer{ public void shopping(ShaoguanShop shop){ //购物 System.out.println(shop.sell()); }}class Customer{ public void shopping(WuyuanShop shop){ //购物 System.out.println(shop.sell()); }}批改后的合乎依赖倒置准则代码(定义“婺源网店”和“韶关网店”的独特接口 Shop,顾客类面向该接口编程,其代码批改如下:): public class DIPtest{ public static void main(String[] args){ Customer wang=new Customer(); System.out.println("顾客购买以下商品:"); wang.shopping(new ShaoguanShop()); wang.shopping(new WuyuanShop()); }}//商店interface Shop{ public String sell(); //卖}//韶关网店class ShaoguanShop implements Shop{ public String sell(){ return "韶关土特产:香菇、木耳……"; } }//婺源网店class WuyuanShop implements Shop{ public String sell(){ return "婺源土特产:绿茶、酒糟鱼……"; }} //顾客class Customer{ public void shopping(Shop shop){ //购物 System.out.println(shop.sell()); }}繁多职责准则(Single Responsibility Principle,SRP):繁多职责准则规定一个类应该有且仅有一个引起它变动的起因,否则类应该被拆分 ...

November 26, 2021 · 1 min · jiezi

关于面向对象设计模式:又是面向对象的一天

521了,就聊点和对象相干的吧~背景如果说要聊下面向对象(Object Oriented),那不得不提的就是和它绝对的另一个概念:面向过程(Procedure Oriented)。而面向对象产生的背景则是设计思维和理念的提高,以及零碎的日益简单,人们缓缓发现面向过程这种形式曾经不再适宜解决现有问题了,于是缓缓催生了面向对象这一概念。 而这两个概念之间的差别点,我认为外围在于剖析问题和对待问题的角度。 面向过程是偏差于过程的,对于一个问题,面向过程会将其拆解为一个个步骤,而后一步步执行,执行完了问题也就解决了。代表语言可能有 C语言, 这里说可能是因为C也能够定义构造体,也算肯定水平上狭义的对象,但整体上它还是面向过程的语言。 面向对象在解决一个问题时候,则是偏向于形象进去一个实体,而后将其作为一个整体或多个整体进行思考,而后解决问题。代表语言有 Java (老牌咖啡了)。 这样说可能有点绕,通晓区别的不看这篇文章也能了解,不通晓的可能看了这两句话也稍稍迷一点。 例子咱们无妨举个谈到面向对象都谈判到的一个问题:如何把一个大象放到冰箱里?。 面向过程首先咱们写个办法找到这个大象咱们执行办法,找到大象,失去他的坐标、大小等参数定义一个挪动大象的办法,接管 步骤2 中的参数,把大象移到冰箱前定义一个管制冰箱门的办法 调用 步骤4 的办法,传入 “开” 关上冰箱门调用 步骤3 的办法,大象挪动到冰箱里调用 步骤4 的办法,传入 “关” 敞开冰箱门咱们能够看到,以上的解决问题的办法就是有一个个步骤/过程组合而成的,而解决问题则是通过依照肯定的顺序调用办法进行。整体都是面向过程的。 面向对象那面向对象怎么该怎么解决这个问题呢? 首先咱们剖析下这个问题波及到哪些实体,这个问题中波及到了 冰箱 和 大象。 所以咱们就能够依照以下的形式进行了~ 定义两个对象:大象 & 冰箱给大象定义“行走”的办法(大象必定会走的嘛)给冰箱定义“开门” 和 “关门”的办法而后咱们就只须要 冰箱.开门 => 大象.行走(冰箱里) (让大象走到冰箱里) => 冰箱.关门 就ok了 整体咱们都是把二者作为了对象去对待,就像在事实中看待两个事物一样。 通过比照,咱们就明确面向对象和面向过程二者的区别啦~ 利用当初很多时候,说道面向对象大家就说Java;有的也说面向对象就是 Java。 这必定是错的,就像聊到区块链就说是比特币一样。 面向对象是一种思维,而 Java 则只是它的一种在编程语言方面的利用。而它的利用绝不仅仅于此,不仅 Java,当初对于数据库的设计、零碎层面的功能设计处处都是面向对象的利用。而对于这种思维的使用,还须要咱们有肯定的数据抽象能力。 咱们举个人物身份如何在软件系统中形象的例子,首先咱们把人的共有属性找进去,作为所有角色共有的;而后咱们再在这个根底下来向具体的工作角色具体,而后依据他们的不同职业去增加特有的属性或办法。这在 Java 中也就是封装和继承的体现。 多态则是说同一个动作(办法),不同实体有不同的体现,比如说上图中警察和歌手,都能够唱歌(办法),然而警察的唱歌可能是唱军歌,歌手的唱歌则可能是唱单身情歌。同一个办法在不同的实体有不同的体现。 另外,咱们在进行一个零碎开发设计的过程中,也不乏有面向对象的体现。比如说,咱们要写一个树洞小程序。 首先咱们要剖析,它波及到哪些对象:用户、动静、评论等(简略举这几个)。 而后对于用户,他的属性就有:账号、明码、角色、邮箱、昵称等等等。 对于动静,它就有谁发的(用户)、内容、公布工夫、更新工夫、状态等等。顺次类推,咱们零碎的库表构造根本就进去了,这里说的简略哈,具体问题具体分析。 而后对于办法,咱们围绕动静开展,就有公布、删除、查找等等一系列,这样围绕几个零碎的对象顺次开展,天然零碎性能根本也就列举进去了。当然到具体零碎还是须要具体流程具体分析。还是要同具体的业务逻辑挂钩滴。 定位那面向对象的定位是什么呢?目前面向对象根本能够解决大多数场景下的问题,长处也非常显著:结构性强、易拓展、易复用等。然而他也有一些毛病,比如说如果问题较小,齐全为了面向对象而面向对象,则会比拟“重”一些。 这货色就是编程倒退的必然产物,但也可能是两头产物。但就目前,这种思维还是能够滴! 之后可能会围绕Java的再谈一下面向对象在Java语言中的一些理念和准则,写个小短文,能够点个关注哦~ 感觉文章哪里有问题的小伙伴,能够点击查看原文滑到页面底部进行留言(没方法,公众号太年老了,没这个性能了) ...

May 21, 2021 · 1 min · jiezi

浅析面向过程与面向对象

在面试时经常会被问到面向过程和面向对象有什么区别,虽然都是编程的一种思想,但是他们的侧重点不同,我们从以下几个方面进行简单总结。历史面向过程的编程语言有汇编语言、C语言。C语言,是1972年贝尔实验室的 D.M.Ritchie 在B语言的基础上设计出的一种新的语言。他们的特点就是太底层了,当你在使用面向过程的编程语言编写代码的时候,你就需要把思维转换成机器的思维,时刻要考虑开辟多大的内存,存储多大的数据,数据在使用完毕的后什么时间释放,这样写代码学习成本太高,开发效率低,不适合编程的推广与应用。 所以渐渐的就发展出来了更友好地面向对象编程语言,面向对象编程思想是很早就提出来早在1967年的时候,在挪威计算中心的Kisten Nygaard和Ole Johan Dahl开发了Simula67语言,它提供了比子程序更高一级的抽象和封装,引入了数据抽象和类的概念,这种语言被认为是第一个面向对象语言。在20世纪80年代初期美国AT&T贝尔实验室的本贾尼.斯特劳斯特卢普(Bjarne Stroustrup)博士发明并实现了C++(最初这种语言被称作“C with Classes”)。一开始C++是作为C语言的增强版出现的,从给C语言增加类开始,不断的增加新特性。目前主流的面向对象编程语言有:Java、C++、Object-C、 JavaScript、Python、Go等,降低了学习成本,易于推广,极大的激发了大家的学习热情, 可以让人们更加专注于如何使用编程语言实现想要的功能。 面向对象(Object Oriented,OO)是软件开发方法。面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物,是一种高级的编程思想。 对应于软件开发的过程,面向对象OO衍生出3个概念:OOA、OOD和OOP。采用面向对象进行分析的方式称为OOA,采用面向对象进行设计的方式称为OOD,采用面向对象进行编码的方式称为OOP。面向过程(OP)和面向对象(OO)本质的区别在于分析方式的不同,最终导致了编码方式的不同。 编程思想案例:有一辆车,时速100km/h,行驶在长1000km的跨海大桥上,求多久能跑完? 面向过程编程思想:只关心数学逻辑。 var hours = 1000 / 100; alert(hours);//10面向对象编程思想:将生活逻辑映射到我们的程序里。 找出题目实体,抽象成对象的概念。分析实体属性和功能,给对象赋一些属性和方法。让实体相互作用得出结果,让每个对象去执行自己的方法。 var car = { speed: 100, run:function(road){ return road.length / this.speed; } } var kuahaidaqiao = { length:1000 } var hours = car.run(kuahaidaqiao); alert(hours);//10面向过程(Procedure Oriented):看名字它是注重过程的。当解决一个问题的时候,面向过程会把事情拆分成: 一个个函数和数据(用于方法的参数)。然后按照一定的顺序,执行完这些方法(每个方法看作一个个过程),等方法执行完了,事情就搞定了。 面向对象(Object Oriented):看名字它是注重对象的。当解决一个问题的时候,面向对象会把事物抽象成对象的概念,就是说这个问题里面有哪些对象,然后给对象赋一些属性和方法,然后让每个对象去执行自己的方法,问题得到解决。 语法JavaScript是一种基于对象的语言,但是它又不是一种真正的面向对象编程语言,因为它没有类(class)。类是具有一类相同特征事物的抽象概念。 在JS中一切皆对象,对象是具体的某一个实例,唯一的个体。在ECMA6语法中中新增了类这个概念。 对象的概念 在面向对象的编程思想中就是以属性和行为的方式去分析同一类事物,将其共有特性和行为的抽象出来,并封闭起来对待,而且我们封闭的同一类事物的属性和行为是互相关联的,有着内在的联系。【注】对象既能存储属性又能存储函数。 【注】我们声明的变量和函数对比对象的属性和方法,使用方式相同,唯一的区别就是使用对象的属性和方法时前面需要加对象名称,变量是自由的,属性是属于对象的。 声明对象的三种方式 var obj = new Object(); //通过new对象 var obj = Object(); //声明new var obj = {}; //直接通过对象常量声明给对象添加属性 ...

September 10, 2019 · 1 min · jiezi

面向对象基本原则3-最少知道原则与开闭原则

面向对象基本原则(3)- 最少知道原则与开闭原则五、最少知道原则【迪米特法则】1. 最少知道原则简介最少知识原则(Least KnowledgePrinciple,LKP)也称为迪米特法则(Law of Demeter,LoD)。虽然名字不同,但描述的是同一个规则:一个对象应该对其他对象有最少的了解。 通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的这么多public方法,我就调用这么多,其他的我一概不关心。 2. 最少知道原则实现只与直接关联的类交流每个对象都必然会与其他对象有耦合关系,耦合关系的类型有很多,例如组合、聚合、依赖等。 出现在成员变量、方法的输入输出参数中的类称为直接关联的类,而出现在方法体内部的类不属于直接关联的类。 下面举例说明如何才能做到只与直接关联的类交流。 场景:老师想让班长清点女生的数量 Bad/** * 老师类 * Class Teacher */class Teacher { /** * 老师对班长发布命令,清点女生数量 * @param GroupLeader $groupLeader */ public function command(GroupLeader $groupLeader) { // 产生一个女生群体 $girlList = new \ArrayIterator(); // 初始化女生 for($i = 0; $i < 20; $i++){ $girlList->append(new Girl()); } // 告诉班长开始执行清点任务 $groupLeader->countGirls($girlList); }}/** * 班长类 * Class GroupLeader */class GroupLeader { /** * 清点女生数量 * @param \ArrayIterator $girlList */ public function countGirls($girlList) { echo "女生数量是:", $girlList->count(), "\n"; }}/** * 女生类 * Class Girl */class Girl {}$teacher= new Teacher();//老师发布命令$teacher->command(new GroupLeader()); // 女生数量是:20上面实例中,Teacher类仅有一个直接关联的类 -- GroupLeader。而Girl这个类就是出现在commond方法体内,因此不属于与Teacher类直接关联的类。方法是类的一个行为,类竟然不知道自己的行为与其他类产生依赖关系,这是不允许的,违反了迪米特法则。 ...

May 27, 2019 · 5 min · jiezi

面向对象基本原则2-里式代换原则与依赖倒置原则

面向对象基本原则(2)- 里式代换原则与依赖倒置原则三、里式代换原则0. 继承的优缺点在面向对象的语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点: 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性。提高代码的重用性。子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同。提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;提高产品或项目的开放性。自然界的所有事物都是优点和缺点并存的,继承的缺点如下: 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束。增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大段的代码需要重构。1. 里式代换原则简介里式代换原则的英文名称是 Liskov Substitution Principle,简称LSP。 里式代换原则的英文定义是: Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.意思是:所有引用基类的地方必须能透明地使用其子类的对象。 通俗点讲,就是只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。 2. 里氏替换原则为良好的继承定义了规范子类必须完全实现父类的方法/** * 枪支抽象类 * Class AbstractGun */abstract class AbstractGun{ public abstract function shoot();}/** * 手枪 * Class Handgun */class Handgun extends AbstractGun{ public function shoot() { echo "手枪射击\n"; }}/** * 步枪 * Class Rifle */class Rifle extends AbstractGun{ public function shoot() { echo "步枪射击\n"; }}如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚合、组合等关系代替继承。 ...

May 25, 2019 · 3 min · jiezi

面向对象基本原则1-单一职责原则与接口隔离原则

面向对象基本原则(1)- 单一职责原则与接口隔离原则一、单一职责原则1. 单一职责原则简介单一职责原则的英文名称是 Single Responsibility Principle,简称SRP。 单一职责原则的原话解释是: There should never be more than one reason for a class to change.意思是:应该有且仅有一个原因引起类的变更。 2. 单一职责原则优点类的复杂性降低,实现什么职责都有清晰明确的定义。可读性提高,复杂性降低,那当然可读性提高了。可维护性提高,可读性提高,那当然更容易维护了。变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。3. 最佳实践接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。 注意 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。4. Show me the code代码使用PHP7.2语法编写用户业务场景IUserBo 接口负责用户属性 interface IUserBo{ public function setUserID(string $userID); public function getUserID() : string ; public function setPassword(string $password); public function getPassword() : string ; public function setUserName(string $userName); public function getUserName() : string ;}IUserBiz 接口负责用户行为 interface IUserBiz{ public function changePassword(string $password) : bool ; public function deleteUser(IUserBo $userBo) : bool ; public function mapUser(IUserBo $userBo); public function addOrg(IUserBo $userBo, int $orgID) : bool; /** * 给用户添加角色 * @param IUserBo $userBo * @param int $roleID * @return bool */ public function addRole(IUserBo $userBo, int $roleID) : bool ;}UserInfo 类实现 IUserBo, IUserBiz 两个接口 ...

May 23, 2019 · 2 min · jiezi

JavaScript高级程序设计第3版读书笔记-第6章-面向对象的程序设计

面向对象(Object-Oriented, OO)的语言有一个标志,它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。ECMAScript中没有类的概念,因此它的对象也与基类的语言中的对象有所不同ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。” 我们可以把ECMAScript的对象想象成散列表:无非就是一组名值对,其中值可以是数据或者函数每个对象都是基于一个引用类型创建的,这个引用类型可以是第5章讨论的原生类型,也可以是开发者定义的类型理解对象// 创建对象,赋给属性var person = new Object();person.name = "Nicholas";person.age = 29;person.job = "Sofware Engineer";person.sayName = function() { alert(this.name);}// 字面量方式创建对象var person = { name: "Nicholas", age: 29, job: "Sofware Engineer", sayName: function() { alert(this.name); }}属性类型ECMAScript中有两种属性:数据属性和访问器属性数据属性数据属性包含一个数值的位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性 [[Configurable]] 表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前述例子中那样直接在对象定义的属性,它们这个特性默认值为true[[Enumerable]] 表示能否通过for-in循环返回属性。前述例子这个特性默认为true[[Writable]] 表示能否修改属性的值。前述例子这个特性默认为true[[Value]] 包含这个属性的数据值。读取属性值得实惠,从这个位置读;写入属性值得实惠,把新值保存在这个位置。这个特性的默认值为undefined要修改属性默认的特性,必须使用ECMAScript5的Object.defineProperty()方法。 主要接收三个参数:属性所在的对象,属性的名字和一个描述符对象。描述符(descriptor)对象的属性必须是:configurable,enumerable,writalbe和value。设置其中的一或多个值,可以修改对应的特性值var person = {};// 创建了一个name属性,它的值"Nicholas"是只读的。Object.defineProperty(person, "name", { writable: false, value: "Nicholas"});console.log(person.name); // "Nicholas"// 在非严格模式下,赋值操作会被忽略// 而在严格模式下,会导致错误person.name = "Greg";console.log(person.name); // "Nicholas"把configurable设置为false,表示不能从对象中删除属性。如果对这个属性调用delete,则在非严格模式下什么也不会发生,而在严格模式下会导致错误一旦把属性定义为不可配置的,就不能再把它变回可配置了。此时再调用Object.defineProperty()方法修改除writable之外的特性,都会导致错误var person = {};Object.defineProperty(person, "name", { configurable: false, value: "Nicholas"});// 抛出错误Object.defineProperty(person, "name", { configurable: true, value: "Nicholas"})可以多次调用Object.defineProperty()方法修改同一个属性,但在把configurable特性设置为false之后就会有限制了。调用Object.defineProperty()如果不指定,configurable,enumerable,writalbe 特性的默认值都是false。多数情况下,没有必要利用Object.defineProperty()方法提供的高级功能。访问器属性访问器属性不包含数据值,它们包含一对getter和setter函数(不是必须的)在读取访问器属性时,会调用getter函数,这个函数负责返回有效的值写入访问器属性时,会调用setter函数并传入新值,这个函数负责决定如何处理数据访问器属性有如下4个特性: ...

May 14, 2019 · 8 min · jiezi

Python中什么是面向对象封装

文字有点长,对于不想看文字的朋友,可以去这里看视频,内容和这个文字一样的,视频可能更好理解https://www.piqizhu.com/v/yjB...回顾面向过程前面我们已经学过了 面向过程 我们知道,面向过程 的时候,我们的关注点是 步骤 面向过程做事,就是把 一件事情 分割为多个步骤, 然后依次去完成每一个步骤 这样做事可以让我们的事情变的很明朗,不会弄乱 那么, 既然有了面向过程,为什么还要跑出来一个面向对象呢?面向对象是什么东西呢?有啥好处?他和面向过程有啥关系呢? 他们两个我们应该选谁呢?带着这么3个问题,开始本节课的讲解 面向对象概述所谓面向对象, 意思就是,我们的关注点 是对象, 而非过程(步骤) 那么,这里的对象是啥意思呢? 要回答这个问题, 就必须先回到实际的案例中去讲解 上节课的我们的案例是制作一个玩具鸭子,我们的关注点是制作鸭子的每一个步骤 如果我们只是捏个泥娃娃,或者制作一个简单的玩具鸭子, 使用面向过程,是没有多大问题的 但是如果我们面对的一个复杂的事情呢? 有一家玩具公司,这家玩具公司不止生成玩具鸭,还生产玩具狗,玩具猫,玩具猫头鹰, 。。。。。 等等 100多种玩具 如果按照之前的 面向过程思路,那么我们的代码 会很长, 很杂乱, 那 怎么办呢? 这时候就需要使用面向对象的思路来解决问题了 面向对象-封装于是某一天,,这家玩具公司有了一台鸭子制造机器, 这台机器,当我们按下开关后,它就会立刻开始制作玩具鸭子, 此刻,我们不再去关注先做脚,还是先做头, 还是身体, 这台机器会帮我们搞定所有步骤, 我们只需要在机器的出口处,等着完整的玩具鸭子出来就可以了 这时候,我们的关注点,就是这台机器本身,而不是制作鸭子的某一个步骤 这台机器,此刻就是一个对象(整体),此刻,我们就开始了面向对象 可能到这里大家还是不明白,还是有点糊涂,这是正常的, 请允许我再来解释解释 这台鸭子制造机器,包含了 以前制造鸭子的所有步骤,它把制作鸭子的步骤,封装在了机器内部, 留给我们的,只有一个开关,我们只需要按开关,就可以开始制造鸭子 而以前的面向过程,我们需要关注制造鸭子的细节,需要先制作鸭头,然后制作翅膀,然后.... 但是,当我们有了一台封装了详细步骤的机器,只需要关心什么时候按开关,别的都不用关心 这就是面向对象的第一个特性(好处): 封装 封装特性,可以把复杂的信息,流程,包起来,内部处理, 让使用者不去关注细节, 只关心什么时候按开关, 如此一来当我们要制作鸭子的时候,只要按开关就可以了,是不是省心很多?? 再来举个例子比如,某一天,你以程序员的身份,去某家公司工作,老板让你开发一个网站, 此刻,老板就是面向了对象,这里的对象,在老板眼里 就是你, 因为老板只要把任务丢给你,他不关注你用什么电脑写代码,也不关注你用什么输入法,不会关注你写代码的时候听什么歌,不会关注你写代码的时候是穿拖鞋好,还是光脚丫好, 更加不会关注你今天穿什么颜色的内裤 写代码效率更高; 但是在你自己的角度,你就是面向过程的,你会关心自己用哪个电脑写代码更舒服, 你会关注自己用哪个输入法效率更高,你还会关注写代码应该听什么歌.. 等等 而,当你写代码的时候,你使用的电脑,对于你而言,也是一个封装好的对象, 当你在键盘上按下字母A, 你不会关注电脑内部究竟发生了 多么复杂的化学反应, 你只关注,我按了键盘上的字母A,电脑就要显示一个A在屏幕上 ...

April 23, 2019 · 1 min · jiezi

The Principles of OOD 面向对象设计原则

本文首发于 vivo 互联网技术微信公众号 https://mp.weixin.qq.com/s/Q_…作者:Robert C. Martin 翻译:张硕本文由来自美国业界大牛——Robert C. Martin(俗称“Bob大叔) 发布在 butunclebob.com 上,已获得翻译授权。英文原文链接:http://butunclebob.com/Articl…本篇概括性的介绍了OOD的设计原则,后续还有更多文章会详细剖析、吃透面向对象业务设计的原则。什么是面向对象设计?它是怎么一回事?使用它会有什么利弊得失?似乎问出这些问题显得有些愚蠢,特别是在一个几乎每个开发者都会使用某种面向对象语言的时代中。然而在我看来,这些问题即极为重要,因为我们中的大多数使用者并不知道答案,当然也不知道如何发挥面向对象语言的最大价值。在我们这个行业发生的所有变革中,有两场是非常成功的,以至于它们已经渗透到我们的思维中,以至于我们认为它们是理所当然的。那就是"结构化设计编程"(也叫面向过程设计)与"面向对象设计编程"。所有主流的现代编程语言都被这两种编程范式深刻影响。甚至是,我们很难摒弃这两种编程范式去写任何一个程序。我们的主流编程语言中没有“GOTO”,因此似乎是遵守了著名的结构化编程"禁令";我们大多数的主流编程语言是基于类并且不支持使用没有写入任何一个类中的变量、函数(方法),因此他们似乎是遵守了面向对象设计中最明显的特点。用这些语言(主流结构化语言或面向对象语言)写出的程序也许看上去是结构化的或面向对象的,但是外表也可以是虚假的。今天的程序员常常不知道这些语言产生的原因以及其中的基础原则。在另一篇博客中,我会讨论结构化编程的设计原则,而在这篇文章中我想要聊聊面向对象设计原则。在1995年3月,在comp.object上,我写过一篇文章,这篇文章也成为我日后众多OOD设计原则文章中的处女作。你们将会在我的PPP书籍中以及发表了我多篇文章的objectmentor网站上看到这些文章,当然也包括那个广为人知的那篇摘要文章。(译者注:PPP指——“Agile Software Development: Principles, Patterns, and Practice,中文即—敏捷软件开发:原则、模式与实践”)这些原则揭示了OOD依赖管理方面的内涵,而在概念化和建模方面并没有深入涉及。然而这并不代表OO在对问题领域的概念化上很薄弱,也不代表OO在建模能力上很薄弱。我很确定在这两方面上,很多从OO设计原则中获得价值。需要注意的是,这些原则非常关注依赖关系管理。(译者注:此处指宽泛概念的依赖关系管理,如系统与系统之间的依赖,模块与模块之间的依赖,类方法直接的依赖)依赖管理是一个大多数架构师需要面对的问题。每当我们在屏幕上看到一堆乱七八糟的遗留代码时,我们都在经历依赖管理不善的结果。糟糕的依赖关系管理导致代码难以更改、脆弱和不可重用。实际上,在我的PPP书中谈到了几种不同的设计风格,都与依赖管理有关。另一方面,当依赖关系得到很好的管理时,代码仍然是灵活可扩展的、健壮的和可重用的。因此,依赖关系管理的思考,以及这些原则的使用,是软件开发人员设计灵活性系统的基础。以下5个原则是阶级设计原则:SRP单一职责原则 指一个类模块包甚至系统 都应该有单一的原则。OCP开闭原则 你应该能够扩展类的行为,而不需要修改它。如果软件系统想要更容易被改变,其设计就必须允许新增代码来修改,而非修改原来代码。LSP 里氏替换原则简答理解就是 如果想要可替换的组件来构建软件系统,那么这些组件就必须遵守共同一个约定,以便让这些组件可以相互替换。ISP 接口隔离原则使细粒度接口特定于客户端,主要告诫设计师应该在设计中避免不必要的依赖。DIP 依赖倒置原则依赖抽象,而非具体实现。此原则指出高层策略性代码不应该依赖实现的代码,相反,那些底层实现应该依赖于高层策略代码。(译者注:这里的“类”泛指:方法和数据的耦合分组)接下来的六条原则是关于包(译者注:指jar、war,而非package)的。在这个上下文中,包是二进制的可交付文件,比如:jar文件,或者dll,而不是java包或c++命名空间。前三个包原则是关于包内聚的,它们告诉我们在包中放入什么:REP 重用发布等价原则 重用的颗粒就是释放的颗粒。CCP 共同封闭原则 一起更改的类被打包在一起。CRP 共同重用原则 一起使用的类被打包在一起。ADP 无环依赖原则 包的依赖关系图必须没有循环。SDP 稳定依赖原则 依赖于稳定性的方向,特别是(变化更多的)具体的元素应该取决于是否要完全依赖于(更稳定的)抽象成分。SAP 稳定抽象原则 抽象性随稳定性而增加。更多内容敬请关注 vivo互联网技术微信公众号注:转载文章请先与微信号:labs2020 联系。

March 29, 2019 · 1 min · jiezi

PHP面试常考内容之面向对象(3)

PHP面试专栏正式起更,每周一、三、五更新,提供最好最优质的PHP面试内容。继上一篇“PHP面试常考内容之面向对象(2)”发表后,今天更新面向对象的最后一篇(3)。需要(1),(2)的可以直接点文字跳转获取。PHP面试常考内容之面向对象(1)整个面向对象文章的结构涉及的内容模块有:一、面向对象与面向过程有什么区别?二、面向对象有什么特征?三、什么是构造函数和析构函数?四、面向对象的作用域范围有哪几种?五、PHP 中魔术方法有哪些?六、什么是对象克隆?七、this、self和parent的区别是什么?八、抽象类与接口有什么区别与联系?九、PHP面向对象的常考面试题讲解关于PHP面向对象的内容将会被分为三篇文章进行讲解完整块内容,第一篇主要讲解一到四点内容,第二篇主要讲解五到八的内容,第三篇围绕第九点进行讲解。以下正文的内容都来自《PHP程序员面试笔试真题解析》书籍,如果转载请保留出处:九、PHP面向对象的常考面试题讲解【真题1】 什么是面向对象?其主要特征是什么?答案:面向对象是程序的一种设计方式,它是一种对现实世界的理解和抽象的方法,它可以提高程序的重用性,让程序结构更加清晰。面向对象的主要特征为:封装、继承、多态。【真题2】 可以获得对象的类名的函数是( )。A.get_class_name B.get_class C.class_exists D.get_class_vars答案:B。PHP中获取对象的类名函数是get_class()。所以,选项B正确。对于选项A,不存在该方法。所以,选项A错误。对于选项C,class_exists()函数可以检查类是否存在。所以,选项C错误。对于选项D,get_class_vars()函数可以获取类的默认属性。所以,选项D错误。所以,本题的答案是B。【真题3】 请简单说明PHP的垃圾收集机制。答案:在PHP中,当没有任何变量指向该对象时,该对象变为垃圾将会在内存中被销毁,可以防止内存溢出。内存中对变量有引用计数,当计数到0时变量被销毁。【真题4】多态与方法重写有什么关系?答案:多态是指一个类可以被多个类继承,每个子类都可以对父类方法进行重写,每个类里的同名方法可以实现不同的功能从而表现出多种形态,它增强了软件的灵活性和重用性。重写是子类对父类中的方法进行改写。它们的关系是重写让类具备多态性。【真题5】面向对象几大原则是什么?答案:面向对象存在五大基本原则,分别是:单一职责原则、开放封闭原则、替换原则、依赖原则、接口分离原则等。(1)单一职责原则所谓单一职责原则,即一个类最好只做一件事。为了提高内聚性减少引起变化,单一原则是低耦合、高内聚的面向原则上的引申。(2)开放封闭原则软件的功能应该是可扩展的,尽可能减少修改,因为修改程序,可能会对原来的程序造成影响。虽然建议尽可能不修改程序,但是允许通过添加功能来减少修改。(3)替换原则只有子类能够替换基类,在继承机制的约束规范中,子类替换基类时,可以保证运行期内识别子类,保证继承复用。(4)依赖倒置原则高层模块不依赖底层模块,二者都依赖于抽象,抽象不依赖于实体,而实体依赖于抽象。模块间的依赖是通过抽象方法发生的,实现类中不发生直接的依赖关系,而依赖关系是通过接口或抽象类产生的。即接口或抽象类不依赖于实现类,而实现类依赖于接口和抽象类。这种依赖倒置原则可以有效地减少类之间的耦合性,提高系统的稳定性,减少并发引起的风险,提高代码的可读性和可维护性。(5)接口隔离原则建议开发使用多个小的、专门的接口,避免使用一个大的总接口。即每一个功能有一个专门的功能接口,需要用到才调用,不需要全部功能汇总到一个接口,这样可以提高代码的灵活性,降低类之间的耦合性,提高稳定性。【真题6】以下有关PHP高级特性的说法中,正确的是( )。A.可以定义一个类去实现预定义接口Iterator,然后就能像访问数组一样访问这个类创建的对象B.spla_utoload_register()提供了一种更加灵活的方式来实现类的自动加载,不再建议使用_autoload()函数C.PHP在对象中调用一个不可访问方法时,invoke()方法会被自动调用D.匿名函数也叫闭包函数,常用作回调函数参数的值,但是不能作为变量的值来使用答案:B。对于选项A,只有ArrayAccess能够提供像访问数组一样访问这个对象的接口,不能定义一个类或预定义接口Iterator去实现这个功能。所以,选项A错误。对于选项B,因为可以通过spla_utoload_register()函数创建autoload函数的队列,按定义顺序逐个执行,比_autoload()函数只可以定义一次使用更方便,所以不建议使用_autoload()函数。所以,选项B正确。对于选项C,_call方法是在创建一个类实例化后就可以直接调用对象使用,当调用的方法不可访问或没有权限访问时,会自动调用_call方法。所以,选项C错误。对于选项D,匿名函数是可以赋值给变量的。所以,选项D错误。所以,本题的答案是B。【真题7】__autoload()函数的工作原理是什么?答案:使用这个魔术函数的基本条件是,类文件的文件名要和类的名字保持一致。当程序执行到实例化某个类时,如果在实例化前没有引入这个类文件,那么就自动执行__autoload()函数。这个函数根据实例化的类名去查找这个类的路径,一旦找到这个类后就会通过执行include或require载入该类,从而保证程序能够继续执行。如果没有找到,那么报错。【真题8】以下关于PHP命名空间的说法中,不正确的是( )。A.访问任意全局类、函数或常量,都可以使用完全限定名称,例如strlen()或Exception或INI_ALLB.关键字 namespace可用来显式访问当前命名空间或子命名空间中的元素,它等价于类中的 this 操作符 C.任意合法的PHP代码都可以包含在命名空间中,但只有三种类型的代码受命名空间的影响,它们是类、函数和常量D.常量__NAMESPACE__的值是当前命名空间名称的字符串。如果是在全局中,那么它不包括任何命名空间中的代码,本身是一个空字符串答案:B。namespace关键字是用来声明命名空间用的,它并不能等价于this操作符的功能。所以,选项B说法不对。所以,本题的答案是B。【真题9】以下代码的运行结果是( )。<?php class Person{ var $name; } $a = new Person(); $a->name = “张三”; $b = $a; $b->name = “李四”; echo $a->name;?>A.张三 B.李四 C.Null D.什么都没有答案:B。首先$a实例化Person类,把张三赋值给类内的变量name,把对象张三的值给了$b,通过$b去修改类内name的值为李四,所以最后输出Person类内的name,输出得到结果李四。所以,选项B正确,选项A、选项C、选项D错误。所以,本题的答案是B。【真题10】下面说法错误的是( )。A.如果一个类的成员前面有访问修饰符private,那么这些成员不能被继承,在类的外部不可见。但如果成员被指定为protected和public,那么可以被继承,在类的外部也是可见的B.PHP5中,final关键字可以禁止继承和重载C.PHP5中,析构函数的名称是__destruct(),并且不能有任何参数D.继承接口的类必须实现接口中声明的所有方法,在PHP中,如果继承接口的类没有实现接口中的方法,那么将会产生一个致命错误答案:A。对于选项A,private修饰的成员是不可以被继承的,protected的成员是可以被继承的,但是在外部不可见,选项A说法错误,所以,选项A正确。对于选项B,final关键字的方法是禁止被继承和重载的,选项B说法正确,所以选项B错误。对于选项C,析构函数不能有参数,选项C说法正确,所以,选项C错误。对于选项D,继承接口的类没有实现接口中的方法是会产生错误的,选项D说法正确,所以,选项D错误。所以,本题的答案是A。自己整理了一篇“个人编程6年的心得——如何学好编程?”的文章,关注公众号:“琉忆编程库”,回复:“学好”,我发给你。【真题11】 定义类成员的访问权限控制符有哪些?默认修饰符是什么?答案:类成员的访问修饰符有public、private、protected,主要用来修饰类中的成员属性和方法。public是公共类型,允许在类的内部或子类中使用,也可以在类外部被访问。private是私有类型,只能在类的内部被使用,不能被继承使用。protected是保护类型,只能在类的内部或子类中使用。如果不使用public、private、protected等关键字修饰方法或属性,那么可以使用var关键字,它的功能等同于public,可以在类内或类外被调用,也可以被继承使用。其中,PHP默认的修饰符是public,即公有类型。类前面只能加final、abstract关键字,被final修饰的属性或方法是不能被继承的,只能在当前类中使用,abstract定义的类或方法,叫作抽象类或抽象方法。属性前面:必须有访问修饰符(private,protected,public,var)。【真题12】 PHP的魔术方法包含哪些(越多越好)?在什么情况下被自动调用?答案:PHP可用的魔术方法会在特定情况下被自动调用,但是前提是特定的条件被触发,并且这些魔术方法可以在类中作为方法。PHP的魔术方法有:1)_construct():构造函数,创建对象时自动被调用。2)_destruct():析构函数,对象的所有引用都被删除或者当对象被显式销毁时执行。3)__clone():克隆函数,调用clone方法时自动调用。4)__set():当程序试图写入一个不存在或不可见的成员变量时自动调用。该函数在类中定义时必须有两个参数:变量名和变量值。5)__get():当程序调用一个未定义或不可见的成员变量时自动调用__get()来读取变量值。定义时必有有一个参数:变量名。6)__call():当程序试图调用不存在或不可见的成员方法时,自动调用__call()。__call()方法一般用于监视错误的方法调用。为了避免当调用的方法不存在时产生错误,可以使用__call()方法来避免。该方法包含两个参数:方法名和方法参数。其中,方法参数以数组形式存在。7)__sleep():使用serialize()实现序列化对象时,先调用该方法,可以用来清除对象并返回一个该对象中所有变量的数组。8)__wakeup():使用unserialize()还原一个被序列化的对象时,先执行该方法,恢复在序列化中可能丢失的数据库连接及相关工作。9)__toString():当使用echo或print输出对象时,将对象转化为字符串。10)__autoload():调用未被实例化的类时,自动调用,在指定路径下查找和该类名称相同的文件。【真题13】 $this、self和parent这三个关键词分别代表什么?在哪些场合下使用?答案:$this表示当前对象,在当前类中可以通过->符调用类内的属性和方法。self表示当前类,只能通过self的形式(“self::方法或属性”)调用类内的方法。parent表示当前类的父类,调用父类内的方法只能使用“parent::”形式调用。【真题14】下面关于面向对象的描述中,错误的是( )。A.父类的构造函数与析构函数不会自动被调用B.成员变量需要用public、protected、private修饰,在定义变量时不再需要var关键字C.父类中定义的静态成员,不可以在子类中直接调用D.包含抽象方法的类必须为抽象类,抽象类不能被实例化答案:A。对于选项A,子类继承父类,如果子类没有构造函数和析构函数,那么实例化子类时会自动调用父类的构造函数和析构函数;但如果子类只有构造函数没有析构函数时,那么实例化子类时,自动调用的是子类的构造函数,销毁对象时调用父类的析构函数;如果子类没有构造函数只有析构函数,那么实例化子类时会自动调用父类的构造函数,销毁对象时调用子类的析构函数,选项A说法不完全。所以,选项A正确。对于选项B,成员变量使用了public、protected、private修饰定义变量时是不需要var关键字的,选项B说法正确。所以,选项B错误。对于选项C,父类中的静态成员,子类中是不可以直接访问的,选项B说法正确。所以,选项C错误。对于选项D,一个包含抽象方法的类必须是抽象类,并且抽象类不能被实例化。选项D说法正确。所以,选项D错误。所以,本题的答案是A。【真题15】 在PHP中,如果派生类与父类有相同名字的函数,那么派生类的函数会替换父类的函数,有如下程序代码:<?php class A { function disName(){ echo “Picachu”; } } class B extends A { var $tmp=’’; function disName(){ echo “Doraemon”; } } $cartoon = new B; $cartoon->disName();?>上述代码的运行结果为( )。A.tmp B.Picachu C.disName D.Doraemon E.无输出答案:D。当派生类继承父类时,如果通过实例化一个派生类的对象来访问对象的方法时,派生类不存在父类中的方法,那么执行父类中的方法。如果派生类和父类存在相同名字的方法,那么派生类的方法会覆盖父类方法,执行派生类的方法。所以,本题中可以执行派生类的disName()方法。所以,选项D正确,选项A、选项B、选项C、选项E错误。所以,本题的答案是D。【真题16】什么是抽象类和接口?抽象类和接口有什么不同和相似的地方?答案:被关键字abstract修饰的类叫作抽象类,抽象类是不能被实例化的。被abstract修饰的方法为抽象方法,一个类只要有一个抽象方法,这个类一定是抽象类。接口是通过关键字interface来定义的,可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体实现。PHP类只支持是单重继承的,但通过接口可以实现PHP类的多重继承。抽象类和接口的不同和相似的地方如下所示。1)抽象类是一种不能被实例化的类,只能作为其他类的父类来使用。2)抽象类是通过关键字abstract来声明的。3)抽象类与普通类相似,都包含成员变量和成员方法,两者的区别在于,抽象类中至少要包含一个抽象方法。4)抽象方法没有方法体,该方法就是要被子类重写的。5)抽象方法的格式为:abstract function abstractMethod()。6)因为PHP中只支持单重继承,所以如果想实现多重继承,那么就要使用接口。也就是说,子类可以实现多个接口。7)接口类是通过interface关键字来声明的,接口类中的成员变量和方法都是public的,可以不用显式地使用public来修饰。8)接口中的方法没有方法体。接口中的方法就是要被子类继承实现的。9)子类继承抽象类使用extends关键字,子类实现接口使用implements关键字。【真题17】用类编程实现:Stu类中有两个私有属性name和sex,有两个公有方法,setName()和setSex()参数自定,方法可实现对两个私有属性进行修改。在实例化类时要求对私有属性能初始化。答案:实现代码如下:<?php Class Stu{ private $name; private $sex; public function setName($name){ $this->name = $name; } public function setSex($sex){ $this->sex = $sex; } }?>【真题18】 假如有一个类Person,实例化(new)一个对象$p,那么以下使用对象$p调用Person类中的getInfo方法的写法中,正确的是( )。A.$p=>getInfo(); B.$this->getInfo(); C.$p->getInfo(); D.$p::getInfo();参考答案:C。分析:“::”主要用于访问类中的静态成员,“->”主要用于访问类中的变量和方法,“=>”主要应用在数组中的key和value映射时使用。所以,选项A、选项B、选项D错误,选项C正确。【真题19】php中public、protected、private三种访问控制模式的区别是什么?参考答案:php中public、protected、private三种访问控制模式的区别如下:访 问 模 式 描 述public 共有,任何地方都可以访问protected 继承,只能在本类或子类中访问,在其他地方不能使用private 私有,只能在本类中访问,在其他地方不能使用【真题20】 在PHP面向对象中,下面关于final修饰符的描述中,错误的是( )。A.使用final标识的类不能被继承 B.在类中使用final标识的成员方法,在子类中不能被覆盖C.不能使用final标识成员属性 D.使用final标识的成员属性,不能在子类中再次定义参考答案:D。分析:因为final只能修饰类与方法,不能修饰类的属性。所以,选项D错误。PS:由于真题较多,仅罗列PHP面向对象笔试中经常遇到的20道考题!至此本周(2019-2-11 至 2019-2-15 的面向对象专题已更新完毕,以上的内容只是摘取了PHP面向对象中最常考的内容,个别内容没有罗列可以从原书中获取。)感谢大家的支持!预告:下周(2019-2.18 —— 2.22)更新“PHP面试常考内容之Memcache和Redis缓存的”专题,敬请期待。以上内容摘自《PHP程序员面试笔试真题解析》书籍,该书已在天猫、京东、当当等电商平台销售。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,将不断完善追求极致,感谢你们的支持。 ...

February 14, 2019 · 1 min · jiezi

PHP面试常考内容之面向对象(2)

PHP面试专栏正式起更,每周一、三、五更新,提供最好最优质的PHP面试内容。继上一篇“PHP面试常考内容之面向对象(1)”发表后,今天更新(2),需要(1)的可以直接点击文字进行跳转获取。整个面向对象文章的结构涉及的内容模块有:一、面向对象与面向过程有什么区别?二、面向对象有什么特征?三、什么是构造函数和析构函数?四、面向对象的作用域范围有哪几种?五、PHP 中魔术方法有哪些?六、什么是对象克隆?七、this、self和parent的区别是什么?八、抽象类与接口有什么区别与联系?九、PHP面向对象的常考面试题讲解关于PHP面向对象的内容将会被分为三篇文章进行讲解完整块内容,第一篇主要讲解一到四点内容,第二篇主要讲解五到八的内容,第三篇围绕第九点进行讲解。以下正文的内容都来自《PHP程序员面试笔试宝典》书籍,如果转载请保留出处:五、PHP种魔术方法有哪些?在PHP中,把所有以__(两个下画线)开头的类方法保留为魔术方法。所以在定义类方法时,不建议使用 __ 作为方法的前缀。下面分别介绍每个魔术方法的作用。1.__get、__set、__isset、__unset这四个方法是为在类和它们的父类中没有声明的属性而设计的。1)在访问类属性的时候,若属性可以访问,则直接返回;若不可以被访问,则调用__get 函数。方法签名为:public mixed __get ( string $name )2)在设置一个对象的属性时,若属性可以访问,则直接赋值;若不可以被访问,则调用__set 函数。方法签名为:public void __set ( string $name , mixed $value )3)当对不可访问的属性调用 isset() 或 empty() 时,__isset() 会被调用。方法签名为:public bool __isset ( string $name )4)当对不可访问属性调用 unset() 时,__unset() 会被调用。方法签名为:public bool _unset ( string $name )需要注意的是,以上存在的不可访问包括属性没有定义,或者属性的访问控制为proteced或private(没有访问权限的属性)。下面通过一个例子把对象变量保存在另外一个数组中。<?php class Test { /* 保存未定义的对象变量 */ private $data = array(); public function __set($name, $value){ $this->data[$name] = $value; } public function __get($name){ if(array_key_exists($name, $this->data)) return $this->data[$name]; return NULL; } public function __isset($name){ return isset($this->data[$name]); } public function __unset($name){ unset($this->data[$name]); } } $obj = new Test; $obj->a = 1; echo $obj->a . “\n”;?>程序的运行结果为12.__construct、__destruct1)__construct 构造函数,实例化对象时被调用。2)__destruct 析构函数,当对象被销毁时调用。通常情况下,PHP只会释放对象所占有的内存和相关的资源,对于程序员自己申请的资源,需要显式地去释放。通常可以把需要释放资源的操作放在析构方法中,这样可以保证在对象被释放的时候,程序员自己申请的资源也能被释放。例如,可以在构造函数中打开一个文件,然后在析构函数中关闭文件。<?php class Test { protected $file = NULL; function __construct(){ $this->file = fopen(“test”,“r”); } function __destruct(){ fclose($this->file); } }?>3.__call()和__callStatic()1)__call( $method, $arg_array ):当调用一个不可访问的方法时会调用这个方法。2)__callStatic的工作方式与 __call() 类似,当调用的静态方法不存在或权限不足时,会自动调用__callStatic()。使用示例如下: <?php class Test { public function __call ($name, $arguments) { echo “调用对象方法 ‘$name’ “. implode(’, ‘, $arguments). “\n”; } public static function __callStatic ($name, $arguments) { echo “调用静态方法 ‘$name’ “. implode(’, ‘, $arguments). “\n”; } } $obj = new Test; $obj->method1(‘参数1’); Test::method2(‘参数2’); ?>程序的运行结果为调用对象方法 ‘method1’ 参数1 调用静态方法 ‘method2’ 参数24.__sleep()和__wakeup()1)__sleep 串行化的时候调用。2)__wakeup 反串行化的时候调用。也就是说,在执行serialize()和unserialize()时,会先调用这两个函数。例如,在序列化一个对象时,如果这个对象有一个数据库连接,想要在反序列化中恢复这个连接的状态,那么就可以通过重载这两个方法来实现。示例代码如下:<?php class Test { public $conn; private $server, $user, $pwd, $db; public function __construct($server, $user, $pwd, $db) { $this->server = $server; $this->user = $user; $this->pwd = $pwd; $this->db = $db; $this->connect(); } private function connect() { $this->conn = mysql_connect($this->server, $this->user, $this->pwd); mysql_select_db($this->db, $this->conn); } public function __sleep() { return array(‘server’, ‘user’, ‘pwd’, ‘db’); } public function __wakeup() { $this->connect(); } public function __destruct(){ mysql_close($conn); } }?>5.__toString()__toString 在打印一个对象时被调用,可以在这个方法中实现想要打印的对象的信息,使用示例如下:<?php class Test { public $age; public function __toString() { return “age:$this->age”; } } $obj = new Test(); $obj->age=20; echo $obj;?>程序的运行结果为age:206.__invoke()在引入这个魔术方法后,可以把对象名当作方法直接调用,它会间接调用这个方法,使用示例如下:<?php class Test { public function __invoke() { print “hello world”; } } $obj = new Test; $obj();?>程序的运行结果为hello world7.__set_state()调用 var_export 时被调用,用__set_state的返回值作为var_export 的返回值。使用示例如下:<?php class People { public $name; public $age; public static function __set_state ($arr) { $obj = new People; $obj->name = $arr[’name’]; $obj->age = $arr[‘aage’]; return $obj; } } $p = new People; $p->age = 20; $p->name = ‘James’; var_dump(var_export($p));?>程序的运行结果为People::__set_state(array( ’name’ => ‘James’, ‘age’ => 20,)) NULL8.__clone()这个方法在对象克隆的时候被调用,php提供的__clone()方法对一个对象实例进行浅拷贝,也就是说,对对象内的基本数值类型通过值传递完成拷贝,当对象内部有对象成员变量的时候,最好重写__clone方法来实现对这个对象变量的深拷贝。使用示例如下:<?php class People { public $age; public function __toString() { return “age:$this->age \n”; } } class MyCloneable { public $people; function __clone() { $this->people = clone $this->people; //实现对象的深拷贝 } } $obj1 = new MyCloneable(); $obj1->people = new People(); $obj1->people->age=20; $obj2 = clone $obj1; $obj2->people->age=30; echo $obj1->people; echo $obj2->people;?>程序的运行结果为age:20 age:30由此可见,通过对象拷贝后,对其中一个对象值的修改不影响另外一个对象。9.__autoload()当实例化一个对象时,如果对应的类不存在,则该方法被调用。这个方法经常的使用方法为:在方法体中根据类名,找出类文件,然后require_one 导入这个文件。由此,就可以成功地创建对象了,使用示例如下:Test.php:<?php class Test { function hello() { echo ‘Hello world’; } }?>index.php:<?php function __autoload( $class ) { $file = $class . ‘.php’; if ( is_file($file) ) { require_once($file); //导入文件 } } $obj = new Test(); $obj->hello();?>程序的运行结果为Hello world在index.php中,由于没有包含Test.php,在实例化Test对象的时候会自动调用__autoload方法,参数$class的值即为类名Test,这个函数中会把Test.php引进来,由此Test对象可以被正确地实例化。这种方法的缺点是需要在代码中文件路径做硬编码,当修改文件结构的时候,代码也要跟着修改。另一方面,当多个项目之间需要相互引用代码的时候,每个项目中可能都有自己的__autoload,这样会导致两个__autoload冲突。当然可以把__autoload修改成一个。这会导致代码的可扩展性和可维护性降低。由此从PHP5.1开始引入了spl_autoload,可以通过spl_autoload_register注册多个自定义的autoload方法,使用示例如下:index.php<?php function loadprint( $class ) { $file = $class . ‘.php’; if (is_file($file)) { require_once($file); } } spl_autoload_register( ’loadprint’ ); //注册自定义的autoload方法从而避免冲突 $obj = new Test(); $obj->hello();?>spl_autoload是_autoload()的默认实现,它会去include_path中寻找$class_name(.php/.inc) 。除了常用的spl_autoload_register外,还有如下几个方法:1)spl_autoload:_autoload()的默认实现。2)spl_autoload_call:这个方法会尝试调用所有已经注册的__autoload方法来加载请求的类。3)spl_autoload_functions:获取所有被注册的__autoload方法。4)spl_autoload_register:注册__autoload方法。5)spl_autoload_unregister:注销已经注册的__autoload方法。6)spl_autoload_extensions:注册并且返回spl_autoload方法使用的默认文件的扩展名。引申:PHP有哪些魔术常量?除了魔术变量外,PHP还定义了如下几个常用的魔术常量。1)LINE:返回文件中当前的行号。2)FILE:返回当前文件的完整路径。3)FUNCTION:返回所在函数名字。4)CLASS:返回所在类的名字。5)METHOD:返回所在类方法的名称。与__FUNCTION__不同的是,__METHOD__返回的是“class::function”的形式,而__FUNCTION__返回“function”的形式。6)DIR:返回文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录(PHP 5.3.0中新增)。7)NAMESPACE:返回当前命名空间的名称(区分大小写)。此常量是在编译时定义的(PHP 5.3.0 新增)。8)TRAIT:返回 Trait 被定义时的名字。Trait 名包括其被声明的作用区域(PHP 5.4.0 新增)。六、什么是对象克隆?对于对象而言,PHP用的是引用传递,也就是说,对象间的赋值操作只是赋值了一个引用的值,而不是整个对象的内容,下面通过一个例子来说明引用传递存在的问题:<?php class My_Class { public $color; } $obj1 = new My_Class (); $obj1->color = “Red”; $obj2 = $obj1; $obj2->color =“Blue”; //$obj1->color的值也会变成"Blue”?>因为PHP使用的是引用传递,所以在执行$obj2 = $obj1后,$obj1和$obj2都是指向同一个内存区(它们在内存中的关系如下图所示),任何一个对象属性的修改对另外一个对象也是可见的。在很多情况下,希望通过一个对象复制出一个一样的但是独立的对象。PHP提供了clone关键字来实现对象的复制。如下例所示:<?php class My_Class { public $color; } $obj1 = new My_Class (); $obj1->color = “Red”; $obj2 = clone $obj1; $obj2->color =“Blue”; //此时$obj1->color的值仍然为"Red”?>$obj2 = clone $obj1把obj1的整个内存空间复制了一份存放到新的内存空间,并且让obj2指向这个新的内存空间,通过clone克隆后,它们在内存中的关系如下图所示。此时对obj2的修改对obj1是不可见的,因为它们是两个独立的对象。在学习C++的时候有深拷贝和浅拷贝的概念,显然PHP也存在相同的问题,通过clone关键字克隆出来的对象只是对象的一个浅拷贝,当对象中没有引用变量的时候这种方法是可以正常工作的,但是当对象中也存在引用变量的时候,这种拷贝方式就会有问题,下面通过一个例子来进行说明:<?php class My_Class { public $color; } $c =“Red”; $obj1 = new My_Class (); $obj1->color =&$c; //这里用的是引用传递 $obj2 = clone $obj1; //克隆一个新的对象 $obj2->color=“Blue”; //这时,$obj1->color的值也变成了"Blue”?>在这种情况下,这两个对象在内存中的关系如下图所示。从上图中可以看出,虽然obj1与obj2指向的对象占用了独立的内存空间,但是对象的属性color仍然指向一个相同的存储空间,因此当修改了obj2->color的值后,意味着c的值被修改,显然这个修改对obj1也是可见的。这就是一个非常典型的浅拷贝的例子。为了使两个对象完全独立,就需要对对象进行深拷贝。那么如何实现呢,PHP提供了类似于__clone方法(类似于C++的拷贝构造函数)。把需要深拷贝的属性,在这个方法中进行拷贝:使用示例如下:<?php class My_Class { public $color; public function __clone() { $this->color = clone $this->color; } } $c =“Red”; $obj1 = new My_Class (); $obj1->color =&$c; $obj2 = clone $obj1; $obj2->color=“Blue”; //这时,$obj1->color的值仍然为"Red”?>通过深拷贝后,它们在内存中的关系如图1-4所示。通过在__clone方法中对对象的引用变量color进行拷贝,使obj1与obj2完全占用两块独立的存储空间,对obj2的修改对obj1也不可见。自己整理了一篇“如果遇到代码怎么改都没效果,怎么办?”的文章,关注公众号:“琉忆编程库”,回复:“问题”,我发给你。七、this、self和parent的区别是什么?this、self、parent三个关键字从字面上比较好理解,分别是指这、自己、父亲。其中,this指的是指向当前对象的指针(暂用C语言里面的指针来描述),self指的是指向当前类的指针,parent指的是指向父类的指针。以下将具体对这三个关键字进行分析。##1.this关键字## 1 <?php 2 class UserName { 3 private $name; // 定义成员属性 4 function __construct($name) { 5 $this->name = $name; // 这里已经使用了this指针 6 } 7 // 析构函数 8 function __destruct() { 9 } 10 // 打印用户名成员函数 11 function printName() { 12 print ($this->name."\n") ; // 又使用了this指针 13 } 14 } 15 // 实例化对象 16 $nameObject = new UserName ( “heiyeluren” ); 17 // 执行打印 18 $nameObject->printName (); // 输出: heiyeluren 19 // 第二次实例化对象 20 $nameObject2 = new UserName ( “PHP5” ); 21 // 执行打印 22 $nameObject2->printName (); // 输出:PHP5 23 ?>上例中,分别在5行和12行使用了this指针,那么this到底是指向谁呢?其实,this是在实例化的时候来确定指向谁,例如,第一次实例化对象的时候(16行),当时this就是指向$nameObject 对象,那么执行第12行打印的时候就把print($this->name)变成了print ($nameObject->name),输出"heiyeluren"。对于第二个实例化对象,print( $this- >name )变成了print( $nameObject2->name ),于是就输出了"PHP5"。所以,this就是指向当前对象实例的指针,不指向任何其他对象或类。2.self关键字先要明确一点,self是指向类本身,也就是self是不指向任何已经实例化的对象,一般self用来访问类中的静态变量。 1 <?php 2 class Counter { 3 // 定义属性,包括一个静态变量 4 private static $firstCount = 0; 5 private $lastCount; 6 // 构造函数 7 function __construct() { 8 // 使用self来调用静态变量,使用self调用必须使用::(域运算符号) 9 $this->lastCount = ++ selft::$firstCount; 10 } 11 // 打印lastCount数值 12 function printLastCount() { 13 print ($this->lastCount) ; 14 } 15 } 16 // 实例化对象 17 $countObject = new Counter (); 18 $countObject->printLastCount (); // 输出 1 19 ?>上述示例中,在第4行定义了一个静态变量$firstCount,并且初始值为0,那么在第9行的时候调用了这个值,使用的是self来调用,中间使用域运算符“::”来连接,这时候调用的就是类自己定义的静态变量$firstCount,它与下面对象的实例无关,只是与类有关,无法使用this来引用,只能使用 self来引用,因为self是指向类本身,与任何对象实例无关。3.parent关键字parent是指向父类的指针,一般使用parent来调用父类的构造函数。 1 <?php 2 // 基类 3 class Animal { 4 // 基类的属性 5 public $name; // 名字 6 // 基类的构造函数 7 public function __construct($name) { 8 $this->name = $name; 9 } 10 } 11 // 派生类 12 class Person extends Animal // Person类继承了Animal类 13 { 14 public $personSex; // 性别 15 public $personAge; // 年龄 16 // 继承类的构造函数 17 function __construct($personSex, $personAge) { 18 parent::__construct ( “heiyeluren” ); // 使用parent调用了父类的构造函数 19 $this->personSex = $personSex; 20 $this->personAge = $personAge; 21 } 22 function printPerson() { 23 print ($this->name . " is " . $this->personSex . “,this year " . $this->personAge) ; 24 } 25 } 26 // 实例化Person对象 27 $personObject = new Person ( “male”, “21” ); 28 // 执行打印 29 $personObject->printPerson (); // 输出:heiyeluren is male,this year 21 30 ?>上例中,成员属性都是public的,特别是父类的,是为了供继承类通过this来访问。第18行: parent::__construct( “heiyeluren” ),使用了parent来调用父类的构造函数进行对父类的初始化,因为父类的成员都是public的,于是就能够在继承类中直接使用 this来访问从父类继承的属性。八、抽象类与接口有什么区别与联系?抽象类应用的定义如下:abstract class ClassName{}抽象类具有以下特点:1)定义一些方法,子类必须实现父类所有的抽象方法,只有这样,子类才能被实例化,否则子类还是一个抽象类。2)抽象类不能被实例化,它的意义在于被扩展。3)抽象方法不必实现具体的功能,由子类来完成。4)当子类实现抽象类的方法时,这些方法的访问控制可以和父类中的一样,也可以有更高的可见性,但是不能有更低的可见性。例如,某个抽象方法被声明为protected的,那么子类中实现的方法就应该声明为protected或者public的,而不能声明为private。5)如果抽象方法有参数,那么子类的实现也必须有相同的参数个数,必须匹配。但有一个例外:子类可以定义一个可选参数(这个可选参数必须要有默认值),即使父类抽象方法的声明里没有这个参数,两者的声明也无冲突。下面通过一个例子来加深理解:<?php abstract class A{ abstract protected function greet($name); } class B extends A { public function greet($name, $how=“Hello “) { echo $how.$name."\n”; } } $b = new B; $b->greet(“James”); $b->greet(“James”,“Good morning “);?>程序的运行结果为Hello JamesGood morning James定义抽象类时,通常需要遵循以下规则:1)一个类只要含有至少一个抽象方法,就必须声明为抽象类。2)抽象方法不能够含有方法体。接口可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。在PHP中,接口是通过interface关键字来实现的,与定义一个类类似,唯一不同的是接口中定义的方法都是公有的而且方法都没有方法体。接口中所有的方法都是公有的,此外接口中还可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖。要实现一个接口,可以通过关键字implements来完成。实现接口的类中必须实现接口中定义的所有方法。虽然PHP不支持多重继承,但是一个类可以实现多个接口,用逗号来分隔多个接口的名称。下面给出一个接口使用的示例:<?php interface Fruit { const MAX_WEIGHT = 3; //静态常量 function setName($name); function getName(); } class Banana implements Fruit { private $name; function getName() { return $this->name; } function setName($_name) { $this->name = $_name; } } $b = new Banana(); //创建对象 $b->setName(“香蕉”); echo $b->getName(); echo “<br />”; echo Banana::MAX_WEIGHT; //静态常量?>程序的运行结果为香蕉 3接口和抽象类主要有以下区别:抽象类:PHP5支持抽象类和抽象方法。被定义为抽象的类不能被实例化。任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方法和参数,不能定义其具体的功能实现。抽象类通过关键字abstract来声明。接口:可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。在这种情况下,可以通过interface关键字来定义一个接口,在接口中声明的方法都不能有方法体。二者虽然都是定义了抽象的方法,但是事实上两者区别还是很大的,主要区别如下:1)对接口的实现是通过关键字implements来实现的,而抽象类继承则是使用类继承的关键字extends实现的。2)接口没有数据成员(可以有常量),但是抽象类有数据成员(各种类型的成员变量),抽象类可以实现数据的封装。3)接口没有构造函数,抽象类可以有构造函数。4)接口中的方法都是public类型,而抽象类中的方法可以使用private、protected或public来修饰。5)一个类可以同时实现多个接口,但是只能实现一个抽象类。预告:PHP面试常考内容之面向对象(3)将于本周五(2019.2-15)更新。以上内容摘自《PHP程序员面试笔试宝典》书籍,该书已在天猫、京东、当当等电商平台销售。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,我将不断完善追求极致,感谢你们的支持。 ...

February 13, 2019 · 5 min · jiezi

PHP面试之面向对象(1)

PHP面试专栏正式起更,每周一、三、五更新,提供最好最优质的PHP面试内容。PHP中面向对象常考的知识点有以下7点,我将会从以下几点进行详细介绍说明,帮助你更好的应对PHP面试常考的面向对象相关的知识点和考题。整个面向对象文章的结构涉及的内容模块有:一、面向对象与面向过程有什么区别?二、面向对象有什么特征?三、什么是构造函数和析构函数?四、面向对象的作用域范围有哪几种?五、PHP 中魔术方法有哪些?六、什么是对象克隆?七、this、self和parent的区别是什么?八、抽象类与接口有什么区别与联系?九、PHP面向对象的常考面试题讲解关于PHP面向对象的内容将会被分为三篇文章进行讲解完整块内容,第一篇主要讲解一到四点内容,第二篇主要讲解五到八的内容,第三篇围绕第九点进行讲解。以下正文的内容都来自《PHP程序员面试笔试宝典》书籍,如果转载请保留出处:一、面向对象与面向过程有什么区别?面向对象是当今软件开发方法的主流方法之一,它是把数据及对数据的操作方法放在一起,作为一个相互依存的整体,即对象。对同类对象抽象出其共性,即类,类中的大多数数据,只能被本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。程序流程由用户在使用中决定。例如,站在抽象的角度,人类具有身高、体重、年龄、血型等一些特称,人类会劳动、会直立行走、会吃饭、会用自己的头脑去创造工具等这些方法,人类仅仅只是一个抽象的概念,它是不存在的实体,但是所有具备人类这个群体的属性与方法的对象都称为人,这个对象人是实际存在的实体,每个人都是人这个群体的一个对象。而面向过程是一种以事件为中心的开发方法,就是自顶向下顺序执行,逐步求精,其程序结构是按功能划分为若干个基本模块,这些模块形成一个树状结构,各模块之间的关系也比较简单,在功能上相对独立,每一模块内部一般都是由顺序、选择和循环三种基本结构组成,其模块化实现的具体方法是使用子程序,而程序流程在写程序时就已经决定。例如五子棋,面向过程的设计思路就是首先分析问题的步骤:第一步,开始游戏;第二步,黑子先走;第三步,绘制画面;第四步,判断输赢;第五步,轮到白子;第六步,绘制画面;第七步,判断输赢;第八步,返回步骤二;第九步,输出最后结果。把上面每个步骤用分别的函数来实现,就是一个面向过程的开发方法。具体而言,二者主要有以下几个方面的不同之处。1)出发点不同。面向对象是用符合常规思维方式来处理客观世界的问题,强调把问题域的要领直接映射到对象及对象之间的接口上。而面向过程方法则不然,它强调的是过程的抽象化与模块化,它是以过程为中心构造或处理客观世界问题的。2)层次逻辑关系不同。面向对象方法则是用计算机逻辑来模拟客观世界中的物理存在,以对象的集合类作为处理问题的基本单位,尽可能地使计算机世界向客观世界靠拢,以使问题的处理更清晰直接,面向对象方法是用类的层次结构来体现类之间的继承和发展。面向过程方法处理问题的基本单位是能清晰准确地表达过程的模块,用模块的层次结构概括模块或模块间的关系与功能,把客观世界的问题抽象成计算机可以处理的过程。3)数据处理方式与控制程序方式不同。面向对象方法将数据与对应的代码封装成一个整体,原则上其他对象不能直接修改其数据,即对象的修改只能由自身的成员函数完成,控制程序方式上是通过“事件驱动”来激活和运行程序。而面向过程方法是直接通过程序来处理数据,处理完毕后即可显示处理结果,在控制程序方式上是按照设计调用或返回程序,不能自由导航,各模块之间存在着控制与被控制、调用与被调用。4)分析设计与编码转换方式不同。面向对象方法贯穿软件生命周期的分析、设计及编码之间是一种平滑过程,从分析到设计再到编码是采用一致性的模型表示,即实现的是一种无缝连接。而面向过程方法强调分析、设计及编码之间按规则进行转换,贯穿软件生命周期的分析、设计及编码之间,实现的是一种有缝的连接。二、面向对象有什么特征?面向对象的主要特征有抽象、继承、封装和多态。1)抽象。抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。2)继承。继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且子类可以修改或增加新的方法使之更适合特殊的需要。3)封装。封装是指将客观事物抽象成类,每个类对自身的数据和方法实行保护。类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的信息进行隐藏。4)多态。多态是指允许不同类的对象对同一消息做出响应。多态包括参数化多态和包含多态。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好地解决了应用程序函数同名问题。自己整理了一篇“PHP和MySQL面试中爱考的10道题”的文章,关注公众号:“琉忆编程库”,回复:“10”,我发给你。三、什么是构造函数和析构函数?1.构造函数在PHP5之前的版本,构造函数的名字必须与类的名字相同,而从PHP5开始,开发者可以定义一个名为__construct的方法作为构造函数。构造函数的作用就是当类被实例化的时候会被自动调用,因此构造函数主要用于做一些初始化的工作。使用__construct作为构造函数名字的一个好处是,当类名修改的时候,不需要修改构造函数的名字。它的声明形式为void __construct ([ mixed $args [, $… ]] )在C++语言中,子类的构造函数会隐式地调用父类的无参数的构造函数。但是在PHP中,子类的构造函数不会隐式地去调用父类的构造函数,需要开发者通过parent::__construct()来显式地去调用父类的构造函数。当子类没有定义构造函数的时候,它会继承父类的构造函数,但前提是父类的构造函数不能被定义为private。使用示例如下:<?php class BaseClass { function __construct() { print “Base constructor\n”; } } class SubClass extends BaseClass { function __construct() { parent::__construct(); print “Sub constructor\n”; } } // 会调用父类构造函数 $obj = new BaseClass(); //调用子类构造函数,子类构造函数会去调用父类构造函数 $obj = new SubClass();?>程序的运行结果为Base constructor Base constructorSub constructor从上面的讲解中可以发现,从PHP5开始多了一种构造函数定义的方法。为了实现不同版本PHP代码的兼容,在PHP5的类中找不到 __construct() 函数并且也没有从父类继承一个的话,那么它就会尝试寻找旧式的构造函数(与类同名的函数)。这种兼容的方法存在一个风险:在PHP5之前的版本中开发的类中已有一个名为 __construct() 的方法却被用于其他用途时,PHP5的类会认为这是一个构造函数,从而当类实例化时自动执行这个方法。从 PHP 5.3.3 开始,在命名空间中,与类名同名的方法不再作为构造函数。这一改变不影响不在命名空间中的类。2.析构函数析构函数是在PHP5引入的,它的作用与调用时机和构造函数刚好相反,它在对象被销毁时自动执行。析构函数__destruct()结构形式如下:function __destruct(){ /* 类的初始化代码*/} 需要注意的是,析构函数是由系统自动调用的,因此,它不需要参数。默认情况下,系统仅释放对象属性所占用的内存,并不销毁在对象内部申请的资源(例如,打开文件、创建数据库的连接等),而利用析构函数在使用一个对象之后执行代码来清除这些在对象内部申请的资源(关闭文件、断开与数据库的连接)。与构造函数类似,如果想在子类中调用父类的析构函数,那么需要显式地调用:parent::__destruct()。如果子类没有定义析构函数,那么它会继承父类的析构函数。当对象不再被引用时,将调用析构函数。如果要明确地销毁一个对象,那么可以给指向对象的变量不分配任何值,通常将变量赋值为NULL或者用unset()函数。示例代码如下:<?php class des{ function __destruct(){ echo “对象被销毁,执行析构函数<br>”; } } $p=new des(); /* 实例化类 / echo “程序开始<br>”; unset($p); / 销毁变量$p */ echo “程序结束”;?>四、面向对象的作用域范围有哪几种?在PHP5中,类的属性或者方法主要有public、protected和private三种类作用域,它们的区别如下:1)public(公有类型)表示全局,类内部、外部和子类都可以访问。默认的访问权限为public,也就是说,如果一个方法没有被public、protected或private修饰,那么它默认的作用域为public。2)protected(受保护类型)表示受保护的,只有本类或子类可以访问。在子类中,可以通过self::var或self::method访问,也可以通过parent::method来调用父类中的方法。在类的实例化对象中,不能通过$obj->var来访问protected类型的方法或属性。3)private(私有类型)表示私有的,只有本类内部可以使用。该类型的属性或方法只能在该类中使用,在该类的实例、子类、子类的实例中都不能调用私有类型的属性和方法。预告:PHP面试常考内容之面向对象(2)将于本周三(2019.2-13)更新。以上内容摘自《PHP程序员面试笔试宝典》书籍,该书已在天猫、京东、当当等电商平台销售。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,将不断完善追求极致,感谢你们的支持。 ...

February 11, 2019 · 1 min · jiezi

❖ Python OOP 面向对象编程

参考:黑马程序员教程 - Python基础 面向对象OOP三大特性,且三个特性是有顺序的:封装继承多态封装指的就是把现实世界的事务,封装、抽象成编程里的对象,包括各种属性和方法。这个一般都很简单,不需要多讲。唯一要注意的就是:推荐从小往大开始封装、开发类。比如手枪,子弹这两个类,我们需要先定义和开发子弹的所有属性和方法,然后再去开发上一层的手枪。这样的话会很方便。反过来开发手枪的适合,发现写到一半写不下去要到子弹那里写,就很乱了。继承子类可以继承父类和父父类的所有属性、方法。继承格式:class Parent: def func1(self): passclass Son(Parent): def func2(self): func1()方法改写:子类在不满意时,也可以进行自己的改写父类的属性、方法。其中有两种情况:Overwrite 覆盖重写父类方法:只需要写一个同名函数即可覆盖。Extend 扩展父类函数:第一种方式(主要):写一个同名函数,并在其中通过super().func()引用父类方法。其中super是一个python builtin 特殊类,而super()即生成一个super的实例。在子类中生成super实例,会得到父类的引用。第二种方式(python 2.x以前使用):写一个同名函数,再通过ParentName.func(self)引用父类方法。但是不推荐,因为父类名称改变的话所有的子类都要改。私有不继承:子类能够继承的只是父类的公开内容,但是不包括父类的私有内容。如果要访问的话也可以,但是需要间接的调用父类再用方法调用私有内容。多继承Python中,子类是可以同时有多个父类的:也就是能够同时继承多个父类的所有属性、方法。继承格式:class Father: def func1(self): passclass Mother: def func2(self): passclass Son(Father, Mother): def func3(self): func1() func2()注意:如果多个父类间存在有同名的方法,那么会继承第一个父类的方法。MRO, Method Resolution Order查看继承顺序:通过类自带的.__mro__属性(MRO, Method Resolution Order),可以查看这个类的继承顺序。子类可以直接写FatherName.func()来调用父级函数。但是当子类用super().func()时候,python就会根据MRO顺序,由近到远逐次寻找,找到最近的上级则返回。用上例,如果是多继承的话,那么寻找顺序是:SON -> Father -> Mother -> object。查看类的内置属性和方法:dir(className)可以查看内置所有属性方法。Python内置的object基础类Python3开始使用新式的类定义,即默认让所有定义的类都自动继承一个叫object的内置基础类。object基础类定义了很多方便的属性。包括18项之多。而旧式的Python2.x时代,不继承object基础类,自己定义的类就只有__doc__和__module__两样内置属性而已。2.x时代,如果需要手动继承,如:class MyClass(object): pass多态多态是指,不同的子类对象调用相同的父类方法,会产生多态多样结果的编程特性。多态的前提是能够继承父类的方法,且能够重写改写父类的方法。多态的特点:是调用方法的技巧,而不影响类的内部设计可以增加代码灵活度def Father(): def work(self): do_job() def do_job(self): print(‘Farming on the field…’)def Son(Father): def do_job(self): print(‘Programming at an office…’)# —- Now let’s work —-Jason = Son()Jason.work()以上代码中,同样是work()函数,且要do_work()。但是,不同的人调用的是不同的do_work。Father调用自己的do_work,儿子因为自己重写了do_work,所以调用自己的方法。这就是多态——所继承的方法,不需要再特殊指定谁用什么方法,而对象会自动调用适合自己的方法。类与实例Python中,实例是一个对象,类也是一个对象,一切皆对象。但这也是Python OOP中引起很多麻烦的原因。实例对象非常好理解,也好用,直接用,就不说了。但是类对象就不那么好理解了。简单说,类对象也是一个标准的对象,有自己的属性和方法,只不过能够像模版一样生成多个实例对象而已。类对象有这两大研究点:类属性:就是能让所有实例访问和操作的公用厕所定义类属性:位于class的所有方法之外访问类属性:className.propertyName类方法:比较难理解,必须用到名为@classmethod的装饰器,函数的第一个参数必须是关键字cls,如同self。@classmethod装饰器:用来告诉解释器这是一个类方法,而不是实例方法。cls参数:类属性与实例属性这是Python OOP中困扰很多人的特点。但是其实不难理解,总结如下:class MyClass: # 在这个位置定义的,叫类属性。==等同于其它语言的“静态属性” # 这是每个实例共有的公用属性,相当于宿舍的公用洗澡间 count = 0 def init(self): # 用self.定义的,叫实例属性,是每个实例只自己所有的属性,selfish self.name = “Jason"访问类属性的方法有两种:ClassName.propertyName:推荐,直接用类名访问类属性。Instance.propertyName:不推荐用实例名访问类属性,因为如果需要写入操作,那么这种方法只会给自己添加一个实例属性,而不会影响类属性。动态添加类属性方法一:>>> MyClass.newAttribute = ‘I am a class attribute’>>> print( MyClass.newAttribute )‘I am a class attribute’方法二:装饰器# Customized decorator for classpropertyclass classproperty(object): def init(self, getter): self.getter= getter def get(self, instance, owner): return self.getter(owner)class MyClass: @classproperty def newAttribute(cls): return ‘I am a class attribute.’>>> print( MyClass.newAttribute )‘I am a class attribute’之所以把方法封装为一个类属性,是因为我们有时候需要根据其它类属性来定制这个类属性。而一般情况下,我们没法在类属性定义的时候获得当前的类或类中其它的属性。类方法类方法如同类属性,是属于全类的方法,但是(推荐)只用来访问类属性。类方法:比较难理解,必须用到名为@classmethod的装饰器,函数的第一个参数必须是关键字cls,如同self。@classmethod装饰器:用来告诉解释器这是一个类方法,而不是实例方法。cls参数:如同self,用来指代当前的类。注意:@classmethod和cls都是关键字,不能改。代码:class MyClass: # 定义一个“类属性” count = 0 # 这里开始定义“类方法” @classmethod def func(cls): print(cls.count)类静态方法类中的staticmethod装饰器同样是python基础类object的一个用于包装、装饰的方法。一旦在类方法前放上装饰器@staticmethod,方法就会转换为一个静态方法。静态方法就是一个非常独立的方法:既不访问实例的信息,也不访问类的信息。代码:class MyClass: # 定义一个“类属性” count = 0 # 这里开始定义“类方法” @staticmethod def func(): passProperty属性类中的property装饰器,也是python基础类object的一个用于包装、装饰的方法。一旦类方法前放上装饰器@property,方法就会转换为一个类属性。很多时候把方法伪装成属性,是非常方便的。class MyClass: # 这里开始定义由方法转换为“类属性” @property def name(self): return “Jason"c = MyClass()print( c.name )在继承object基础类的情况下,python给出了三种类属性装饰,对应三种操作:读取:@property写入:@name.setter删除:@name.deleter也就是说,当你读取类属性my_name的时候,会调用被@property修饰的方法;当你修改my_name当时候,会调用被@my_name.setter修饰的方法;当你删除这个属性时,会调用被@my_name.deleter修饰的方法。注意:其中@property, @.setter, @.deleter,这是固定的名字,不能改。三种操作所修饰的三个函数,必须都是同一个名字:即“类属性”名。代码:class MyClass: # 这里开始定义由方法转换为“类属性” @property def name(self): return “Jason” @name.setter def name(self, value): self.name = value @name.deleter def name(self): del “Jason"c = MyClass()print( c.name ) # READc.name = “Brown” # SETdel c.name # DELETEproperty属性的应用很多OOP语言,针对property属性,一般操作是:一个私有属性,配合两个公有方法。如:class MyClass: def init(self): self.__name = “Jason” def get_name(self): return self.__name def set_name(self, value): self.__name = valuec = MyClass()# 开始调用c.set_name(“Brownee”)print( c.get_name() )在Python下,可以利用装饰器改为以下代码,极大方便调用的过程:class MyClass: def init(self): self.__name = “Jason” @property def name(self): return self.__name @name.setter def name(self, value): self.__name = valuec = MyClass()# 开始调用c.name = “Brownee"print( c.name ) ...

January 10, 2019 · 2 min · jiezi

Js高级编程笔记--面向对象的程序设计

理解对象属性类型1.数据属性特性:Configurable : 表示能否通过 delete 删除属性,能否修改属性特性,能否把属性改为访问器属性Enumerable : 表示能否通过 for in 循环返回Writable : 表示能否修改属性的值Value : 包含属性的值,读取或写入实际上操作的是这个值2.访问器属性特性:Configurable : 表示能否通过 delete 删除属性,能否修改属性特性,能否把属性改为访问器属性Enumerable : 表示能否通过 for in 循环返回Get : 读取时调用的参数.默认值为 undefinedSet : 写入时调用的参数。 默认值为 undefined3.注意:访问器属性不能直接定义,必须使用 Object.defineProperty()定义。修改属性默认的特性,必须使用 Object.defineProperty()方法get,set,并不一定要定义,只定义 get 为只读,只定义 set 为只写不可读。定义多个属性可以使用 Object.defineProperties()方法读取属性的特性,使用 Object.getOwnPropertyDescriptor()创建对象1.工厂模式定义一个方法接受参数,用于创建对象,并将其返回function createPerson(name, age) { var o = new Object(); o.name = name; o.age = age; return o;}var person1 = createPerson(‘andy_chen’, 18);var person2 = createPerson(‘andy_chen’, 18);工厂模式可以创建多个相似对象的问题,却没解决对象识别的问题。例如person1的类型是什么2.构造函数模式 :function Person(name, age) { this.name = name; this.age = age; this.sayName = function() { alert(this.name); };}var person1 = new Person(‘andy_chen’, 18);var person2 = new Person(‘andy_chen’, 18);person1.sayName();person2.sayName();使用 new 操作符。实际上有以下 4 个步骤:创建一个新对象将构造函数的作用域赋给对象(使 this 指向新对象)执行构造方法(为这个对象添加属性)返回新对象构造函数的问题在于,每个方法都要在每个实例中重新创建一遍。即例子中,person1和person2的sayName的不相等的。但是,完成同样的功能的方法,却每个实例都要创建一遍,这显然不合理,所以,又出现了下面的原型模式3.原型模式:理解原型对象一图胜千言:只要创建了一个新函数,就会根据一组特定规则为该函数创建一个 prototype,这个属性指向函数的对象原型。对象原型中,则默认有一个 constructor 属性,指向该新函数。通过新函数创建的实例,有一个[[prototype]]属性(在 chrome,firefox,safari 中该属性即为proto),指向了新函数的 prototype。注意:该属性仅仅是执行构造函数的 prototype,也即是说,他们与构造函数没有直接联系了读取某个对象的属性时,会先在实例上找,如果没找到,则进一步在实例上的 prototype 属性上找为实例添加属性的时候会屏蔽掉原型上属性。这个时候即使置为 null 也没法访问到原型上的属性,只有通过 delete 删掉之后才可以XXX.prototype.isPrototype(xxx), 可以用这个方法判定对象是否是该实例的原型对象Object.getPrototypeOf() 用这个可以获取实例对应的原型对象 (ES5 新增方法)in 操作符单独使用时: in 操作符 可以确定属性是否存在于对象上(无论是存在于实例上还是原型上)用于 for 循环中时,返回的是所有能够通过对象访问的,可枚举的属性。(IE8 中,如果开发者自定义 toString 类似的系统不可枚举的方法,浏览器还是不会将它遍历出来)ES5:Object.keys() 可以返回一个包含所有可枚举属性的字符串数组Object.getOwnPropertyNames() 可以返回所有实例属性,无论是否可枚举//原型模式的实现:function Person() {}Person.prototype.name = ‘andy chen’;Person.prototype.sayName = function() { alert(this.name);};更简单的原型语法重写整个 prototype,不过会导致 constructor 改变。所以需要重新指定 constructor.//更简单的原型语法function Person() {}Person.prototype = { constructor: Person, //因为这种写法会覆盖掉原来的Person.prototype,需要重新为constructor赋值 name: ‘andy chen’, sayName: function() { alert(this.name); }};var person1 = new Person();var person2 = new Person();原型模式的问题:所有实例都共享一个prototype,类似上面的例子,person1,person2的name属性是共享的。如果修改其中一个,会导致另一个也受影响。所以,才会出现下面构造函数与原型模式组合使用4.组合使用构造函数和原型模式创建自定义类型最常见的方式就是组合使用构造函数和原型模式构造函数定义实例属性,而原型模式用于定义方法和共享的属性. 所以,上面的例子可以改写成这样:function Person(name) { this.name = name;}Person.prototype = { constructor: Person, sayName: function() { alert(this.name); }};var person1 = new Person(‘andy chen’);var person2 = new Person(‘andy chen’);除了使用组合模式创建对象,还有以下几种方式,可以针对不同的情况选择。5.动态原型模式在构造方法中,判断是否是第一次进入使用构造方法,如果是,则添加一系列的方法到原型上6.寄生构造函数模式类基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码然后再返回新创建的对象。7.稳妥构造函数模式:稳妥对象指的是没有公共属性,而且其方法也不引用 this 对象。最适合用于一些安全的环境或者在防止数据被其他程序改动时使用稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:新创建的对象实例不引用 this.不使用 new 操作符调用构造函数继承OO 语言一般拥有两种继承方式:接口继承(只继承方法签名)以及实现继承(继承实际方法)ES 无法像其他 OO 语言一样支持接口继承,只能依靠原型链实现 实现继承1. 原型链要了解原型链的概念,先回顾一下构造函数,原型和实例之间的关系(参考图 6-1)每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针.每个实例都包含一个指向原型对象的内部指针的内部属性(在 chrome 中一般为proto属性)那么,如果我们有个新的构造函数,并让它的原型对象等于另一个类型的实例,结果会怎样.对于这个新的构造函数,它的原型对象就变成了另一个类型的实例,而这个实例中,又包含一个内部属性,指向了另一个原型对象(该原型对象内部 constructor 指向另一个构造函数),如果这个原型对象又是另一个类型的实例,则它又包含了一个内部属性,继续指向上层的原型对象。这样层层递进,就形成了原型链。如下图:特点在实例中搜索属性的时候,便是基于原型链来搜索的,先搜索实例,再在原型链上一层层往上搜,直到找到或者到原型链末端才会停下来由于所有引用类型都继承了 Object,所以原型链的最顶层是 Object使用原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会重写原型链原型链实现继承的方式:function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { this.catName = ‘cat’;}Cat.prototype = new Animal();var cat1 = new Cat();var cat2 = new Cat();alert(cat1.getName()); //由于第10行,将Cat的原型指向Animal的实例,因为实例中有指向Animal.prototype的指针。所以,这里可以访问到getName()cat1.name = ‘changed name’;alert(cat2.getName());原型链的问题:使用原型链,由于是使用新的实例作为子类型的原型,实例中却包含了父类型的属性,所以原来父类型的属性,就都到了子类型的原型上了。这就会造成子类型的不同实例会共享同个属性.如上例子中,第 15 行,改变 cat1 实例的 name 属性影响到了 cat2 的 name 属性创建子类型的时候,不能向父类型传递参数2. 借用构造函数由于原型链存在问题,所以便出现了借用构造函数的方法在子类型的构造方法中,调用父类型的构造方法:SuperType.call(this); 将父类型的属性添加到子类型上,并且可以传递参数给父类型借用构造函数实现继承的方式:function Animal() { this.name = ‘animal’;}function Cat() { Animal.call(this);}var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.name); //changed namealert(cat2.name); //animal //借用构造函数的方式,各实例之间的属性便不会互相影响借用构造函数问题:类似创建对象单纯使用构造方法一样,也会造成公有的方法无法公用。所以一般也很少单独使用此方式3. 组合继承组合原型链以及借用构造函数使用原型链实现对原型属性和方法的继承借用构造函数来实现对实例中属性的继承。function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}Cat.prototype = new Animal(); //原型链方式Cat.prototype.constructor = Cat;//这里可以var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal组合继承的问题:父类的属性会存在于子类型的原型上,导致被不同实例共享。虽然由于借用构造函数之后,导致实例上又重写了这些属性,所以每个实例有各自的属性。另外,instanceof 和 isPrototypeOf 能够识别基于组合继承创建的对象组合继承,并不完美因为我们只需要继承父类型原型上的属性而已,不需要父类型实例的属性。还有更好的方法,但我们首先要先了解一下其他继承方式4. 原型式继承//如果o为某个对象的prototype,则object返回的 对象,包含了该对象原型上的所有方法function object(o) { function F() {} F.prototype = o; return new F();}Es5 新增的 Object.create() ,类似这样。 在没有必要创建构造函数,只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过,包含引用类型值的属性始终会共享5. 寄生式继承在复制新对象后,继续以某种方式增强对象,即为寄生式继承。function createAnother(original) { var clone = object(original); clone.sayHi = function() { doSomeThing(); }; return clone;}在主要考虑对象而不是自定义类型和构造函数的时候,适合使用寄生式继承缺点: 类似单纯的构造函数模式使用,函数不能复用6. 寄生组合式继承通过原型式继承,继承父类的原型方法。再通过构造函数方法,继承父类的属性。function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}//原型继承方式function object(superProto) { function F() {} F.prototype = superProto; return new F();}Cat.prototype = object(Animal.prototype); //通过一个空的函数作为媒介,将空函数的原型指向父类型原型,并将子类型的原型指向这个空函数的实例。便只继承父类原型上的属性及方法Cat.prototype.constructor = Cat;//这里可以之后添加子类的方法Cat.prototype.run = function() { alert(‘cat run’);};var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal最后,寄生组合式继承是引用类型最理想的继承范式。上述代码还能再进一步优化。//原型继承方式function object(superProto) { function F() {} F.prototype = superProto; return new F();}//公用的继承方法function inheritPrototype(subType, superType) { subType.prototype = object(superType.prototype); subType.prototype.constructor = subType;}function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}inheritPrototype(Cat, Animal); //调用此方法继承原型//这里可以之后添加子类的方法Cat.prototype.run = function() { alert(‘cat run’);};var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal小结这是 js 对象的创建以及继承,es6 中新增了关键字class和extend。方便我们进行面向对象的编程。但是理解背后的继承原理对我们编程过程中也是极有帮助的:)喜欢就收藏或者点个赞呗 !! ...

January 2, 2019 · 3 min · jiezi

javascript面向对象之“多态”

ES6之前,javascript本质上不能算是一门面向对象的编程语言,因为它对于封装、继承、多态这些面向对象语言的特点并没有在语言层面上提供原生的支持。但是,它引入了原型(prototype)的概念,可以让我们以另一种方式模仿类,并通过原型链的方式实现了父类子类之间共享属性的继承以及身份确认机制。其实,面向对象的概念本质上来讲不是指某种语言特性,而是一种设计思想。正是由于javascript本身对面向对象编程没有一个语言上的支持标准,所以才有了五花八门、令人眼花缭乱的“类继承”的代码。所以在ES6中出现了class extends等关键字,解决了javascript面向对象中出现了问题。之前花了大量篇幅来讲述面向对象中的封装和继承 谈一谈javascript面向对象 javascript面向对象与原型javascript 面向对象之一篇文章搞定call()方法方法")javascript面向对象之继承(上)")javascript面向对象之继承(下)")javascript面向对象之ES6中的类和继承今天我们研究一下javascript面向对象中的多态javascript 多态如果你在搜索引擎中搜索javascript多态,有那么一个栗子你一定会搜到非多态代码示例var makeSound = function( animal ){ if ( animal instanceof Duck ){ console.log( ‘嘎嘎嘎’ ); }else if ( animal instanceof Chicken ){ console.log( ‘咯咯咯’ ); }};var Duck = function(){};var Chicken = function(){};makeSound( new Duck() ); // 嘎嘎嘎makeSound( new Chicken() ); // 咯咯咯多态的代码示例var makeSound = function(animal) { animal.sound();}var Duck = function(){}Duck.prototype.sound = function() { console.log(‘嘎嘎嘎’)}var Chicken = function() {};Chicken.prototype.sound = function() { console.log(‘咯咯咯’)}makeSound(new Chicken());makeSound(new Duck());这个栗子出自《JavaScript设计模式与开发实践》,我手里没有这本书,但是不耽误我们去研究,这个????不太好扩展,我们继续看郭靖和黄蓉的栗子 function doSth(hero) { hero.doSth(); }; function Gj() { this.name = “郭靖” this.favourite = “吃饭”, this.skill = “降龙十八掌”, this.sex=“男” } Gj.prototype.doSth = function () { console.log(${this.name}练习${this.skill}); }; function Hr() { this.name = “黄蓉” this.like = “做饭” this.skill = “打狗棒” } Hr.prototype.doSth = function () { console.log(${this.name}练习${this.skill}); }; const gj = new Gj() const hr = new Hr() doSth(gj);//=>郭靖练习降龙十八掌 doSth(hr);//=>黄蓉练习打狗棒我们分析一下代码郭靖(Gj)和黄蓉(Hr)都有自己的属性和方法,而且不一定是一一对应关系,(郭靖有的属性和方法,黄蓉不一定有,反之亦然,具体参见代码)郭靖有name,favourite,skill,sex属性和在原型链上添加了doSth方法黄蓉有name,like,skill属性和在原型链是上添加了doSth方法脑洞一下这个场景洪七公站在郭靖和黄蓉的面前,发号施令:“练功”,然后郭靖和黄蓉分别执行自己的doSth方法,进行练功,而且每个人修炼的武功不一样因为郭靖和黄蓉都可以练功都有doSth方法,所以我们将这个方法抽离出来进行封装,这么做有什么好处呢?如果我们不这么做,就得进行条件判断,类似于if else,如果是黄蓉就执行黄蓉的doSth,如果是郭靖就执行郭靖的doSth,如果再来一个人,还要继续判断,当人数越来越多的时候,判断就会越来越多,如果把丐帮弟子都拉过来练功,想想会怎么样呢?多态背后的思想是将“做什么”和“谁去做以及怎样去做”分离开来,也就是将“不变的事物”与“可能改变的事物”分离开来。在这个栗子中,郭靖和黄蓉都会练功(都存在doSth),这是不变的,但是他们所修炼的武功是不同的,所具备的属性也不是完全相同的。把不变的部分隔离出来,把可变的部分封装起来,这给予了我们扩展程序的能力,程序看起来是可生长的,也是符合开放—封闭原则的,相对于修改代码来说,仅仅增加代码就能完成同样的功能,这显然优雅和安全得多。这里有一个问题,如果新来的人没有doSth方法怎么办呢?所以,我们需要添加一个判断 function doSth(hero) { if (hero.doSth instanceof Function) { hero.doSth(); } };instanceof ???instanceof用于判断一个变量是否某个对象的实例可以这么理解,上面的例子中hero.doSth是不是一个函数方法,如果是就执行Martin Fowler 在《重构:改善既有代码的设计》里写到:在电影的拍摄现场,当导演喊出“action”时,主角开始背台词,照明师负责打灯光,后面的群众演员假装中枪倒地,道具师往镜头里撒上雪花。在得到同一个消息时,每个对象都知道自己应该做什么。如果不利用对象的多态性,而是用面向过程的方式来编写这一段代码,那么相当于在电影开始拍摄之后,导演每次都要走到每个人的面前,确认它们的职业分工(类型),然后告诉他们要做什么。如果映射到程序中,那么程序中将充斥着条件分支语句。利用对象的多态性,导演在发布消息时,就不必考虑各个对象接到消息后应该做什么。对象应该做什么并不是临时决定的,而是已经事先约定和排练完毕的。每个对象应该做什么,已经成为了该对象的一个方法,被安装在对象的内部,每个对象负责它们自己的行为。所以这些对象可以根据同一个消息,有条不紊地分别进行各自的工作。将行为分布在各个对象中,并让这些对象各自负责自己的行为,这正是面向对象设计的优点。多态增加了代码的可扩展性,降低了代码维护成本原文链接参考链接Javascript的继承与多态 ...

December 20, 2018 · 1 min · jiezi

谈一谈javascript面向对象

从今天起我们开始讨论javascript的面向对象面向对象概念理解面向对象语言有个标志=>它们都具有类的概念,通过类可以创建任意多个具有相同属性和方法的对象。面向对象有三大特性封装继承多态但JS中对象与纯面向对象语言中的对象是不同的JS中的对象:无序属性的集合,其属性可以包含基本值、对象或者函数。可以简单理解为JS的对象是一组无序的值,其中的属性或方法都有一个名字,根据这个名字可以访问相映射的值(值可以是基本值/对象/方法)。创建对象的基本方法我们前面在讲原型链的时候说过,两种创建对象的方法对象字面量(对象直接量)这是最快的一个????: const hero = { name:“欧阳锋”, nickname:“西毒”, doSth:function(){ console.log(‘灵蛇杖法’); }⚠️创建对象的属性名并不强制使用引号包裹,除了以下几种情况属性名中包含空格属性名中包含连字符(中划线)属性名中包含保留字const obj={ “go home”:“包含了空格”, “go-home”:“包含了连字符”, “for”:“这是保留字”}new 实例化一个对象通过new运算符创建并实例化一个新对象,new后面是一个构造函数const hero = new Object()hero.name = “欧阳锋"hero.nickname = “西毒"hero.doSth = function () { console.log(‘灵蛇杖法’);}两种创建方法是一样的创建对象通过以上两种方式似乎足够了,但是当场景稍微复杂一点,问题就显现出来了当我门创建很多结构相同的对象时,会产生大量的重复代码,为了解决这个问题,出现了一个解决方案工厂模式工厂模式抽象了创建具体对象的过程,因为javascript无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象一个????:function createHero(name,nickname,doSth) { const obj = new Object() obj.name=name obj.nickname=nickname obj.doSth = function () { console.log(doSth); } return obj}const hero1 = createHero(“欧阳锋”,“西毒”,“灵蛇杖法”)const hero2 = createHero(“黄药师”,“东邪”,“碧海潮生曲”)console.log(hero1)看下输出:hero1和hero2都直接继承自Object实例,工厂模式就是像工厂一样来创建对象,创建的每一个对象都是通过new Object()来创建的后来,开发人员有发现了更好的模式构造函数模式我们之前讨论过,通过使用自定义构造函数来实例化对象function Hero(name, nickname, doSth) { this.name = name this.nickname = nickname this.doSth = function () { console.log(doSth); }}const hero3 = new Hero(“欧阳锋”,“西毒”,“灵蛇杖法”)const hero4 = new Hero(“黄药师”,“东邪”,“碧海潮生曲”)console.log(hero3);注意⚠️:创建自定义构造函数,函数名首字母大写,用来和非构造函数进行区分我们继续看下输出:hero3是通过Hero实例化出来的,所以hero3先继承自Hero要创建Hero的新实例,必须使用new操作符,以这种方式调用构造函数实际上会经历以下四个步骤,创建一个新对象将构造函数的作用域赋给新对象(因此this就指向了这个新对象)执行构造函数中的代码(为这个新对象添加属性)返回新对象hero3和hero4都是Hero的实例,同时也是Object的实例instanceof用于判断一个变量是否某个对象的实例console.log(hero3 instanceof Hero);//=>trueconsole.log(hero3 instanceof Object);//=>trueconsole.log(hero4 instanceof Hero);//=>trueconsole.log(hero4 instanceof Object);//=>true属性和方法(公有&私有)????的????中,我们将属性和方法绑定在了构造函数Hero中的this上,hero3和hero4都可以访问这些属性绑定在this上的属性我们称之为公有属性绑定在this上的方法我们称之为公有方法也就是说通过构造函数Hero实例化出来的对象是可以方位公有属性和公有方法的既然有公有属性和公有方法,就一定会有私有属性和私有方法我们做一下调整function Hero(name, nickname, doSth) { let test = “私有属性” function method(){console.log(“私有方法”);} this.name = name this.nickname = nickname this.doSth = function () { console.log(doSth); }}const hero3 = new Hero(“欧阳锋”,“西毒”,“灵蛇杖法”)const hero4 = new Hero(“黄药师”,“东邪”,“碧海潮生曲”)console.log(hero3);看下输出:在hero3中是不存在构造函数的私有属性和私有方法的如果我们这创建完构造函数后,追加一下属性和方法,会怎么样呢?试试看 function Hero(name, nickname, doSth) { let test = “私有属性” function method(){console.log(“私有方法”);} this.name = name this.nickname = nickname this.doSth = function () { console.log(doSth); } } Hero.localAttr=“测试属性” Hero.localMethod=function(){ console.log(‘测试方法’); } Hero.prototype.proAttr=“原型属性” Hero.prototype.proMethod=function(){ console.log(‘原型方法’); } const hero3 = new Hero(“欧阳锋”,“西毒”,“灵蛇杖法”) const hero4 = new Hero(“黄药师”,“东邪”,“碧海潮生曲”) console.log(hero3); console.log(’localAttr测试属性:’,hero3.localAttr); console.log(“localMethod测试方法:",hero3.localMethod); console.log(“proAttr原型属性:",hero3.proAttr); console.log(“proMethod原型方法:",hero3.proMethod);看输出:创建完实例对象后,通过.运算符添加的属性是类静态公有属性(实例化的对象无法访问)通过.运算符添加的方法是类静态公有方法(实例化的对象无法访问)通过原型链添加的属性是公有属性(实例化的对象可以访问)通过原型链添加的方法是公有方法(实例化的对象可以访问)今天就到这里,明天不见不散收集整理了一套js进阶教程,公众号后台回复“js进阶”即可领取参考文献:《javascript高级程序设计》(第三版)《javascript设计模式》《javascript语言精粹》(修订版)原文链接 ...

December 14, 2018 · 1 min · jiezi