• 面向对象编程中,封装、抽象、继承、多态分别可以解决哪些编程问题?


    理解面向对象编程及面向对象编程语言的关键就是理解其四大特性:封装、抽象、继承、多态。

    不过,对于这四大特性,光知道它们的定义是不够的,我们还要知道每个特性存在的意义和目的,以及它们能解决哪些编程问题。

    ㉿ 封装(Encapsulation)

    封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。

    对于封装这个特性,我们需要编程语言本身提供一定的语法机制来支持。这个语法机制就是访问权限控制 。private 、public 等关键字就是Java 语言中的访问权限控制语法。private关键字修饰的属性只能类本身访问,可以保护其不被类之外的代码直接访问。

    如果Java 没有提供访问权限控制语法,所有的属性默认都是public 的,那任意外部代码都可以通过类似wallet.id=123 ; 这样的方式直接访问、修改属性,也就没办法达到隐藏信息和保护数据的目的了,也就无法支持封装特性了。

    1、封装的意义是什么?它能解决什么编程问题?

    如果我们对类中属性的访问不做限制,那任何代码都可以访问、修改类中的属性,虽然这样看起来更加灵活。

    但从另一方面来说,过度灵活也意味着不可控,属性可以随意被以各种奇葩的方式修改,而且修改逻辑可能散落在代码中的各个角落,势必影响代码的可读性、可维护性。

    比如某个同事在不了解业务逻辑的情况下,在某段代码中“偷偷地”重设了wallet 中的balanceLastModifiedTime 属性,这就会导致balance 和balanceLastModifiedTime 两个数据不一致。

    除此之外,类仅仅通过有限的方法暴露必要的操作,也能提高类的易用性。

    如果我们把类属性都暴露给类的调用者,调用者想要正确地操作这些属性,就势必要对业务细节有足够的了解。

    而这对于调用者来说也是一种负担。相反,如果我们将属性封装起来,暴露少许的几个必要的方法给调用者使用,调用者就不需要了解太多背后的业务细节,用错的概率就减少很多。

    这就好比,如果一个冰箱有很多按钮,你就要研究很长时间,还不一定能操作正确。相反,如果只有几个必要的按钮,比如开、停、调节温度,你一眼就能知道该如何来操作,而且操作出错的概率也会降低很多。

    ㉿ 抽象(Abstraction)

    封装主要讲的是如何隐藏信息、保护数据,而抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。

    在面向对象编程中,我们常借助编程语言提供的接口类(比如Java 中的interface 关键字语法)或者抽象类(比如Java 中的abstract 关键字语法)这两种语法机制,来实现抽象这一特性。


     

    在上面的这段代码中,我们利用Java 中的interface 接口语法来实现抽象特性。调用者在使用图片存储功能的时候,只需要了解IPictureStorage 这个接口类暴露了哪些方法就可以了,不需要去查看PictureStorage 类里的具体实现逻辑。

    实际上,抽象这个特性是非常容易实现的,并不需要非得依靠接口类或者抽象类这些特殊语法机制来支持。

    换句话说,并不是说一定要为实现类(PictureStorage )抽象出接口类(IPictureStorage ),才叫作抽象。即便不编写IPictureStorage 接口类,单纯的PictureStorage 类本身就满足抽象特性。

    之所以这么说,那是因为,类的方法是通过编程语言中的“函数”这一语法机制来实现的。通过函数包裹具体的实现逻辑,这本身就是一种抽象。

    调用者在使用函数的时候,并不需要去研究函数内部的实现逻辑,只需要通过函数的命名、注释或者文档,了解其提供了什么功能,就可以直接使用了。

    比如,我们在使用C 语言的malloc() 函数的时候,并不需要了解它的底层代码是怎么实现的。

    1、抽象有时候会被排除在面向对象的四大特性之外,为什么呢?

    抽象这个概念是一个非常通用的设计思想,并不单单用在面向对象编程中,也可以用来指导架构设计等。

    而且这个特性也并不需要编程语言提供特殊的语法机制来支持,只需要提供“函数”这一非常基础的语法机制,就可以实现抽象特性、所以,它没有很强的“特异性”,有时候并不被看作面向对象编程的特性之一。

    2、抽象的意义是什么?它能解决什么编程问题?

    如果上升一个思考层面的话,抽象及其前面讲到的封装都是人类处理复杂性的有效手段。

    在面对复杂系统的时候,人脑能承受的信息复杂程度是有限的,所以我们必须忽略掉一些非关键性的实现细节。而抽象作为一种只关注功能点不关注实现的设计思路,正好帮我们的大脑过滤掉许多非必要的信息。

    除此之外,抽象作为一个非常宽泛的设计思想,在代码设计中,起到非常重要的指导作用。

    很多设计原则都体现了抽象这种设计思想,比如基于接口而非实现编程、开闭原则(对扩展开放、对修改关闭)、代码解耦(降低代码的耦合性)等。我们在讲到后面的内容的时候,会具体来解释。

    换一个角度来考虑,我们在定义(或者叫命名)类的方法的时候,也要有抽象思维,不要在方法定义中,暴露太多的实现细节,以保证在某个时间点需要改变方法的实现逻辑的时候,不用去修改其定义。

    举个简单例子,比如getAliyunPictureUrl() 就不是一个具有抽象思维的命名,因为某一天如果我们不再把图片存储在阿里云上,而是存储在私有云上,那这个命名也要随之被修改。相反,如果我们定义一个比较抽象的函数,比如叫作getPictureUrl() ,那即便内部存储方式修改了,我们也不需要修改命名。

    ㉿ 继承(Inheritance)

    继承是用来表示类之间的is-a 关系,比如猫是一种哺乳动物。从继承关系上来讲,继承可以分为两种模式,单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类,比如猫既是哺乳动物,又是爬行动物。

    为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持,比如Java 使用extends关键字来实现继承,C++ 使用冒号(class B : public A ),Python 使用paraentheses(),Ruby 使用< 。

    不过,有些编程语言只支持单继承,不支持多重继承,比如Java 、PHP 、C# 、Ruby 等,而有些编程语言既支持单重继承,也支持多重继承,比如C++ 、Python 、Perl 等。

    1、继承存在的意义是什么?它能解决什么编程问题?

    继承最大的一个好处就是代码复用。

    假如两个类有一些相同的属性和方法,我们就可以将这些相同的部分,抽取到父类中,让两个子类继承父类。这样,两个子类就可以重用父类中的代码,避免代码重复写多遍。

    不过,这一点也并不是继承所独有的,我们也可以通过其他方式来解决这个代码复用的问题,比如利用组合关系而不是继承关系。

    如果我们再上升一个思维层面,去思考继承这一特性,可以这么理解:我们代码中有一个猫类,有一个哺乳动物类。猫属于哺乳动物,从人类认知的角度上来说,是一种is-a 关系。我们通过继承来关联两个类,反应真实世界中的这种关系,非常符合人类的认知,而且,从设计的角度来说,也有一种结构美感。

    继承的概念很好理解,也很容易使用。

    不过,过度使用继承,继承层次过深过复杂,就会导致代码可读性、可维护性变差。为了了解一个类的功能,我们不仅需要查看这个类的代码,还需要按照继承关系一层一层地往上查看“父类、父类的父类……”的代码。还有,子类和父类高度耦合,修改父类的代码,会直接影响到子类。

    所以,继承这个特性也是一个非常有争议的特性。很多人觉得继承是一种反模式,我们应该尽量少用,甚至不用。

    ㉿ 多态(Polymorphism)

    多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。对于多态这种特性,纯文字解释不好理解,我们还是看一个具体的例子。

    多态这种特性也需要编程语言提供特殊的语法机制来实现。

            第一个语法机制是编程语言要支持父类对象可以引用子类对象 。

            第二个语法机制是编程语言要支持继承 。

            第三个语法机制是编程语言要支持子类可以重写(override )父类中的方法。

    通过这三种语法机制配合在一起,我们就实现了多态特性。

    对于多态特性的实现方式,除了利用“继承加方法重写”这种实现方式之外,我们还有其他两种比较常见的的实现方式,一个是利用接口类语法,另一个是利用duck-typing 语法。

    不过,并不是每种编程语言都支持接口类或者duck-typing 这两种语法机制,比如C++ 就不支持接口类语法,而duck-typing 只有一些动态语言才支持,比如Python 、JavaScript 等。

    1、接下来来看如何利用接口类来实现多态特性。


     

    在这段代码中,Iterator 是一个接口类,定义了一个可以遍历集合数据的迭代器。Array 和LinkedList 都实现了接口类Iterator 。我们通过传递不同类型的实现类(Array 、LinkedList )到print(Iterator iterator) 函数中,支持动态的调用不同的next() 、hasNext() 实现。

    2、再来看下,如何用duck-typing 来实现多态特性。

    我们还是先来看一段代码。这是一段Python 代码。


     

    从这段代码中可以发现,duck-typing 实现多态的方式非常灵活。Logger 和DB 两个类没有任何关系,既不是继承关系,也不是接口和实现的关系,但是只要它们都有定义了record() 方法,就可以被传递到test() 方法中,在实际运行的时候,执行对应的record() 方法。

    也就是说,只要两个类具有相同的方法,就可以实现多态,并不要求两个类之间有任何关系,这就是所谓的duck-typing ,是一些动态语言所特有的语法机制。

    3、多态特性存在的意义是什么?它能解决什么编程问题?

    多态特性能提高代码的可扩展性和复用性。为什么这么说呢?我们回过头去看讲解多态特性的时候,上面举的 Iterator 的例子。

    在那个例子中,我们利用多态的特性,仅用一个print() 函数就可以实现遍历打印不同类型(Array 、LinkedList )集合的数据。

    当再增加一种要遍历打印的类型的时候,比如HashMap ,我们只需让HashMap 实现Iterator 接口,重新实现自己的hasNext() 、next() 等方法就可以了,完全不需要改动print() 函数的代码。所以说,多态提高了代码的可扩展性。

    除此之外,多态也是很多设计模式、设计原则、编程技巧的代码实现基础,比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的if-else 语句等等。

    ㉿ 重点回顾·总结

    1、关于封装特性

    封装也叫作信息隐藏或者数据访问保护。

    类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或者数据。

    它需要编程语言提供权限访问控制语法来支持。封装特性存在的意义,一方面是保护数据不被随意修改,提高代码的可维护性;另一方面是仅暴露有限的必要接口,提高类的易用性。

    2、关于抽象特性

    封装主要讲如何隐藏信息、保护数据,那抽象就是讲如何隐藏方法的具体实现,让使用者只需要关心方法提供了哪些功能,不需要知道这些功能是如何实现的。

    抽象可以通过接口类或者抽象类来实现,但也并不需要特殊的语法机制来支持。

    抽象存在的意义,一方面是提高代码的可扩展性、维护性,修改实现不需要改变定义,减少代码的改动范围;另一方面,它也是处理复杂系统的有效手段,能有效地过滤掉不必要关注的信息。

    3、关于继承特性

    继承是用来表示类之间的is-a 关系,分为两种模式:单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类。

    为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持。继承主要是用来解决代码复用的问题。

    4、关于多态特性

    多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。多态这种特性也需要编程语言提供特殊的语法机制来实现,比如继承、接口类、duck-typing 。

    多态可以提高代码的扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。


     

    最后,不管你是转行也好,初学也罢,进阶也可,如果你想学编程~

    【值得关注】我的 C/C++编程学习交流俱乐部!【点击进入】

    问题答疑,学习交流,技术探讨,还有超多编程资源大全,零基础的视频也超棒~

  • 相关阅读:
    谁在TDD
    开源许可证简单总结
    【转】IIS HTTP500错误以及COM+应用程序8004e00f错误的解决方法
    [原]Linux平台Boost的编译方法
    [原]linux下格式化磁盘的相关问题
    [原]编译MongoDB,C++连接MongoDB测试
    [转]谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词(科普)
    [转]linux下如何查看文件编码格式及转换文件编码
    [原]linux(虚拟机)下安装MySQL
    [转]Linux下比较全面的监控工具dstat
  • 原文地址:https://www.cnblogs.com/huya-edu/p/14217075.html
Copyright © 2020-2023  润新知