1. 为什么要封装?
解决复杂问题!
解决复杂问题!
解决复杂问题!
重要的事情说3遍!
举个例子
假如,你要同时指挥 10000人跳集体舞
未封装就是 你要指挥10000每个人要干什么,记住他们每个时刻要干什么,这显然是个人类无法解决的复杂问题。
那么现实中我们是怎么做的呢?
就是分为10个人一个小组 10个小组一个大组以此类推。这个问题就不是一个复杂问题了,而且分层便于维护。
可以看到 分层非常清楚便于维护,每层只需要关注自己的实现不需要了解整个项目的具体实现。把一个复杂的问题变成一个个简单的问题 所以封装的本质是 把大问题变成小问题。
计算机科学就是控制复杂度的科学
什么是面向对象?
就像上面说的意义 面向对象是一种封装方式。
数据分为数据结构和算法
数据结构是指数据存储的形式,算法指的数据变化的形式。
对象本身就是 一块内存块 也就是数据,和一组方法。
talk is cheap, show me the code
public class Animal {
private int weight = 0;
public void eat() {
this.weight += 1;
}
}
可以看到在java的类形象的抽象了这个过程
用属性描述数据结构 方法描述算法。
举个例子
老八吃了屎,老八生病了
其中 老八就是一个对象,吃就是一个方法,屎也是一个对象
用OOP的java表示
public class LaoBa {
private boolean sick;
public void eatShit(Shit shit) {
sick = true;
}
public static void main(String[] args) {
LaoBa laoBa = new LaoBa();
laoBa.eatShit(new Shit());
}
}
class Shit {
// 意思一下
}
这是符合现实逻辑的抽象方式,是对现实生活的模拟,这种封装过程大大的减少了心智负担 可以说是封装的一次大跃进。当然也大大的节省了代码量,符合开闭原则用人话来说就是,子类只需要关注不同点,不需要了解相同点,便于扩展。
这也是为什么现代化的语言几乎都是面向对象的语言。
什么是继承?
先说结论,继承就是子集和父集的关系 韦恩图表示如下
举个例子
鸭子 集合 鸭子{嘎嘎叫,游泳,跑}
那么 集合鸟 {嘎嘎叫,跑} 鸟集合属于鸭子集合 用人话来说就是 鸭子是鸟
集合会游泳 {游泳} 会游泳集合属于鸭子集合 用人话来说就是鸭子会游泳
那么接下来有个
一个程序员 集合 {嘎嘎叫,游泳,跑,写代码}
我们有一个方法需要一只鸭子来活跃气氛
显然传入一个程序员也是合理的而且程序运行良好!!!
fun party(鸭子 鸭子){
鸭子.嘎嘎叫()
鸭子.游泳()
鸭子.跑()
}
程序员 coder = new 程序员()
party(coder);
但是你不能让一只鸭子来写代码如下
fun coder(coder zcx){
zcx.写代码();
}
鸭子 zcx = new 鸭子()
coder(zcx); // 这显然会报错!因为鸭子不会写代码
子类可以代替父类 ,父类不一定能代替子类。
这是里式替换原则和多态
很显然可以得出,方法应该尽可能的依赖于父类,而不是具体实现。
这样我们就可以在 要任何鸭子来活跃气氛了!🍾🍾🍾 甚至是一个有鸭子功能的程序员 !!
而不是非要一只具体的鸭子。
这就是 依赖倒置原则
是程序要依赖于抽象接口,不要依赖于具体实现。
鸭子类型和动态语言
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
举个例子
鸭子 集合 鸭子{飞,嘎嘎叫,游泳,跑}
一个程序员 集合 {嘎嘎叫,游泳,跑,写代码}
在这个逻辑下 程序员 会嘎嘎叫,也会游泳,也会跑在某些条件下 程序员可以看作是鸭子
在动态语言中这样的类型是可以编译通过的导致了不可预期的错误,只要不调用飞方法程序员就可以伪装成一只鸭子!
fun party(鸭子 鸭子){
鸭子.嘎嘎叫()
鸭子.游泳()
鸭子.跑()
}
程序员 coder = new 程序员()
party(程序员);
有了这些基础我们再来看一下各个语言的继承实现,更好的理解什么是继承。
2. 各个语言中的继承
Java
public class Animal {
public void eat(){
System.out.println("eat");
}
}
public class Cat extends Animal {
public void call(){
System.out.println("喵喵喵");
}
}
这是一个简单的继承代码
不用继承实现继承 ——- 组合模式
public class Animal {
public void eat(){
System.err.println("eat");
}
}
public class Cat {
private Animal animal;
public Cat() {
this.animal = new Animal();
}
public void call(){
System.out.println("喵喵喵");
}
public void eat(){
animal.eat();
}
}
这是一个组合实现的继承 进阶模式就是装饰器模式
可以看到和完整的继承代码非常类似
public class Animal {
public void eat(){
System.out.println("eat");
}
}
public class Cat extends Animal {
public Cat() {
super();
}
public void call(){
System.out.println("喵喵喵");
}
public void eat(){
super.eat();
}
}
可以看到 实际上java的继承和组合模式实现是非常类似的。
接下来是动态语言中的继承实现
PYTHON
class Person(object): # 定义一个父类
def talk(self): # 父类中的方法
print("person is talking....")
class Chinese(Person): # 定义一个子类, 继承Person类
def walk(self): # 在子类中定义其自身的方法
print('is walking...')
可以看到在python 继承是显式的传入了 Person类
JS
1.NEW关键字
js不是完全的oop模式 他的继承更加符合我们之前说的组合的概念
js中的原型链接模式非常有趣
为了便于理解我不使用new关键字,用闭包和原型链实现一个原汁原味的JS继承
这和 用new关键字实现的继承是一样的
可以看到new 关键字本质上是闭包 +原型链+this指向变换而已。以下的6种方法的本质都是 原型链+设计模式+属性复制 目的是为了模拟oop的继承。
所以复杂的JS new可以理解为 语法糖
- 不用创建临时对象,因为 new 会帮你做
- 不用绑定原型,因为 new 会帮你做(new 为了知道原型在哪,所以指定原型的名字为 prototype);
- 不用 return 临时对象,因为 new 会帮你做;
- 不要给原型想名字了,因为 new 指定名字原型的名字是 prototype。
2.重写?
本质上js的继承就是在原型链上向上寻找属性的过程(在函数式编程中函数是一等公民 函数也是对象!)。举个例子
抽象的来说就是这样!
在整条原型链上都没有的方法就会出现未定义
下面是我总结的java程序员快速理解 JS几个点
①函数是对象
②继承是组合
③就近原则
④闭包
①寄生实现就是工厂类
②借用构造函数就是属性复制
③组合模式就是 属性复制加工厂设计模式而已
……
在ES6之后的伪类继承实现就是用寄生组合模式实现的,TS的也是。
3.js继承实现
- 原型链继承
- 借用构造函数继承
本质上是属性复制
- 组合继承(组合原型链继承和借用构造函数继承)(常用)
本质上是属性复制+原型链
- 原型式继承
本质上是工厂模式
- 寄生式继承
- 寄生组合式继承(常用)
工厂模式+原型链
重点:修复了组合继承的问题
本质上还是这张图
不过他是链式实现的 大概就是这么个逻辑
子类——-(原型)—》父类 ——(原型)—》爷类 —-(原型)—-》Object
Go语言中的继承
实际上go 是没有继承的
go 语言的设计非常简洁优雅 GO的继承一目了然 ,用匿名父字段实现继承
父类作为一个匿名字段被子类持有。
Do better!
①for 前端 为什么要什么是组件化!
②for 后端为什么要微服务!