如何通俗理解设计模式及其思想_

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 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 observer = new Observer() {
@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.依赖倒置原则

现在我们来了解一下依赖倒置原则

抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而非针对实现编程。

它的原则是:

  • 1.高层模块不应该依赖于低层模块,两个都应该依赖于抽象。
  • 2.抽象不应该依赖细节,细节应该依赖于抽象。

在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

了解了依赖倒置原则,我们再接再厉,学习最后一个设计模式的基本原则:

5.接口隔离原则

接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

这个应该是最好理解的原则了,它的意义就是:使用多个专门的接口比使用单一的总接口要好

这很好理解,对于鸟的实现(Bird),我们可以定义两个功能接口,分别是Fly和Eat,我们可以让Bird分别实现这两个接口——如果我们还有一个Dog,那么对于Eat接口,可以复用,但是如果只有一个接口(包含Fly和Eat两个功能),对于Dog来说,它是不会飞(Fly)的,那么就需要针对Dog再声明一个新的接口,这是没有必要的设计。

6.小结

在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高。

现在我们再看上文中对行为型模式比较严谨的定义,相信大家能够理解一些了:

行为模式涉及到算法和对象间职责的分配,行为模式不仅描述对象或类的模式,还描述它们之间的通信模式。这些模式刻划了在运行时难以跟踪的复杂的控制流,将你的注意力从控制流转移到对象间的联系方式上来。

喘口气

关于设计模式相关的讲解内容,到此基本就告一段落了。

等等…

我一个设计模式都没学会,你TM告诉我你讲完了?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一无所获?

本文并没有去通过代码简单描述各个设计模式的实现方式,于我而言毫无意义,设计思想是通过不断思考,理解并在亲身尝试中学会的,短时间快速大量阅读学习的方式效果甚微,即使学会了,如何将sample中的设计思想,转换为实际产品中复杂的业务设计,这也是一个非常大的难题。

在这里,我引用《倚天屠龙记》中我最喜欢的经典片段, 就是张三丰在武当山当着敌人的面教张无忌太极剑那段。

只听张三丰问道:‘孩儿,你看清楚了没有?’张无忌道:‘看清楚了。’张三丰道: ‘都记得了没有?’张无忌道:‘已忘记了一小半。’张三丰道:‘好,那也难为了你。你自己去想想罢。’张无忌低头默想。过了一会,张三丰问道:‘现下怎样了?’张无忌道: ‘已忘记了一大半。’

周颠失声叫道:‘糟糕!越来越忘记得多了。张真人,你这路剑法很是深奥,看一遍怎能记得?请你再使一遍给我们教主瞧瞧罢。’

张三丰微笑道:‘好,我再使一遍。’提剑出招,演将起来。众人只看了数招,心下大奇,原来第二次所使,和第一次使的竟然没一招相同。周颠叫道:‘糟糕,糟糕!这可更加叫人胡涂啦。’张三丰画剑成圈,问道:‘孩儿,怎样啦?’张无忌道:‘还有三招没忘记。’张三丰点点头,收剑归座。

张无忌在殿上缓缓踱了一个圈子,沉思半晌,又缓缓踱了半个圈子,抬起头来,满脸喜色,叫道:‘这我可全忘了,忘得干干净净的了。’张三丰道:‘不坏不坏!忘得真快,你这就请八臂神剑指教罢!’

总结

关于设计模式,我的理解是,不要拘泥于其概念,只有深刻理解了其设计的思想,理解之后,亲自去尝试使用它,在使用的过程中加深对这种思想的理解,我想比通过书籍或者博客一个一个的去学,效果要更好。

我学习它们的方式是,学习一些优秀开源库的源码,思考为什么这里使用这些设计模式,之后再参考《设计模式》一书的相关概念,最后自己去尝试并加深理解。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后说一下我的学习路线

其实很简单就下面这张图,含概了Android所有需要学的知识点,一共8大板块:

  1. 架构师筑基必备技能
  2. Android框架体系架构(高级UI+FrameWork源码)
  3. 360°Androidapp全方位性能调优
  4. 设计思想解读开源框架
  5. NDK模块开发
  6. 移动架构师专题项目实战环节
  7. 移动架构师不可不学习微信小程序
  8. 混合开发的flutter

Android学习的资料

我呢,把上面八大板块的分支都系统的做了一份学习系统的资料和视频,大概就下面这些,我就不全部写出来了,不然太长了影响大家的阅读。

330页PDF Android学习核心笔记(内含上面8大板块)

Android学习的系统对应视频

总结

我希望通过我自己的学习方法来帮助大家去提升技术:

  • 1、多看书、看源码和做项目,平时多种总结

  • 2、不能停留在一些基本api的使用上,应该往更深层次的方向去研究,比如activity、view的内部运行机制,比如Android内存优化,比如aidl,比如JNI等,并不仅仅停留在会用,而要通过阅读源码,理解其实现原理

  • 3、同时对架构是有一定要求的,架构是抽象的,但是设计模式是具体的,所以一定要加强下设计模式的学习

  • 4、android的方向也很多,高级UI,移动架构师,数据结构与算法和音视频FFMpeg解码,如果你对其中一项比较感兴趣,就大胆的进阶吧!

希望大家多多点赞,转发,评论加关注,你们的支持就是我继续下去的动力!加油!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 1、多看书、看源码和做项目,平时多种总结

  • 2、不能停留在一些基本api的使用上,应该往更深层次的方向去研究,比如activity、view的内部运行机制,比如Android内存优化,比如aidl,比如JNI等,并不仅仅停留在会用,而要通过阅读源码,理解其实现原理

  • 3、同时对架构是有一定要求的,架构是抽象的,但是设计模式是具体的,所以一定要加强下设计模式的学习

  • 4、android的方向也很多,高级UI,移动架构师,数据结构与算法和音视频FFMpeg解码,如果你对其中一项比较感兴趣,就大胆的进阶吧!

希望大家多多点赞,转发,评论加关注,你们的支持就是我继续下去的动力!加油!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 16
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值