设计模式

 

 

拿实际工作经历来说,当一个项目开发完成后,如果客户提出增新功能,怎么办

如果项目开发完后,原来程序员离职,你接收维护该项目怎么办?(维护性,可读性,规范性)

目前开发人员门槛越来越高,一线IT公司(大厂),都会问你在实际项目中使用过什么

设计模式,怎样使用的,解决了什么问题

 

设计模式哪里使用

基于面向对象

功能模块 [设计模式+算法(数据结构)]

=> 框架 [使用到多种设计模式]

=>架构 [服务器集群]

 

设计模式的目的

编写软件过程中,开发人员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战,设计模式是为了让程序(软件),具有更好的代码重用性、可读性、可扩展性、可靠性、使程序呈现高内聚,低耦合

 

设计模式七大原则

1.单一职责原则

对类来说,即一个类应该只负责一项职责,如类A负责两个不同职责:职责1,职责2

当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2

 

一个类去做一类事,一个方法去做一件事

 

降低类的复杂度,一个类只负责一项职责

提高类的可读性,可维护性

降低变更引起的风险

通常情况下,我们应当遵循单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则

 

2.接口隔离原则

(Interface Segregation Principle)

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接 口上

 

类A通过接口Interface依赖类B,类C通过接口Interface1依赖类D,如果接口Interface对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法

 

按隔离原则应当这样处理:将接口Interface拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系,也就是采用接口隔离原则

 

在接口中只去定义需要被实现类所需要的方法

以后在设计程序时,一个类实现的接口应该是最小接口,这个接口中的方法应该是这个类都需要用到的

 

3.依赖倒转原则

(Dependence Inversion Principle)

 

 

 

 

 

多态:面向多态、面向抽象、面向接口

 

低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好

变量声明类型尽量是抽象类接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化

 

4.里氏替换原则

 

继承包含这样层含义:

父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏

 

继承在给程序设计带来便利的同时,也带来了弊端,比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障

 

在子类和父类中如果存在一个同名方法,默认会构成重写

所以说默认类B就重写了类A的方法,数据会错乱,性质就变了

继承会使得,类与类之间产生耦合性,同时也会给程序带来侵入性

所以说以后如果我们在一个类中需要使用另外一个类的资源,我们通常情况下会采用组合、聚合的方式来引入这个类的实例,而尽量不要采用继承的方式(使用组合、聚合等方式替换继承

组合、聚合:

 

5.开闭原则(OCP)

 

 

6.迪米特法则*

 

我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友,也就是说陌生的类最好不要局部变量的形式出现在类的内部

 

迪米特法则的核心降低类之间的耦合

但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系

 

一个类对于他所依赖的实例知道的越少越好

耦合只能降低,不可能完全消除

 

7.合成复用原则

原则是尽量使用合成(组合)/聚合的方式,而不是使用继承

 

设计原则核心思想

 

设计模式介绍

设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern) 代表了最佳的实践,这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的

 

设计模式的本质是提高软件的维护性,通用性和扩展性,并降低软件的复杂度

 

设计模式类型

1.创建型模式:

单例模式

抽象工厂模式

原型模式

建造者模式

工厂模式

 

2.结构型模式:

适配器模式

桥接模式

装饰模式

组合模式

外观模式

享元模式

代理模式

 

3.行为型模式:

模板方法模式

命令模式

访问者模式

迭代器模式

观察者模式

中介者模式

备忘录模式

解释器模式(Interpreter模式)

状态模式

策略模式

职责链模式(责任链模式)

 

★单例设计模式介绍

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)

 

就是在软件系统中,对于某个类,在任何位置,只能存在一份对象实例

 

解耦:

类A 在项目中的很多模块都需要用到,这个时候如果正常的去获取该类的实例,需要用到new A(),每使用一个new关键字都会去开辟一份空间,占用系统的资源,指向不同的内存地址

我们能不能想一种解决方案,在模块需要调用A的资源的时候,不需要反复的去创建对象

单例模式:这个软件系统中你获得的A的实例指向的是一份内存地址

1.饿汉式:

 

静态代码块的方式

 

2.懒汉式:

懒汉式会有线程安全的问题,如果t1线程和t2线程同时访问这个类,并且同时判断实例为空,两个线程就会同时去创建实例,就不再是单例

如果直接在方法上加锁确实可以解决线程安全问题,但是效率却不是最好的

在保证线程安全的同时也要考虑效率问题

在实例还没有创建的时候,多线程确实需要同步执行,但是实例已经创建出来的时候,多线程可以并发的去获取实例

volatile关键字修饰引用,实现多线程之间的可见性,在t1线程创建实例时及时的去通知t2线程

最终解决方案:通过双重校验来实现当实例存在的情况下,多线程可以并发的去获取,实例不存在,多线程要同步的去创建

 

不会直接去创建实例,先去判断实例是否为空,如果实例为空再去创建

 

★代理模式(Proxy)介绍

代理模式:

 

 

代理模式是干什么的?

代理模式就是为了完成对目标类的增强,完成对目标类的织入

代理模式增强的是切入点(目标类方法)

 

静态代理:

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同的父类

 

优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展

缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护

 

目标类(被代理类),代理类,逻辑发起点 :

类似于中介(代理类)和房东(目标类)的关系

逻辑发起点:客户要找房

代理类:负责帮客户选房

目标类:签合同


逻辑发起点

代理类与目标类需要实现的接口

代理类

目标类


 

动态代理:

JDK动态代理

代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象

实现步骤:需要调用JDK提供的相关方法

创建的对象:目标对象、代理对象(不需要我们创建,通过反射去创建)、创建代理对象的对象

 

目标类和代理类实现了相同的接口,那么可以使用两者接口的方法字节码对象来调用两者的对应方法

 


代理类创建类

目标类

共同接口

逻辑发起点


 

InvocationHandler接口 (调用处理程序对象)

用于返回目标对象方法调用后的返回值

需要在创建代理对象的时候作为参数使用,通过匿名内部类方式创建实例

需要重写invoke方法,此方法回返值即目标对象方法的返回值

 

区分使用method字节码对象与使用invocationHandler实例调用invoke方法所需要的参数

假设在代理类中聚合了invocationHandler实例

提供目标类实例参数列表

提供代理类实例代理对象方法字节码对象参数列表

 

模拟动态代理在底层的调用

 

 

Cglib动态代理

 

目标类

创建代理类

代理类创建类需要去实现一个MethodInterceptor接口,重写intercept方法,实现此接口后该创建类会继承Callback类

测试类

 

总结JDK动态代理和Cglib动态代理

相同点:都是在内存中去创建对象,功能也是一样的,都是完成对目标方法的增强

不同点

jdk代理:要求目标类必须实现一个接口,内存中创建出来的代理类也会实现相同的接口

Cglib代理:目标类不用实现接口,可以只是一个普通的类,在内存中创建出来的代理类是目标类的子类

 

总结:动态代理实际上都是用于增强目标方法,如果需要被增强的类实现了接口,我们需要采用JDK代理,如果没有实现接口,我们采用Cglib代理

 

动态代理在以后开发中的作用

SpringAop(面向切面编程:底层就是通过代理模式实现的):

模块1:

模块2:

模块3:

假设某项目有很多模块,每个模块封装了不同的业务逻辑,我们以后使用框架进行开发,目的可以提高开发效率,可以让开发人员只去关注核心业务,不去关注一些非核心业务

提一个需求:希望每个模块都能完成日志的收集,当任意一个模块的代码发生了异常,这个时候希望整理成日志文档,方便开发人员排错

注意:日志收集的代码不属于核心业务,日志收集的代码是每个模块都需要的

SpringAop可以完成对核心模块的增强:

日志收集的代码(需要对每个模块增强的部分)

增强的步骤

1、获取每个模块的目标方法

2、配置SpringAop的切面 ==> 切面(指的就是增强的过程)

切面

spring会去校验核心模块中的核心类是否实现了接口,如果实现了,在底层使用jdk代理来完成,如果没有实现,则使用Cglib代理来完成

 

核心类实现了接口需要在 invoke 方法当中完成对目标方法的增强

invoke{

日志收集的代码1

通过反射机制调用目标方法

日志收集的代码2

}

 

所以说,通过这种方式可以让非核心代码脱离核心模块,而且方便维护,易于管理

 

 

★工厂模式

普通工厂模式

是指由一个工厂对象决定创建出哪一种产品类的实例,属于创建型模式

1、适用于工厂类负责创建的对象较少的场景

2、且客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关心

简单工厂模式缺点

1、工厂类的职责相对过重,增加新的产品时需要修改工厂类的逻辑判断违背开闭原则(ocp)

2、不易于扩展过于复杂的产品结构

 

由工厂来为我们生产对象

生产手机的工厂Phone_Factory:华为、苹果、小米

普通工厂模式:根据标识去生产实例

工厂模式为了避免程序过多的使用new关键字来创建对象,这样会产生耦合

 


工厂方法模式

 

 

产品需要实现的接口

 

测试类

 

 


静态工厂

将工厂类中的方法加上static关键字,这样就不需要创建工厂对象


 

抽象工厂模式

是指提供一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类

 

抽象工厂模式使用场景

1、客户端(应用层)不依赖于产品类实例如何被创建,实现等细节

2、强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码

3、提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体的实现

 

抽象工厂模式优点

1、具体产品在应用层代码隔离,无需关系创建细节

2、将一个系列的产品族统一到一起创建

 

抽象工厂模式要求工厂和产品都需要基于接口开发

 

工厂类需要实现的工厂接口

 

产品需要实现的接口

 

创建手机工厂类实现工厂接口

 

测试类