引子
有一本讲诺贝尔奖获得者, 物理学家费曼的书, 叫做《发现的乐趣》, 书中写到一个费曼小时候的故事:
"我们家有《大不列颠百科全书》, 我还是小孩子的时候, 父亲就常常让我坐在他腿上, 给我读些《大不列颠百科全书》. 比如说, 我们读关于恐龙的部分, 书上可能讲雷龙或其他什么龙, 书上会说:" 这家伙有 25 英尺高, 脑袋宽 6 英尺."这时父亲就停下来, 说:" 我们来看看这句话什么意思. 这句话的意思是: 假如它站在我们家的前院里, 它是那么高, 高到足以把头从窗户伸进来. 不过呢, 它也可能遇到点麻烦, 因为它的脑袋比窗户稍微宽了些, 要是它伸进头来, 会挤破窗户.
费曼说: 凡是我们读到的东西, 我们都尽量把它转化成某种现实, 从这里我学到一个本领 -- 凡我所读的内容, 我总设法通过某种转换, 弄明白它究竟什么意思, 它到底在说什么.
费曼技巧
费曼技巧, 或者说费曼学习法是一种以教促学的方法, 一共有四步(已经知道的可以无视, 直接跳过):
(1) 选择新概念 / 新知识, 自己先去学习它.
(2) 假装当一个老师, 去教授别人
想象你面对一群小白, 怎么把这个概念讲给他们听, 让他们理解呢?
把你讲解的思路也写到纸上, 如果实在不想写, 可以说出来.
非常重要!!! 不要让你的思路停留在大脑中, 因为大脑中对于知识点之间的关联会有些想当然的, 错误的假设, 说出来或者写出来能找到这些 "盲点"!!
(3) 如果你在教授的过程中遇到了麻烦, 卡了壳, 返回去学习.
重新去看书, 搜相关资料, 问别人, 倒逼自己把这个概念搞清楚, 然后回到第二步, 继续给小白讲授.
(4) 简化你的语言.
目标是用你自己的语言, 非专业的词汇去解释这个概念. 尽量做到简单直白, 或者找到比喻来表达.
非常简单的过程, 对吧?
实战演练
我们来用个例子来演练一下, 有请码农翻身头号主人公张大胖出场.
张大胖正在学习 Java, 这一天他遇到了一个新的概念:"动态代理" (注意是学习这个概念, 不是具体实现), 非常抽象, 在日常编程中几乎不会直接使用, 理解起来有难度.
第一步, 自学
张大胖看了动态代理的介绍, 书上列举出一堆烦人的代码来展示这个东西是怎么使用的, 比如有个接口 (IHelloWorld) 及其实现类 (HelloWorld), 然后有个 InvocationHandler 的实现, 最后用 Proxy.newProxyInstance(....) 创建一个新的类出来, 这些都是什么鬼? 啰里啰唆的.
第二步, 张大胖尝试教一下小白(当然这里的小白至少得懂点儿 Java)
张大胖: 动态代理嘛, 很简单, 就是给定一个接口和实现类, 再加上一个 InvocationHandler , 动态代理这个技术可以在运行时创建一个新的代理类出来.
小白: 张老师, 新的代理类有什么用?
张大胖: 举个例子, 有个叫 IHelloWorld 接口及其实现类 HelloWorld, 它有一个叫 sayHello()的方法. 可以在 sayHello()之前和之后, 额外加一些日志的输出.
(在讲解一个概念的时候, 举例和类比很重要, 人类习惯于通过例子来学习, 从具体走向抽象)
小白: 那我直接写一个新的类, 比如 HelloWorldEx, 把日志输出添加到其中不就行了, 为什么还要用 Proxy.newProxyInstance(......)这么麻烦的方法?
- public class HelloWorldEx implements IHelloWorld{
- IHelloWorld hw;
- public HelloWorldEx(IHelloWorld hw){
- this.hw = hw;
- }
- public void sayHello(){
- Logger.startLog();
- hw.sayHello();
- Logger.endLog();
- }
- }
张大胖无法回答这个问题, 卡壳了!
第三步, 回过头去看书, 学习.
书中也没有解释, 唉!
仔细想一想, 手动写一个类 HelloWorldEx 和用 Proxy.newProxyInstance 来创建, 区别到底是什么?
实现的功能是相同的, 但是 HelloWorldEx 需要事先写好, 编译后不能改了, 相当于写死了! 如果我想对 Order 类, Employee 类, Department 类, 也想加点儿日志, 还得写个 OrderEx,EmployeeEx,DepartmentEx 的类, 太麻烦了!
而 Proxy.newProxyInstance 这种方法, 可以在程序运行的时候为任意类动态地创建增强的类.
事先写死的叫做静态代理, Proxy.newProxyInstance 这种方式叫做动态代理, 更加灵活.
张大胖觉得这么解释就通了.
小白: 为什么要创建新的代理类, 那个 Proxy.newProxyInstance 不能直接修改老的 HelloWorld 类吗?
张大胖再度卡壳, 上网搜索, 找到了答案, 和 Python,Ruby 等方法不同, Java 本质是一个静态类型的语言, class 一旦被装入 JVM, 是不能修改, 添加, 删除方法的, 既然老的 class 不能修改, 只能通过代理的方式来创建新的类了.
小白: 懂了, 这个技术主要用在什么地方啊? 难道只是加个日志?
张大胖第三次卡壳, 只好再次搜索.
原来动态代理使用得最多的是 AOP,AOP 中经常会以声明的方式提出这样的要求:
某个包下所有 add 开头的方法, 在执行之前都要调用 Logger.startLog()方法, 在执行之后都要调用 Logger.endLog()方法.
或者对于所有以 Service 结尾的类, 所有的方法执行之前都要调用 tx.begin(), 执行之后都要调用 tx.commit(), 如果抛出异常的话调用 tx.rollback().
到此为止, 张大胖可以这样来给小白讲述了:
你不是用过 Spring AOP 吗? AOP 中经常有这样的需求...... ,Spring 想添加这些日志和事务的功能, 但是却没有办法去修改用户的类, 它是框架啊, 一是不知道用户类的源码, 二是 Java 不允许再修改装载入 JVM 的 class.
没办法, Spring 只好在运行时找到用户的类, 然后操作字节码动态创建一个新类, 新类会对原有的类进行增强, 添加日志, 事务这些功能, 注意啊, 这些都是在内存中动态创建的.
这个技术就是 Java 的动态代理, 不过它有个前提要求, 就是用户的类需要实现接口才行. 我用一个简单的例子给你说下, 你就明白细节了......
第四步, 简化, 比喻
上面的讲解从文字上来说还是非常啰嗦的, 用了很大篇幅来讲解 "为什么", 因为理解了 why , 剩下的就是细节了.
如果你彻底理解了以后, 动态代理的技术细节会在大脑中会建立这么一幅图景:
$HelloWorld100 就是那个代理类, 它和 HelloWorld 都实现了 IHelloWorld 这个接口.
如果一定要用个比喻来说, 它们俩就是 "兄弟关系",CgLib 提供了另外一种对现有类增强的办法, 动态生成的类继承了现有的类, 两者是 "父子关系".
小结
怎么样? 用这种 (假装) 教授别人, 层层递进, 自我逼问的方法是不是很有效果? 收益很大?
用这种办法, 实际上就是逼着你把大脑中的盲点和一些想当然的假设给暴露出来, 效果要比单纯地阅读和记忆好得多, 赶紧在学习中试一下吧!
来源: http://zhuanlan.51cto.com/art/202005/617290.htm