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

Composite(组合)
Decorator(装饰者)
Façade(外观)
Flyweight(享元)
Proxy(代理) | Chain of Responsibility(职责链)
Command(命令)
Iterator(迭代器)
Mediator(中介者)
Memento(备忘录)
Observer(观察者)
State(状体)
Strategy(策略)
Visitor(访问者) |

这是我从 这篇文章 中找到的对设计模式的归纳。

同时,我们需要了解到,设计模式的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 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”)

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

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

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

img

img

img

img

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

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

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

文末

不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊

小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。

其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊**

小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。

[外链图片转存中…(img-fotLDNHO-1713547221931)]

其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值