如何通俗理解设计模式及其思想_,小白看完都学会了

2021年09月15日 阅读数:1
这篇文章主要向大家介绍如何通俗理解设计模式及其思想_,小白看完都学会了,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

我最终鼓起勇气写这篇文章的目的是:我想经过分享我的对于设计模式的理解,以及本身的学习方式和所得,这种学习方式可能并不是适用于全部人,可是它至少能给予须要的人一个参考程序员

设计模式的分类

咱们先看设计模式的分类:面试

范围 建立型 结构型 行为型
Factory Method(工厂方法) Adapter(类) (适配器) Interpreter(解释器)
Template Method(模版方法)
对象 Abstract Factory(抽象工厂)

Builder(建造者)
Prototype(原型)
Singleton(单例) | Bridge(桥接)
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<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.小结

经过上述案例,咱们简单理解了几种结构型设计模式的概念和思想,总结一下:

最后

针对Android程序员,我这边给你们整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;但愿能帮助到你们,也节省你们在网上搜索资料的时间来学习,也能够分享动态给身边好友一块儿学习!

领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一块儿讨论交流解决问题。

如何通俗理解设计模式及其思想_,小白看完都学会了

如何通俗理解设计模式及其思想_,小白看完都学会了