1. 为什么要封装?

解决复杂问题!
解决复杂问题!
解决复杂问题!
重要的事情说3遍!

举个例子
假如,你要同时指挥 10000人跳集体舞
未封装就是 你要指挥10000每个人要干什么,记住他们每个时刻要干什么,这显然是个人类无法解决的复杂问题。
那么现实中我们是怎么做的呢?
就是分为10个人一个小组 10个小组一个大组以此类推。这个问题就不是一个复杂问题了,而且分层便于维护。
8FXmF33Q4x.png
可以看到 分层非常清楚便于维护,每层只需要关注自己的实现不需要了解整个项目的具体实现。把一个复杂的问题变成一个个简单的问题 所以封装的本质是 把大问题变成小问题。
z3SkM713K7.png

计算机科学就是控制复杂度的科学



什么是面向对象?

就像上面说的意义 面向对象是一种封装方式。
数据分为数据结构和算法
数据结构是指数据存储的形式,算法指的数据变化的形式。
对象本身就是 一块内存块 也就是数据,和一组方法。

talk is cheap, show me the code

  1. public class Animal {
  2. private int weight = 0;
  3. public void eat() {
  4. this.weight += 1;
  5. }
  6. }

可以看到在java的类形象的抽象了这个过程
用属性描述数据结构 方法描述算法。

举个例子
老八吃了屎,老八生病了
其中 老八就是一个对象,吃就是一个方法,屎也是一个对象
用OOP的java表示

  1. public class LaoBa {
  2. private boolean sick;
  3. public void eatShit(Shit shit) {
  4. sick = true;
  5. }
  6. public static void main(String[] args) {
  7. LaoBa laoBa = new LaoBa();
  8. laoBa.eatShit(new Shit());
  9. }
  10. }
  11. class Shit {
  12. // 意思一下
  13. }

这是符合现实逻辑的抽象方式,是对现实生活的模拟,这种封装过程大大的减少了心智负担 可以说是封装的一次大跃进。当然也大大的节省了代码量,符合开闭原则用人话来说就是,子类只需要关注不同点,不需要了解相同点,便于扩展。
这也是为什么现代化的语言几乎都是面向对象的语言。


什么是继承?

先说结论,继承就是子集和父集的关系 韦恩图表示如下

JwYtgHgg9Y.png

举个例子
鸭子 集合 鸭子{嘎嘎叫,游泳,跑}
那么 集合鸟 {嘎嘎叫,跑} 鸟集合属于鸭子集合 用人话来说就是 鸭子是鸟
集合会游泳 {游泳} 会游泳集合属于鸭子集合 用人话来说就是鸭子会游泳

那么接下来有个
一个程序员 集合 {嘎嘎叫,游泳,跑,写代码}
我们有一个方法需要一只鸭子来活跃气氛
显然传入一个程序员也是合理的而且程序运行良好!!!

  1. fun party(鸭子 鸭子){
  2. 鸭子.嘎嘎叫()
  3. 鸭子.游泳()
  4. 鸭子.跑()
  5. }
  6. 程序员 coder = new 程序员()
  7. party(coder);

但是你不能让一只鸭子来写代码如下

  1. fun coder(coder zcx){
  2. zcx.写代码();
  3. }
  4. 鸭子 zcx = new 鸭子()
  5. coder(zcx); // 这显然会报错!因为鸭子不会写代码


子类可以代替父类 ,父类不一定能代替子类。
这是里式替换原则和多态

很显然可以得出,方法应该尽可能的依赖于父类,而不是具体实现。
这样我们就可以在 要任何鸭子来活跃气氛了!🍾🍾🍾 甚至是一个有鸭子功能的程序员 !!
而不是非要一只具体的鸭子。

这就是 依赖倒置原则

是程序要依赖于抽象接口,不要依赖于具体实现。


鸭子类型和动态语言

重新面向对象(闲谈各种语言中的继承) - 图4

“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

举个例子
鸭子 集合 鸭子{飞,嘎嘎叫,游泳,跑}
一个程序员 集合 {嘎嘎叫,游泳,跑,写代码}

在这个逻辑下 程序员 会嘎嘎叫,也会游泳,也会跑在某些条件下 程序员可以看作是鸭子

在动态语言中这样的类型是可以编译通过的导致了不可预期的错误,只要不调用飞方法程序员就可以伪装成一只鸭子!

  1. fun party(鸭子 鸭子){
  2. 鸭子.嘎嘎叫()
  3. 鸭子.游泳()
  4. 鸭子.跑()
  5. }
  6. 程序员 coder = new 程序员()
  7. party(程序员);

有了这些基础我们再来看一下各个语言的继承实现,更好的理解什么是继承。


2. 各个语言中的继承

Java

  1. public class Animal {
  2. public void eat(){
  3. System.out.println("eat");
  4. }
  5. }
  6. public class Cat extends Animal {
  7. public void call(){
  8. System.out.println("喵喵喵");
  9. }
  10. }

这是一个简单的继承代码

不用继承实现继承 ——- 组合模式

  1. public class Animal {
  2. public void eat(){
  3. System.err.println("eat");
  4. }
  5. }
  6. public class Cat {
  7. private Animal animal;
  8. public Cat() {
  9. this.animal = new Animal();
  10. }
  11. public void call(){
  12. System.out.println("喵喵喵");
  13. }
  14. public void eat(){
  15. animal.eat();
  16. }
  17. }

这是一个组合实现的继承 进阶模式就是装饰器模式

可以看到和完整的继承代码非常类似

  1. public class Animal {
  2. public void eat(){
  3. System.out.println("eat");
  4. }
  5. }
  6. public class Cat extends Animal {
  7. public Cat() {
  8. super();
  9. }
  10. public void call(){
  11. System.out.println("喵喵喵");
  12. }
  13. public void eat(){
  14. super.eat();
  15. }
  16. }

可以看到 实际上java的继承和组合模式实现是非常类似的。
接下来是动态语言中的继承实现


PYTHON

  1. class Person(object): # 定义一个父类
  2. def talk(self): # 父类中的方法
  3. print("person is talking....")
  4. class Chinese(Person): # 定义一个子类, 继承Person类
  5. def walk(self): # 在子类中定义其自身的方法
  6. print('is walking...')

可以看到在python 继承是显式的传入了 Person类


JS

1.NEW关键字

js不是完全的oop模式 他的继承更加符合我们之前说的组合的概念
js中的原型链接模式非常有趣
为了便于理解我不使用new关键字,用闭包和原型链实现一个原汁原味的JS继承
Code_Z79HBTWOsw.png
这和 用new关键字实现的继承是一样的
可以看到new 关键字本质上是闭包 +原型链+this指向变换而已。以下的6种方法的本质都是 原型链+设计模式+属性复制 目的是为了模拟oop的继承。
所以复杂的JS new可以理解为 语法糖

  • 不用创建临时对象,因为 new 会帮你做
  • 不用绑定原型,因为 new 会帮你做(new 为了知道原型在哪,所以指定原型的名字为 prototype);
  • 不用 return 临时对象,因为 new 会帮你做;
  • 不要给原型想名字了,因为 new 指定名字原型的名字是 prototype。

2.重写?

本质上js的继承就是在原型链上向上寻找属性的过程(在函数式编程中函数是一等公民 函数也是对象!)。举个例子

抽象的来说就是这样!

LF4oZ5tPAm.png
在整条原型链上都没有的方法就会出现未定义
下面是我总结的java程序员快速理解 JS几个点
①函数是对象
②继承是组合
③就近原则
④闭包

①寄生实现就是工厂类
②借用构造函数就是属性复制
③组合模式就是 属性复制加工厂设计模式而已
……

在ES6之后的伪类继承实现就是用寄生组合模式实现的,TS的也是。

3.js继承实现

  重新面向对象(闲谈各种语言中的继承) - 图7  

  • 原型链继承

     重新面向对象(闲谈各种语言中的继承) - 图8
 
  

  •  借用构造函数继承

本质上是属性复制
     重新面向对象(闲谈各种语言中的继承) - 图9
    

  •   组合继承(组合原型链继承和借用构造函数继承)(常用)

本质上是属性复制+原型链
     重新面向对象(闲谈各种语言中的继承) - 图10
    

  •   原型式继承

本质上是工厂模式
     重新面向对象(闲谈各种语言中的继承) - 图11
    
  

  •   寄生式继承

     重新面向对象(闲谈各种语言中的继承) - 图12
    
    

  •  寄生组合式继承(常用)

   工厂模式+原型链
     重新面向对象(闲谈各种语言中的继承) - 图13
    重点:修复了组合继承的问题

重新面向对象(闲谈各种语言中的继承) - 图14
本质上还是这张图
JwYtgHgg9Y.png
不过他是链式实现的 大概就是这么个逻辑

子类——-(原型)—》父类 ——(原型)—》爷类 —-(原型)—-》Object

Go语言中的继承

实际上go 是没有继承的
go 语言的设计非常简洁优雅 GO的继承一目了然 ,用匿名父字段实现继承
父类作为一个匿名字段被子类持有。
idea64_VI1VOkhs2A.png

Do better!

①for 前端 为什么要什么是组件化!
②for 后端为什么要微服务!