设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
17458 人已学习
课程目录
已更新 20 讲 / 共 100 讲
0/6登录后,你可以任选6讲全文学习。
开篇词 (1讲)
开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!
免费
设计模式学习导读 (3讲)
01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?
02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
设计原则与思想:面向对象 (11讲)
04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?
05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?
08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
设计原则与思想:设计原则 (3讲)
15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?
17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
不定期加餐 (2讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
设计模式之美
登录|注册

05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?

王争 2019-11-13
上一节课,我简单介绍了面向对象的一些基本概念和知识点,比如,什么是面向对象编程,什么是面向对象编程语言等等。其中,我们还提到,理解面向对象编程及面向对象编程语言的关键就是理解其四大特性:封装、抽象、继承、多态。不过,对于这四大特性,光知道它们的定义是不够的,我们还要知道每个特性存在的意义和目的,以及它们能解决哪些编程问题。所以,今天我就花一节课的时间,针对每种特性,结合实际的代码,带你将这些问题搞清楚。
这里我要强调一下,对于这四大特性,尽管大部分面向对象编程语言都提供了相应的语法机制来支持,但不同的编程语言实现这四大特性的语法机制可能会有所不同。所以,今天,我们在讲解四大特性的时候,并不与具体某种编程语言的特定语法相挂钩,同时,也希望你不要局限在你自己熟悉的编程语言的语法思维框架里。

封装(Encapsulation)

首先,我们来看封装特性。封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。这句话怎么理解呢?我们通过一个简单的例子来解释一下。
下面这段代码是金融系统中一个简化版的虚拟钱包的代码实现。在金融系统中,我们会给每个用户创建一个虚拟钱包,用来记录用户在我们的系统中的虚拟货币量。对于虚拟钱包的业务背景,这里你只需要简单了解一下即可。在面向对象的实战篇中,我们会有单独两节课,利用 OOP 的设计思想来详细介绍虚拟钱包的设计实现。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(201)

  • Smallfly
    争哥对面向对象的总结完美符合 What/How/Why 模型,我按照模型作下梳理。

    ## 封装
    What:隐藏信息,保护数据访问。
    How:暴露有限接口和属性,需要编程语言提供访问控制的语法。
    Why:提高代码可维护性;降低接口复杂度,提高类的易用性。

    ##抽象
    What: 隐藏具体实现,使用者只需关心功能,无需关心实现。
    How: 通过接口类或者抽象类实现,特殊语法机制非必须。
    Why: 提高代码的扩展性、维护性;降低复杂度,减少细节负担。

    ##继承
    What: 表示 is-a 关系,分为单继承和多继承。
    How: 需要编程语言提供特殊语法机制。例如 Java 的 “extends”,C++ 的 “:” 。
    Why: 解决代码复用问题。

    ##多态
    What: 子类替换父类,在运行时调用子类的实现。
    How: 需要编程语言提供特殊的语法机制。比如继承、接口类、duck-typing。
    Why: 提高代码扩展性和复用性。

    3W 模型的关键在于 Why,没有 Why,其它两个就没有存在的意义。从四大特性可以看出,面向对象的终极目的只有一个:可维护性。易扩展、易复用,降低复杂度等等都属于可维护性的实现方式。
    2019-11-14
    6
    192
  • №修罗★幻影
    Java 不支持多重继承的原因
    多重继承有副作用:钻石问题(菱形继承)。
    假设类 B 和类 C 继承自类 A,且都重写了类 A 中的同一个方法,而类 D 同时继承了类 B 和类 C,那么此时类 D 会继承 B、C 的方法,那对于 B、C 重写的 A 中的方法,类 D 会继承哪一个呢?这里就会产生歧义。
    考虑到这种二义性问题,Java 不支持多重继承。但是 Java 支持多接口实现,因为接口中的方法,是抽象的(从JDK1.8之后,接口中允许给出一些默认方法的实现,这里不考虑这个),就算一个类实现了多个接口,且这些接口中存在某个同名方法,但是我们在实现接口的时候,这个同名方法需要由我们这个实现类自己来实现,所以并不会出现二义性的问题。
    2019-11-13
    12
    179
  • 哥本
    我理解的四大特性
    封装:加装备(添加盔甲)
    继承:师傅掌对掌传输武功(毫无保留)
    抽象:从道到术,柳叶能伤人
    多态:奥特曼变身。
    2019-11-13
    9
    24
  • 小白
    go语言的“隐藏式接口”算是多态中duck-typing的实现方式吧
    2019-11-13
    2
    16
  • 拉格朗日的忧桑
    这是迄今讲面向对象特性最深刻的,没有之一

    作者回复: 😄 多谢认可

    2019-11-13
    15
  • weiguozhihui
    c 语言通过结构体来实现封装,只是c 的结构体没有关键字来控制结构体内部成员的访问权限问题,属于一种比较粗的封装。另外C中通过void*+结构体+函数指针也是可以实现多太的。Linux内核代码好多都是用了面向对象编程思想。C++中引入public protected private 关键字来进行访问控制权管理。C++中没有Java中的interface 关键字来描述接口类,但也是可以通过虚函数基类来进行的Java中的接口类的。C++是直接支持多继承的,但这个特性也被好多人诟病。
    2019-11-13
    1
    9
  • 划时代
    话题一:
    C++语言的多重继承,存在三层继承关系时,采用virtual继承方式,形成菱形继承。标准库中的iostream类就存在多重继承关系,见图http://www.cplusplus.com/img/iostream.gif。
    话题二:
    C++语言的多态实现方式比较丰富,除了类中的virtual函数实现运行期多态以外。还支持编译期多态(模板元编程),不仅能够动态改变对象执行的函数,还能动态改变对象的定义类型。
    2019-11-13
    9
  • 业余草
    是时候抛出这道难住99%的程序员的设计模式面试题了!
    https://mp.weixin.qq.com/s/9SBV9ZycAQY82BacYICY2w
    2019-11-13
    19
    9
  • 丁丁历险记
    好久没复杂这些基础知识了,借今天写笔记的时间过足瘾。我就菜鸟一个,也深知言多必失,肯定有瞎扯的地方,还请指正,我好迭代。
    1 封装
    常见编程语言通过 public protected privite 来支持
    封装的意义 。
    关掉直接操作数据的口子,让数据的修改更贴合真实业务。
    例如:创建时间,就没有修改数据的场景。
    钱包的钱数,只能通过增加,减少接口进行调整,不开放直接设置金额的接口(口子一开,对账就是个麻烦事)
    另一个意义,调用类,没有必要知道太多的实施类的具体实现细节。让操作更为简单。
    个人思考:
    封装就是让代码遵守开闭原则的重要手段。
    当调用类,关注过多实施类的继节时,一个需求的变化,可能引发多个依赖模块都发生了级联的改动,而一但调用类过度的知道了太多了实施类的细节(然后各种调用类代码再一散落),这时程序也相应地变得出各种毛病出来,既然这样,把实现的方法封装起来多好,让调用端少操点心。
    Law of Demeter 有个别名,就叫最少知道原则,争哥说的第二个意义,感觉就是在描述遵守迪米特法则的好处.
    再乱想一下, 人体,不就是最好的的封装么, 给你物料的入口(吃),出口(拉),还给你调节增强的接口(训练) 有多少人,并不知道其内部细节,不照样过着精彩的一生。 还有,调用类,总想知道更多被调用类的信息,以期更精准的控制,这种代码风格,就是完美主义病,而完美主义的并发症就是拖延症,以及极高的复杂度造成项目越写越混乱。
    2 抽像。
    抽像讲的是如何隐藏信息,而抽像讲的是隐藏方法的具体实现,让调用者只关心方法提供了哪些功能即可。
    通常借 interface 和 abstract class 来实现 抽像这一特性。当implement 某接口时,只要知道接口干了啥,即可,无需要关心实施类的细节实现。
    抽你的意义,能解决啥问题。
    1 处理复杂性的有效手段。人脑承受信息有限,抽像做为不关注实现细节的套路,正好管用。
    2抽像指导了很多的oo原则 , 凡时对实现进行封装抽离的,都可以叫抽像,例如malloc()
    3 定义类时,要有抽像思维,不要在方法中,暴露太多细节。(建议一层抽象层,让功能分解得更细,让意图和实现分享)
    个人理解
    (抽象处理的复杂度是人月神话里描述 的本质复杂度(Essential Complexity), 也是尊重人脑的认知学的 ,大脑的特性是很烂的( 抽像的工具包记重点,归纳特性,忽视细节),(推荐阅读google 整理术)
    再瞎想一下,抽像是将很多的知道点给封装起来了(encoding 成为一个模型) , 归纳法便是抽象的重要实现套路,也是支撑及发展文明的利器。 抽像在开发中的意义 ,是让具体的实施类,在合适的场所下编写 (最好满足sru),然后通过合理的ood ,去调用或运行时create 具体的子类对象,去实现。 拜抽象所赐,一种各有分工,又能有序协同的场景就出来了
    其三,个人套路包,我沟通时,当和建议相同方向时,就往抽像了说,当想有差异时,就很细节说。 (套路是双韧剑,就看你如何用了),我个人发现很多营销大的套路,抽像来看和传销是一至的,有些技术大牛的工作套路,和某著名贪官一模一样。(例如,把话说明白了再干,明确职责)
    笔记下(下)
    3继承
    继续用来表示类之间是 is -a 关系。
    猫是动物,于是就拥有的动物的自带属性行为。(移动速度,移动距离) 进一步的,动物属生物(于是) 有了生命的共有属性,年龄
    有些语言单继续,有些多继承。
    意义 和解决问题。
    1 符合认知美感 。
    2 减少重复coding .
    问题: 重要隐患,父子类,大量方便藕合。
    个人理解。
    继续和不停的复杂粘贴代码是两个极度,复制代码虽散但各管各的,继续父一改,子跟着改,而复制代码这事,是问题是,实现一个细节后,其它的相联代码,都要不停的改。而继随相反,一改全刷了,除非你子类完全重写了,而一些不成熟的开发,前期的父类往往考虑不周,后期一折腾,悲剧从此开场。
    再者 复制粘贴代码,两段代码关联太轻,基本上就是要一处一处的改,而复制这事,就关联得太重了。 一但重了,就往往产生大量的不必要的负担。 我学设计模式最开心的就是,基于原则,而不是基于规则,不同场景就用不同的套路。
    基于职责太重这事,我是很反感用多重继续的。 需要啥,再组合一个类多好。 多关联一个类,死不了人的。 搞出菱形继随这种蛋疼的东西,又要不停的去关注细节,活着会很累的
    4 多态。(个人觉得oo 中最有趣的一块)
    多态指子类替代父类。
    三种语法机制。(父可以)
    实现多态,除继续外,还有利用接口类语法, duck-typing
    个人理解:理解不深。有感觉描述不出来,就觉得,继续把事搞死了,多态就要死的东西搞活。 于是支持这种把事搞活的套路出来。
    2019-11-14
    2
    8
  • zcdll
    1. 你熟悉的编程语言是否支持多重继承?如果不支持,请说一下为什么不支持。如果支持,请说一下它是如何避免多重继承的副作用的。
        1. JavaScript 不支持多继承,多继承理论上都存在“菱形问题”,也就是说如果 class D 继承了 class B 和 class C,class B 和 class C 都继承了 class A,class A 中有一个方法 add,B 和 C 都重写了 add 方法,当 D 去调用 add 方法时 就会出问题,不知道调用哪个方法了。
        2. 理论上是可以通过 Mixin 的方式来实现多继承。
        3. 通过一些 合并 算法来“部分”解决“菱形问题”
        4. 参考 JavaScript中的“多继承” [https://juejin.im/entry/5ac46b6c5188255570063b71](https://juejin.im/entry/5ac46b6c5188255570063b71)
    2. 你熟悉的编程语言对于四大特性是否都有现成的语法支持?对于支持的特性,是通过什么语法机制实现的?对于不支持的特性,又是基于什么原因做的取舍?
        1. JavaScript 支持封装,抽象,继承,多态
        2. 封装 ES6的话,通过 public protected private 等关键字来实现,ES5 的话通过 “函数作用域”,this,原型链来实现,ES6 的 class 本质上也是 function 的语法糖。
        3. 抽象 可以通过 this指针 和 原型链 的形式来实现
        4. 继承 通过 原型链 来实现,或者说基于封装的特性来实现
        5. 多态 通过原型链 的方式,子类覆写父类的方法来实现
    2019-11-15
    7
  • 晨风破晓
    PHP不支持多继承,具体为什么还没了解过,四大特性都是有现有语法支持的;看完这堂课,貌似对多态还不是很理解
    2019-11-13
    5
    7
  • 见哥哥
    我们使用Java已经很长时间了,我们有多少次因为缺少多重继承而面临困难呢?
    我个人的经验是一次都没有。因为多重继承很少有机会被用到,所以更安全的做法是去掉它而保持简单性。
    就算是碰到需要多重继承的情景,我们也可以找到替代方法。
    我的观点是,去掉对多重继承的支持不是Java的缺陷,对开发者来说是件好事。
    2019-11-13
    2
    7
  • 秉心说
    多继承会带来菱形继承的问题。例如一个类的两个父类,都继承了同一个祖父类,两个父类都 override 了祖父类的方法,这时候孙子类就不知道如何调用了。

    Java 8 的 interface 可以有方法默认实现,这应该可以算是曲线救国的多继承吧。
    2019-11-13
    7
  • 初心
    多态一句话,现在调用将来
    2019-11-16
    6
  • 每天晒白牙
    专栏中有个思考题是 java 为何不支持类多继承?却支持接口的多继承?
    而有些语言如python是支持多继承的?
    首先java类支持多继承的话,两个父类有同名方法,在子类中调用的时候就不知道调用哪个了,出现决议(钻石问题或菱形问题)问题
    而接口支持多继承因为接口定义的方法不能有方法体,所以不会出现决议问题。
    而从jdk1.8开始,接口可以有默认方法(方法要用default标识),必须要有方法体,这样在接口多继承上不也会有决议问题吗?其实你去试下就知道了,java发现这种情况,会通过让你强制在子接口中重写这个默认方法,这样就不会有决议问题了

    python支持多继承因为它通过MRO解决的,pythoner应该懂,我是不太懂,感兴趣的可以去研究
    2019-11-13
    6
  • 李湘河
    示例代码自己运行了麽,虽说是将设计思想、设计模式,但是代码很多错误呀,像多态示例代码,父类私有属性在子类继承中能直接用吗?

    作者回复: 已经修改成protected的了。代码自己改改,可以运行的。但代码的作用主要还是辅助解释理论,所以有所删减,也是考虑到文章篇幅的问题。

    2019-11-13
    1
    4
  • 秋惊蛰
    试着说一下Python吧
    - 抽象:抽象是编程语言的共有特点,甚至是计算机科学的特点,从变量,函数,类,模块,包等概念都是不同层次的抽象。抽象和把大象装进冰箱分三步是一个道理,它给出了思路,指明了方向,省略了细节。我们用层层抽象来应对计算机系统的复杂性。Python主要的抽象工具是函数和类,模块和包也算吧,毕竟也是隐藏了细节。
    - 封装:Python不支持严格意义上的封装,没有private, protected等访问修饰符,这样做是为了保证动态语言最大的灵活性,同时Python里很多理念都是约定大于定义的,私有的属性需要大家守约,不要去随意访问,这也是Python被吐槽的地方吧,大型项目约束力不够。
    - 继承:Python支持多重继承,主要是因为它没有类似于Java的“接口类”的语法吧,用多重继承可以定义一些纯功能性的类,减少类的层级。
    - 多态:Python的多态就是鸭子类型了,鸭子类型的背后是所谓“协议”,协议是非正式的接口,是一种特性,表现为一个或多个相关的方法,比如迭代器协议,序列协议。实现了迭代器协议就和Java中实现了Iterator接口一样。
    2019-11-13
    4
  • ldd
    话题1:
    Objective-C不支持多继承,OC的方法调用是基于消息机制,是基于方法名调用的,而且是发生在运行时而非编译时,很难解决多个基类可能导致的二义性问题。
    话题2:
    封装:@private、@protected、@public 表作用域的关键字,而且还可以用.h、.m机制实现。
    抽象:protocol 协议来实现。
    继承:简单的 Child : Parent 来实现,内部其实用 isa 指针来实现的。
    多态:继承、protocol 都可以实现

    番外:OC也可以实现多继承,可以用消息转发机制去实现。但本人觉得多继承好像确实不是很实用,之前看 c++ 文档也不建议多用,很想听听争哥对多继承的态度☺️
    2019-11-19
    3
  • 小妖
    我觉得文中对多肽的定义有问题,多肽不仅是只子类替换父类(父类对象引用子类对象),也包括父类(代理类)可以在某些时候代替子类作为参数传递(继承的方式实现参数代理),更直观的表现是实现类替换接口(接口引用接口的实现比如 : LIst<String> list=new ArrayList<>();),这听起来有点像是抽象,实际上抽象的很多情况下是依赖多肽的,比如,方法接口接口作为参数,而不必接收具体的类这体现了抽象,但更体现了多肽…………求交流

    作者回复: 你举的例子不还是子类传递给父类吗

    2019-11-21
    2
  • 与雨日肇事的爱
    王老师,你这系列课程写的代码有和数据结构和算法类似的GitHub仓库么?可以获取源码么?

    作者回复: 你github上搜wangzheng0822,我打算集中放到那里

    2019-11-14
    2
收起评论
99+
返回
顶部