面向对象 (Object Oriented,OO) 的思想对软件开发相当重要,它的概念和应用甚至已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD 技术、人工智能等领域。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。一种比较先进的代码组织和重用方法。
面向过程 (Procedure Oriented) 是一种 以过程为中心 的编程思想。这些都是以什么正在发生为主要目标进行编程,不同于面向对象的是谁在受影响。与面向对象明显的不同就是 封装、继承、类。
无论是在软件开发还是在实际工作中,深入地理解软件开发的思想都非常有必要。
在一个软件村里。
有一名资深「面向过程」程序员——老过。
和一名「面向对象」信徒——阿对。
同时受雇于一家挨踢店。
有一天老板突发奇想。
决定让这两名程序员进行一次比赛。
获胜者将获得一个限量的360 度全自动按摩椅。
编程比赛开始了。
老板:“写一个店里的收银程序,根据客人购买的商品单价和数量,生成所购商品的金额”。
不一会,他俩都写出了几乎相同的代码。
class Bill{ // 获取总价 fun getPrice(): Double { val unit = getUnit() ; val number = getNumber() ; val price = unit * number ; return price; } // 获取单价 fun getUnit(): Double { //…… } // 获取数量 fun getNumber(): Int{ //…… } }
老板:“效率很高嘛!不过先别得意, 这只是第一关而已。快七夕了,我们店要在七夕节对商品搞活动 所有的商品打77折。 ”
老过:“小case ,只要加一个分支 语句就可以了。”
老过看到新需求,微微一笑。
class Bill{ // 获取总价 fun getPrice(): Double { val unit = getUnit() ; val number = getNumber() ; val price = unit * number ; if (todayIsLoversDay()) { return price * 0.77; } return price; } // 获取单价 fun getUnit(): Double { //…… } // 获取数量 fun getNumber(): Int{ //…… } fun todaylsLoversDay(): Boolean { //…… } }
阿对:“咱又没有女朋友, 过什么七夕节? 新的收银方式应该属于新的对象,它和普通收费方式有共同点:先获取单价和数量,再计算总价。”
阿对决定让新的收银方式继承 Bill 类。 先在 Bill 类中新增 discount 方法。 并将其开放。class Bill{ // 获取总价 fun getPrice(): Double { val unit = getUnit() ; val number = getNumber() ; val price = unit * number ; return discount(price) ; } //处理打折优惠 open fun discount(price: Double): Double{ return price; } // 获取单价 fun getUnit(): Double { //…… } // 获取数量 fun getNumber(): Int{ //…… } }
普通的收费方式在 discount函数中直接返回价格,七夕节的收费方式则继承此类,在 discount 函数中实现打 77折。
class LoversDayBi11 : Bill(){ override fun discount(price: Double): Double{ return price * 0.77 ; } }
当老过和阿对同时将程序交给老板时。
老过:“我只改了一处就实现了新需求,阿对竟然改了两处,孰优孰劣还不清楚吗?”
老过已经开始幻想自己将来。
坐在按摩椅上的舒服日子。
老板:“第二关老过的程序看起来更 简洁明了,暂时领先。现在我需要增加需求:”
1 .中秋节:购物满399减100; 2 .国庆节:购物总价100元以内1/10概率免单;
听到新需求
老过一阵头大
不由在「面向过程天下第一群」中吐槽道
阿过:“不知道老板怎么想的,非要搞这么多不一样的活动,搞活动的时候继续打77折多好,这样我就只需要在todaylsLoversDay中添加中秋节和国庆节的判断,将它修改为 todaylsDiscountDay就好了”。
群友也附和说。
可不是嘛,
就是因为老板这么多需求,
才会搞得面向过程的程序一团糟?
吐槽归吐槽,
老过在 getPrice 函数中再次增加了条件判断,
class Bill{ // 获取总价 fun getPrice(): Double { val unit = getUnit() ; val number = getNumber() ; val price = unit * number ; if (todayIsLoversDay()) { return price * 0.77; } if (todayIsMiddleAutunin() && price > 399) { return price - 200 ; } if (todaylsNationalDay() && price < 100) { //生成0 - 9随机数字,如果为0则免单。即:十分之一概率免单 val free = Random().nextlnt(10) if (free == 0) { return 0.0 ; } } return price; } // 获取单价 fun getUnit(): Double { //…… } // 获取数量 fun getNumber(): Int{ //…… } fun todaylsLoversDay(): Boolean { //…… } fun todayIsMiddleAutumn(): Boolean { //…… } fun todaylsNationalDay(): Boolean { //…… } }
看着越来越复杂的。
老过眉头紧锁,
他深知事情远没有结束。
中秋和国庆一过,
他还需要到这个复杂的类中,
删掉打折的方法。
天知道老板还会再提什么天马行空的需求。
无论是新增或删除代码,在这个过长的类里做修改都是件不太愉快的事。为了在一个很长的函数中找到需要修改的位置,「面向过程」使得老过不得不浏览大量与修改无关的代码,小心翼翼地修改后,又要反复确认不会影响到类的其他部分。
老过在心底里默默地祈祷。
这个类不再需要修改,
提交了自己的程序。
阿对收到新需求时,
先是新增了中秋节支付类。
class MiddleAutumePrice : Bill() { override fun discount(price: Double): Double { if (price > 399) { return price - 200 } return super.discount(price) } }再增加了国庆节支付类。
class NationalDayBill : Bill() { override fun discount(price: Double): Double { if (price < 100) { //生成0 ~ 9随机数字,如果为0则免单。即:十分之一概率免单 val free = Random().nextlnt(10) if (free == 0) { return 0.0 } } return super.discount(price) } }
「面向对象」让阿对最喜欢的一点是:
应对新需求时,无需更改已经测试通过的既有的类。它良好的灵活性和扩展性仿佛天生就是用来适应变化的。
每个支付方式互相独立,若某天不再需要时,将对应的类删除即可。
“有一个好消息要告诉大家!"
当老板兴高采烈地说出这句话时,
老过和阿对都露出了心惊胆战的表情,
这句话往往意味着要更改需求。
老板说:
“咱们的七夕活动宣传海报挂出去以后,反响非常好!我决定加大优惠力度。
在七夕节当天,凡是到本店来购物的情侣,
满99元及以上的订单,随机附赠精美礼品一份「鲜花、巧克力、9.9元抵扣券」,
之前打77折的活动,也修改为仅限情侣参加。”
“这样对单身狗的伤害太大了吧!不仅没有优惠,也无法获得礼品。我还是觉得一视 同仁比较好。”
老过反抗道。
但他并没有说出心里另一个小九九,
实在不想再去给 Bill 类添加代码了。
“不,我们就是要鼓励情侣结 伴前来。况且,这对单身人 士来说,也是和异性搭讪的 好机会。”老过说。
老过无奈再次修改了自己的程序。
我可以在判断七夕节的代码中加上随机获取礼物的代码,这样应该也还可以运行。太naive 了,天知道老板下次又会再有什么奇怪的修改。难道每次修改都要这么难受吗?
这次修改老过花了较长的时间才完成。
class Bill{ val gifts = listOf("flower", "chocolate", "9.9 discount") // 获取总价 fun getPrice(): Double { val unit = getUnit() ; val number = getNumber() ; val price = unit * number ; if (todayIsLoversDay()&& isCouple()) { if (price > 99) { val lucky = Random().nextlnt(gifts.size) println("Congratulations on getting ${gifts[lucky]}!") } return price * 0.77 } if (todayIsMiddleAutunin() && price > 399) { return price - 200 ; } if (todaylsNationalDay() && price < 100) { //生成0 - 9随机数字,如果为0则免单。即:十分之一概率免单 val free = Random().nextlnt(10) if (free == 0) { return 0.0 ; } } return price; } // 获取单价 fun getUnit(): Double { //…… } // 获取数量 fun getNumber(): Int{ //…… } fun todaylsLoversDay(): Boolean { //…… } private fun isCouple(): Boolean { //…… } fun todayIsMiddleAutumn(): Boolean { //…… } fun todaylsNationalDay(): Boolean { //…… } }
看着那个只属于七夕节的 gifts 变量,
老过像看着自己白衬衫上的油渍一样难受。
以后每次收费时都会生成一个只有七夕节才会用到的变量,
都是因为老板的需求太奇葩,
才让这个程序看起来乱糟糟的。
由于这个类做了修改,
本来已经测试通过的代码又得重测一遍。
阿对打开了 LoversDayBill 类,
将其修改如下:
class LoversDayBill : Bill() { val gifts = listOf("flower", "chocolate", "9.9 discount") override fun discount(price: Double): Double { if (!isCouple()) return price if (price > 99) { val lucky = Random().nextlnt(gifts.size) printlnf"Congratulations on getting ${gifts[lucky]}!") } return price * 0.77 } fun isCouple(): Boolean { //… } }
阿对:“程序的其他部分则完全不需要修改,测试时也仅需要重测这一个类而已。面向对象真是太酷了 !”
“中秋节只有满减太缺少气氛了,咱们再加上送月饼活动吧!”
当老板看完老过和阿对的代码后,
再次兴奋地提出新需求时,
老过顿时晕了过去......
比赛真是太焦灼了,
最后赢得奖励的是?
第三个参赛者,
老板的傻儿子。
他完全不会写程序,
但他使用 Ctrl+C,Ctrl+V
拷贝了阿对的代码。
在面试中,面向对象的常见考察点是三个基本特征:封装、继承、多态。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来类的情况下对这些功能进行扩展。通过继承创建的新类称为「子类」或「派生类」,被继承的类称为「基类」、「父类」或「超类」。
要实现继承,可以通过 继承和组合 来实现。
多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单说就是一句话:允许将子类类型的指针赋值给父类类型的指针。
实现多态,有两种方式,覆盖和重载。两者的区别在于:覆盖在运行时决定,重载是在编译时决定。并且覆盖和重载的机制不同。例如在 Java 中,重载方法的签名必须不同于原先方法的,但对于覆盖签名必须相同。
我对面向对象的理解:面向对象的编程方式使得每一个类都只做一件事。面向过程会让一个类越来越全能,就像一个管家一样做了所有的事。而面向对象像是雇佣了一群职员,每个人做一件小事,各司其职,最终合作共赢!
最后,我们谈谈面向对象有什么好处?
《大话设计模式》中大鸟给小菜讲的故事非常经典:
“话说三国时期,曹操带领百万大军攻打东吴,大军在长江赤壁驻扎,军船连成一片,眼看就要灭掉东吴,统一天下,曹操大悦,于是大宴众文武,在酒席间,曹操诗性大发,不觉吟道:‘喝酒唱歌,人生真爽……’众文武齐呼:‘丞相好诗!’于是一臣子速命印刷工匠刻版印刷,以便流传天下。”
“样张出来给曹操一看,曹操感觉不妥,说道:‘喝与唱,此话过俗,应改为‘对酒当歌’较好!’于是此臣就命工匠重新来过。工匠眼看连夜刻版之工,彻底白费,心中叫苦不迭。只得照办。”
“样张再次出来请曹操过目,曹操细细一品,觉得还是不好,说:‘人生真爽‘太过直接,应改问语才够意境,因此应改为‘对酒当歌,人生几何……’当臣子转告工匠之时,工匠晕倒……”
大鸟:“小菜你说,这里面问题出在哪里?”
小菜:“是不是因为三国时期活字印刷还未发明,所以要改字的时候,就必须要整个刻板全部重新刻。”
大鸟:“说得好!如果是有了活字印刷,则只需更改四个字就可,其余工作都未白做。岂不妙哉。
一、要改,只需更改要改之字,此为可维护;
二、这些字并非用完这次就无用,完全可以在后来的印刷中重复使用,此乃可复用;
三、此诗若要加字,只需另刻字加入即可,这是可扩展;
四、字的排列其实可能是竖排可能是横排,此时只需将活字移动就可做到满足排列需求,此是灵活性好。”
“而在活字印刷术出现之前,上面的四种特性都无法满足,要修改,必须重刻,要加字,必须重刻,要重新排列,必须重刻,印完这本书后,此版已无任何可再利用价值。”
小菜:“是的,小时候我一直奇怪,为何火药、指南针、造纸术都是从无到有,从未知到发现的伟大发明,而活字印刷仅仅是从刻版印刷到活字印刷的一次技术上的进步,为何不是评印刷术为四大发明之一呢?原来活字印刷是思想的成功,面向对象的胜利。”
本页共351段,13334个字符,20587 Byte(字节)