术与道
数据结构,算法,设计模式被认为是程序员必备技能的三叉戟,如果说编程语言的语法特性和业务编码能力是【术】,那么这三者可以称得上是【道】——【术】可以让你在IT行业赖以生存,而【道】则决定你未来在技术这条道路上可以走多远。
边学边忘的窘境
先自我介绍一下。
我是一个两年工作经验的Android开发人员,就像很多同行一样,对于数据结构,算法,设计模式这些被奉为程序员必修的三门内功,几乎没有去系统性地学习过(曾经专业课的数据结构,如今也基本还给了老师)。
你问我想不想当一个称职的程序员,当然!数据结构,算法以及设计模式不止一次的被我列入学习计划中,我认为这是有意义并且必须的,并且,我也尝试在闲暇之余去主动学习它们。
但是很遗憾,至今为止,有关于数据结构和算法,我依然处于入门阶段,原因就是:
我学会没多久,一直没机会用到,然后就忘了!
如果您翻看过我之前的 相关博客 或者我 Github 的 这个仓库,就能发现,我曾经关于数据结构和算法,都做出过尝试,但是遗憾的是,我并不能学以致用 ——它们匆匆而来,匆匆而去,就好像生命中的过客一样。
至于设计模式,因为空闲时间学习的时间并不多,虽然我也经常通过博客或者书籍学习设计模式,但是结果依然没有太大的改变:过于抽象的概念,加上不经常使用,让我很快把这些概念忘得差不多了。
我觉得这种学习方式效率不是很高,于是我决定换一种学习的方式——通过阅读Github上那些开源库的源码,学习其中的设计思想和理念。
动机
和大多数人一样,我只是在编程行业众多平凡的开发者中的一员,在我职业生涯的伊始,我没有接触过技术大牛, 但是阅读源码可以让我零距离碰撞全球行业内最顶尖工程师们的思想,我认为对我而言这是一种效果不错的学习方式,让我受益匪浅。
事实上,在要写这篇博客的时候,我依然忐忑不安,我担心一篇文章如果写的不够好,会给读者带来误导。
我最终鼓起勇气写这篇文章的目的是:我想通过分享个人对于设计模式的理解,以及自己的学习方式和所得,这种学习方式可能并非适用于所有人,但是它至少能给予需要的人一个参考。
设计模式的分类
我们先看设计模式的分类:
同时,我们需要了解到,设计模式的6个基本原则(这里先列出来,接下来会参考案例一个个解释):
单一职责原则(Single Responsibility Principle)
里氏代换原则(Liskov Substitution Principle)
依赖倒转原则(Dependence Inversion Principle)
接口隔离原则(Interface Segregation Principle)
迪米特法则,又称最少知道原则(Demeter Principle)
开闭原则(Open Close Principle)
在设计模式的学习过程中,这些设计模式并非是按照不同类型循序渐进讲解的,更多的场景是,多个不同类型的设计模式相互组合——最终展示出来的是一个完整的架构设计体系。这种设计模式复杂组合带来的好处是:高内聚,低耦合,这使得库本身的拓展非常简单,同时也非常便于单元测试。
当然,对于通过源码,想一窥设计思想的学习者来说,额外的接口,以及可能随之额来额外的代码会需要更多的学习成本,对于最初的我来说,复杂的设计真的给我带来了很大的困扰,我试图去理解和反思这样设计的好处——它的确花费了我更多的时间,但是更让我受益匪浅。
最初的收获——创建型模式
在Android学习的过程中,我最先接触到的就是创建型模式,所谓创建型模式,自然与对象的创建有关。
实际上,不谈源码,实际开发中,我们也遇到了很多创建型模式的体现,最常见的当属单例模式和建造者模式(Builder)。
1.“最简单”的设计模式
我们以单例模式为例,他的定义是:
“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
相信大家对这个单例模式并不陌生,它被称为 “设计模式中最简单的形式之一”,它很简单,并且易于理解,开发者总能遇到需要持有唯一对象的业务需求。
以Android开发为例,经常需要在某个类中,使用到Application对象,它本身是唯一的,因此我们只需要通过一个类持有它的静态引用,然后通过静态方法获取就可以了。
另外的一种需求是,某个类的对象会占用很大的内存,我们也没有必要对这个类实例化两次,这样,保持其对象的类单例,能够省下更多的性能空间,比如Android的数据库db的引用。
实际上,单例模式的细分下来,有很多种实现方式,比如众所周知的懒汉式,饿汉式,Double CheckLock,静态内部类,枚举,这些不同的单例实现方式,都有各自的优缺点(比如是否线程安全),也对应着不同的适用场景,这也正是单例模式作为看起来“最简单”同时也是面试中的重点考察项目的原因。
这些不同的实现方式,百度上讲解的非常详细,本文不赘述。
我们需要理解的是,我们什么时候使用单例模式。
对于系统中的某些类来说,只有一个实例很重要,比如上述的Application,这很好理解,实际上,在开发过程中,我们更需要关注一些细节的实现。
比如对Gson的单例。
实际开发中,调用Gson对象进行转换的地方非常多,如果在调用的地方每次new Gson的话,是影响性能的。
Gson本身是线程安全的,它可以被多个线程同时使用,因此,我更倾向于通过下面的方式获取Gson的实例:
public class Gsons {
private static class Holder {
private static final Gson INSTANCE = new Gson();
}
public static Gson getInstance() {
return Holder.INSTANCE;
}
}
不仅是Gson, 除此之外还有比如网络请求的相关管理类(Retrofit对象,ServiceManager等),Android系统提供的各种XXXManager(NotificationManager)等等,这些通过单例的方式去管理它,能够让你业务设计的更加严谨。
2.Builder的链式调用
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。
Android开发者一定很熟悉它,因为我们创建AlertDialog的时候,链式调用的API实在是赏心悦目:
new AlertDialog
.Builder(this)
.setTitle("标题")
.setMessage("内容")
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//...
}
})
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//....
}
})
.create()
.show();
此外,稍微细心的同学会发现,其实JDK中,StringBuilder和StringBuffer的源码中的append()方法也是Builder模式的体现:
public StringBuilder append(String str) {
super.append(str); // 调用基类的append方法
return this;
}
// 基类的append方法
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this; // 返回构建对象
}
除了赏心悦目的代码之外,我更关注Builder模式的使用场景:
当我们面临着一个复杂对象的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
很好,我把 这个学习网站 关于Builder模式的适用场景复制下了下来,我曾经在学习它的时候尝试去理解它的叙述,所得出的结论是 ——上文的定义非常严谨,但是我看不懂。
我们参考AlertDialog,对于一个Dialog而言,它的基本构成是复杂的(有标题,内容,按钮及其对应的事件等等属性),但是在实际需求中,不同的界面,我们需要展示给用户的Dialog是不一样的(标题不一样,内容不一样,点击事件也不一样),这些各个部分都是在不断剧烈的变化,但是他们组合起来是相对稳定的(就是一个Dialog弹出展示在界面上)。
在这种情况下,我们可以尝试使用Builder模式,和普通的构造器生成对象不同,如果没有需求,我们可以忽略配置某些属性——对于Dialog,我可以不去定义title,也可以不去定义取消按钮的点击事件,他们内部都有默认的处理;此外,对于API的设计来讲,Builder模式更利于去扩展新的功能或者属性。
Builder模式在我们开发中非常常见,除上述案例之外,Android流行的图片加载库,以及Notification通知的实例化等等,都能看到Builder的身影。
上文说到,Builder模式对于对象的创建提供了非常赏心悦目的API,我理解了Builder模式的思想和实现方式之后,便尝试给自己的一些工具类加一些这样的设计。
很快,我遇到了一个问题,那就是——这样写太TM累了!
3.避免过度设计
关于过度设计的定义,请参考 什么是软件开发中的过度设计? 的解释,我认为讲解的非常风趣且易懂。
从我个人的角度而言,我遇到了问题,我尝试给一些工具改为Builder实现,结果是,我添加了很多很多代码,但是效果平平。
不仅如此,这样的设计给我的工具带来了更多的复杂度,本来一个构造器new一下能解决的问题,非要很多行代码链式配置,这种设计,做了还不如不做。
这样的结果,让我对网络上一位前辈的总结非常赞同,那就是:
设计模式的一个重要的作用是代码复用,最终的目的是提升效率。
所以,一个模式是否适合或必要,只要看看它是否能减少我们的工作,提升我们的工作效率。
那么,如何避免过度设计,我的经验告诉我,写代码之前多思考,考虑不同实现方式所需的成本,保证代码的不断迭代和调整。
即使如此,在开发的过程中,过度设计仍然是难以避免的情况,只有依靠经验的积累和不断的总结思考,慢慢调整和丰富自己的个人经验了。
4. 单一职责原则与依赖注入
对于单例模式,我似乎也会遇到过度设计这种情况——每个对象的单例都需要再写一个类去封装,似乎也太麻烦了。
实际上这并非过度设计,因为这种设计是必要的,它能够节省性能的开销,但是对象的创建和管理依然是对开发者一个不可小觑的工作量。
此外,还需要考量的是,对于一个复杂的单例对象,它可能有很多的状态和依赖,这意味着,单例类的职责很有可能很重,这在一定程度上违背了单一职责原则:
一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
单一职责原则告诉我们:一个类不能太“累”! 一个类的职责越重(这往往从构造器所需要的依赖就能体现出来),它被复用的可能性就越小。
在了解了单例模式的优点和缺点后,我们可以有选择的使用单例模式,对于依赖过于复杂的对象的单例,我们更需要仔细考量。
对于复杂的依赖管理,依赖注入库(比如Dagger)是一个可以考虑的解决方案(慎重),对于单例模式的实现,你只需要在Module中对应的依赖Provider上添加一个@Singleton注解,编译器会在编译期间为您自动生成对应的单例模式代码。
不能否认,这个工具需要相对较高的学习成本,但是学会了依赖注入工具并理解了IOC(控制反转),DI(依赖注入)的思想之后,它将成为你开发过程中无往不胜的利器。
5.开闭原则
开闭原则:一个软件应对扩展开放、对修改关闭,用head first中的话说就是:代码应该如晚霞中 的莲花一样关闭(免于改变),如晨曦中的莲花一样开放(能够扩展).
建造者模式(Builder)便是开闭原则的完全体现,它将对象的构建和调用隔离开来,不同的使用者都可以通过自由的构建对象,然后使用它。
6.小结
创建型模式是最容易入门的,因为该类型的模式,更经常暴露在开发者面前,但是它们并不简单,我们除了知道这些模式的使用方式,更应该去思考什么时候用,用哪个,甚至是组合使用它们——它们有些互斥,有些也可以互补,这需要我们去研究更经典的一些代码,并自己作出尝试。
不只是创建型,接下来的结构型和行为型的设计模式,本文也不会去一一阐述其目录下所有的设计模式。
结构型模式
1.定义
首先阐述书中结构型模式的定义:
结构型模式涉及到如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来组合接口或实现。
在学习之初,对我个人而言,阅读《设计模式:可复用面向对象软件的基础》 的内容宛如诵读天书,书中对每种设计模式都进行了详细的讲解,但是我看完之后,很快就忘掉了,亦或是对看起来非常相似的两种设计模式感到疑惑——书中的讲解细致入微,但是太抽象了。
最终(也就是现在),我个人对于结构型模式的理解是,通过将不同类或对象的组合,采用继承或者组合接口,或者组合一些对象,以实现新的功能。
用一句话陈述,就是对不同职责的对象(以对象/抽象类/接口的形式)之间组合调度的实现方式。
2.并非所有对象的组合都是结构型模式
实际上,并非所有对对象的组合都属于结构型模式,构型模式的意义在于,对一些对象的组合,以实现新功能的方式—— 通过运行时,通过改变组合的关系,这种灵活性产生不同的效果,这种机制,普通的对象组合是不可能实现的。
接下来我将通过阐述数种不同的结构型模式在实际开发中的应用,逐步加深对上文叙述的理解。
3.RecyclerView:适配器模式
RecyclerView是Android日常开发中实现列表的首选方案,站在我的角度来看,我还没想明白一个问题,RecyclerView是如何实现列表的?
我可以回答说,通过实现RecyclerView.Adapter就能实现列表呀!
事实上,是这样的,但是这引发了另外一个问题,Adapter和RecyclerView之间的关系是什么,为啥实现了Adapter就能实现RecyclerView呢?
思考现实中的一个问题,我有一台笔记本电脑,我的屋子里也有一个电源,我如何给我的笔记本充电?
不假思索,我们用笔记本的充电器连接电源和笔记本就行了,实际上,充电器更官方的叫法应该叫做电源适配器(Adapter)。对于笔记本电脑和电源来讲,它们并没有直接的关系,但是通过Adapter适配器,它们就能产生新的功能——电源给笔记本充电。
RecyclerView和数据的展示也是一样,数据对象和RecyclerView并没有直接的关系,但是我如果想要将数据展示在RecyclerView上,通过给RecyclerView配置一个适配器(Adapter)以连接数据源,就可以了。
现在我们来看Adapter模式的定义:
使原本由于接口不兼容而不能一起工作的那些类可以一起工作。
现在我们理解了适配器模式的应用场景,但是我想抛出一个问题:
为啥我要实现一个Adapter,设计之初,为什么不能直接设置RecyclerView呢?
比如说,我既然有了数据源,为什么设计之初,不能让RecyclerView通过这样直接配置呢:
mRecyclerView.setDataAndShow(datas);
我的理解是,如果把RecyclerView比喻为屋子里的电源插口,电源不知道它将要连接什么设备(同样,RecyclerView也不可能知道它要展示什么样的数据,怎么展示),而不同的设备的接口也可能不一样,但是只要为设备配置一个对应的适配器,两个不相关的接口就能一起工作。
RecyclerView的设计者将实现对开发者隐藏,并通过Adapter对开发者暴露其接口,开发者通过配置数据源(设备)和对应的适配器(充电器),就能实现列表的展示(充电)。
4.Retrofit:外观模式与动态代理
说到迪米特法则(也叫最少知识原则),这个应该很好理解,就是降低各模块之间的耦合:
迪米特法则:一个软件实体应当尽可能少地与其他实体发生作用。
我的学习过程中,让我感受到设计模式的组合之美的第一个库就是Retrofit,对于网络请求,你只需要配置一个接口:
public interface BlogService {
@GET("blog/{id}")
Call<ResponseBody> getBlog(@Path("id") int id);
}
// 使用方式
// 1.初始化配置Retrofit对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:4567/")
.addConverterFactory(GsonConverterFactory.create())
.build();
// 2.实例化BlogService接口
BlogService service = retrofit.create(BlogService.class);
Retrofit的源码中,通过组合,将各种设计模式应用在一起,构成了整个框架,保证了我们常说的高内聚,低耦合,堪称设计模式学习案例的典范,如下图(图片参考感谢这篇文章):
在分析整个框架的时候,我们首先从API的使用方式入手,我们可以看到,在配置Retrofit的时候,库采用了外观模式作为Retrofit的门面。
有朋友说了,在我看来,Retrofit的初始化,不应该是Builder模式吗,为什么你说它是外观模式呢?
我们首先看一下《设计模式:可复用面向对象软件的基础》一书对于外观模式的定义:
为子系统中的一组接口提供一个一致的界面,外观模式定义一个高层接口,这个接口使得这一子系统更容易使用。
我的解读是,对于网络请求库的Retrofit,它内部有着很多不同的组件,包括数据的序列化,线程的调度,不同的适配器等,这一系列复杂的子系统,对于网络请求来讲,都是不可或缺的且关系复杂的,那么,通过将它们都交给Retrofit对象去配置和调度(当然,Retrofit对象的创建是通过Builder模式实现的),对于API的调用者来说,使用配置起来简单方便,这符合外观模式 的定义。
简单理解了外观模式的思想,接下来我们来看一下动态代理,对于最初接触Retrofit的我来说,我最难以理解的是我只配置了一个接口,Retrofit是如何帮我把Service对象创建出来的呢?
// 2.实例化BlogService接口
BlogService service = retrofit.create(BlogService.class);
实际上,并没有BlogService这个对象的创建,service只不过是在jvm运行时动态生成的一个proxy对象,这个proxy对象的意义是:
为其他对象提供一种代理以控制对这个对象的访问。
我想通过BlogService进行网络请求,Retrofit就会通过动态代理实现一个proxy对象代理BlogService的行为,当我调用它的某个方法请求网络时,实际上是这个proxy对象通过解析你的注解和方法的参数,通过一系列的逻辑包装成一个网络请求的OkHttpCall对象,并请求网络。
现在我明白了,怪不得我无论怎么给Service的接口和方法命名,Retrofit都会动态生成代理对象并在调用其方法时进行解析,对于复杂多变的网络请求来讲,这种实现的方式非常合适。
5.里氏替换原则
在优秀的源码中,我们经常可以看到,很多功能的实现,都是依赖其接口进行的,这里我们首先要理解面向对象中最重要的基本原则之一里氏替换原则:
任何基类可以出现的地方,子类一定可以出现。
里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
向上转型是Java的基础,我们经常也用到,实际上,在进行设计的时候,尽量从抽象类继承,而不是从具体类继承。同时,保证在软件系统中,把父类都替换成它的子类,程序的行为没有变化,就足够了。
6.小结
通过上述案例,我们简单理解了几种结构型设计模式的概念和思想,总结一下:
在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。所以也有多种结构型模式可供开发人员选择使用。
提高类之间的协作效率——行为型模式
1.定义
我们先看书中对行为型模式比较严谨的定义:
行为模式涉及到算法和对象间职责的分配,行为模式不仅描述对象或类的模式,还描述它们之间的通信模式。这些模式刻划了在运行时难以跟踪的复杂的控制流,将你的注意力从控制流转移到对象间的联系方式上来。
依然是有点难以理解,我们先举两个例子:
2.OkHttp:Intercepter和职责链模式
在 Okhttp 中, Intercepter就是典型的职责链模式的体现.它可以设置任意数量的Intercepter来对网络请求及其响应做任何中间处理——设置缓存, Https的证书验证, 统一对请求加密/防串改, 打印自定义Log, 过滤请求等。
new OkHttpClient.Builder()
.addNetworkInterceptor(interceptor1)
.addNetworkInterceptor(interceptor2)
.addNetworkInterceptor(interceptor3)
职责链模式的定义为:
让多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将他们连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
以现实为例,职责链模式之一就是网络连接,七层或五层的网络连接模型如下:
网络请求发出,经过应用层->传输层->网络层->连接层->物理层
收到响应后,物理层->连接层->网络层->传输层->应用层
在请求经过各层时,由每层轮流处理.每层都可以对请求或响应进行处理.并可以中断链接,以自身为终点返回响应。
3.RxJava:观察者模式
Android开发中,点击事件的监听是很经典观察者模式的体现:
button.setOnClickListener(v -> {
// do something
})
对设置OnClickListener来说,View是被观察者,OnClickListener是观察者,两者通过setOnClickListener()方法达成注册(订阅)关系。订阅之后,当用户点击按钮,View就会将点击事件发送给已经注册的 OnClickListener。
同样,对于可以和Retrofit配套的RxJava来讲,它是也通过观察者模式来实现的。
// 被观察者
Observable observable = Observable
.just("Hello", "Hi", "Aloha")
// 观察者
Observer<String> observer = new Observer<String>() {
@Override
public void onNext(String s) {
Log.d(tag, "Item: " + s);
}
@Override
public void onCompleted() {
Log.d(tag, "Completed!");
}
@Override
public void onError(Throwable e) {
Log.d(tag, "Error!");
}
};
// 执行订阅关系
observable.subscribe(observer);
RxJava强大的异步处理,将数据的创建和接收分成了两部分,对于观察者来说,它不关心数据什么时候发射的,怎么发射的,它只关心,当观察到到最新数据时,怎样进行对应的处理。
我们知道了观察者模式的这种方式,我们更需要去深入思考对于观察者模式使用前后,对我们代码设计系统的影响——它的好处是什么?
最直接的好处是,被观察者并不知道观察者的详细实现。
就像我刚才所说的,被观察者只负责发射事件,对于事件如何处理,它并不关心,这意味着被观察者和观察者之间并不是紧密耦合的,它们可以处于一个系统中的不同抽象层次。
不同抽象层次这句话本身就有点抽象,我们以Button的点击事件为例,对于Button来讲,它是一个库的工具,它应该属于项目中底层组件,而对于我们某个Activity的某个点击事件来讲,它是属于靠顶部业务层的代码,可以说,Button和点击事件是不在一个抽象层次,较低层次的Button可以将点击事件发送给较高层次的事件监听器并通知它。
而如果不采用这种方式,观察者和被观察者就必须混在一起,这样对象就会横贯项目的2个层次(违反了层次性),或者必须放在这两层中的某一层中(可能会损害层次抽象)。
将底层组件按钮被点击后行为,抽象出来交给较高层级去实现,了解了这种方式的好处,依赖倒置原则就不难理解了。
4.依赖倒置原则
现在我们来了解一下依赖倒置原则:
抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而非针对实现编程。
它的原则是:
在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
了解了依赖倒置原则,我们再接再厉,学习最后一个设计模式的基本原则:
5.接口隔离原则
接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
这个应该是最好理解的原则了,它的意义就是:使用多个专门的接口比使用单一的总接口要好。
这很好理解,对于鸟的实现(Bird),我们可以定义两个功能接口,分别是Fly和Eat,我们可以让Bird分别实现这两个接口——如果我们还有一个Dog,那么对于Eat接口,可以复用,但是如果只有一个接口(包含Fly和Eat两个功能),对于Dog来说,它是不会飞(Fly)的,那么就需要针对Dog再声明一个新的接口,这是没有必要的设计。
6.小结
在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高。
现在我们再看上文中对行为型模式比较严谨的定义,相信大家能够理解一些了:
行为模式涉及到算法和对象间职责的分配,行为模式不仅描述对象或类的模式,还描述它们之间的通信模式。这些模式刻划了在运行时难以跟踪的复杂的控制流,将你的注意力从控制流转移到对象间的联系方式上来。
喘口气
关于设计模式相关的讲解内容,到此基本就告一段落了。
等等…
我一个设计模式都没学会,你TM告诉我你讲完了?
一无所获?
本文并没有去通过代码简单描述各个设计模式的实现方式,于我而言毫无意义,设计思想是通过不断思考,理解并在亲身尝试中学会的,短时间快速大量阅读学习的方式效果甚微,即使学会了,如何将sample中的设计思想,转换为实际产品中复杂的业务设计,这也是一个非常大的难题。
在这里,我引用《倚天屠龙记》中我最喜欢的经典片段, 就是张三丰在武当山当着敌人的面教张无忌太极剑那段。
只听张三丰问道:‘孩儿,你看清楚了没有?’张无忌道:‘看清楚了。’张三丰道: ‘都记得了没有?’张无忌道:‘已忘记了一小半。’张三丰道:‘好,那也难为了你。你自己去想想罢。’张无忌低头默想。过了一会,张三丰问道:‘现下怎样了?’张无忌道: ‘已忘记了一大半。’
周颠失声叫道:‘糟糕!越来越忘记得多了。张真人,你这路剑法很是深奥,看一遍怎能记得?请你再使一遍给我们教主瞧瞧罢。’
张三丰微笑道:‘好,我再使一遍。’提剑出招,演将起来。众人只看了数招,心下大奇,原来第二次所使,和第一次使的竟然没一招相同。周颠叫道:‘糟糕,糟糕!这可更加叫人胡涂啦。’张三丰画剑成圈,问道:‘孩儿,怎样啦?’张无忌道:‘还有三招没忘记。’张三丰点点头,收剑归座。
张无忌在殿上缓缓踱了一个圈子,沉思半晌,又缓缓踱了半个圈子,抬起头来,满脸喜色,叫道:‘这我可全忘了,忘得干干净净的了。’张三丰道:‘不坏不坏!忘得真快,你这就请八臂神剑指教罢!’
总结
关于设计模式,我的理解是,不要拘泥于其概念,只有深刻理解了其设计的思想,理解之后,亲自去尝试使用它,在使用的过程中加深对这种思想的理解,我想比通过书籍或者博客一个一个的去学,效果要更好。
我学习它们的方式是,学习一些优秀开源库的源码,思考为什么这里使用这些设计模式,之后再参考《设计模式》一书的相关概念,最后自己去尝试并加深理解。
于我而言,这种方式的短板在于刚开始的时候,可能需要更多的时间去学习,很多库的源码在初接触时,并非容易理解,但是好处是,当学会之后,这种思想就很难再从你的记忆中跑掉了,而且,在写代码时,会下意识尝试使用这些模式(当然,更多情况是打开2个窗口,一边参考源码,一边学习将其融入到自己的代码中)。
设计模式相对于分类和概念正如太极拳(剑),它更是一种思想,一种应对变化的解决思想——我不认为本文自己的学习方式和心得,能够适合每一个正在阅读本文的读者,但是如果本文对您对技术成长的道路上有一些帮助,对我而言这就是值得的。
参考
1.菜鸟教程——设计模式:
http://www.runoob.com/design-pattern/design-pattern-tutorial.html
2.《设计模式:可复用面向对象软件的基础》
伽玛等著;李英军等译,机械工业出版社,2015年12月第一版37次印刷
3.Retrofit分析-漂亮的解耦套路:
https://blog.piasy.com/2016/06/25/Understand-Retrofit/
4.创建型模式、结构型模式和行为型模式之间的关系
https://blog.csdn.net/qq_34583891/article/details/70853637
今日话题
请在留言区说说你对通俗理解设计模式及其思想的理解,小乐期待你的留言,让我们共同养成好习惯!
猜你还想看
阿里、腾讯、百度、华为、京东最新面试题汇集
用信鸽来解释 HTTPS
红黑树插入算法实现原理分析
那些让你起飞的计算机基础知识