设计模式

一、设计模式介绍

1)设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern)代表了最佳的实践。
2)设计模式的本质:提高软件的维护性,通用性和扩展性,并降低软件的复杂度。
3)设计模式哪里使用
基于面向对象
功能模块【设计模式+算法(数据结构)】
框架【使用多种设计模式】
架构【服务器集群】
4)设计模式的目的:
让程序具有更好的代码重用性、可读性、可扩展性、可靠性、使程序呈现高内聚、低耦合。

二、设计模式七大原则

原文链接:https://blog.csdn.net/fyj13925475957/article/details/103178579

1)单一职责原则

定义:即一个类只负责一项职责。
针对的问题:有一个类A负责2个不同的职责:职责1和职责2。当因为职责1的需求发生改变而需要修改类A的时候,有可能会导致原本运行正常的职责2功能发生故障。
解决方案:遵守单一职责原则,分别建立2个类A和类B,是类A完成职责1,类B完成职责2。这样当类A发生改变时不会使职责2发生故障。同理,当修改类B的时候,也不会使职责1发生故障,2个类和职责互不影响。

2)接口隔离原则

定义:

  1. 1. **客户端不应该依赖它不需要的接口**
  2. 1. **类之间的依赖关系应该建立在最小的接口上**

对定义的解析:
1、接口隔离原则是对接口的使用进行约束规范的一个原则,它告诉我们要想把接口用好,关键在于隔离。隔离,指断绝接触、断绝往来。
2、那么我们使用接口时,要隔离什么东西呢?对于上述定义的第1点,“客户端不应该依赖它不需要的接口”,这里的隔离是指客户端和它不需要的接口隔离,也就是客户端不要使用它不需要的接口。
3、我们着重看一下第2点,“类间的依赖关系应该建立在最小的接口上”,它要求“最小的接口”,也就是该接口中没有多余的方法,所以这里的隔离是指和多余的方法隔离
4、综上所述,接口隔离原则告诉我们,不要把一大堆方法塞进一个接口里,导致这个接口变得臃肿无比。应该要根据实际需要,让接口中只有用得上的方法,也就是说要细化我们的接口。

3)依赖倒转原则

定义:1、高层模块不应该依赖低层模块,两者都应该依赖其抽象
2、抽象不应该依赖细节,细节应该依赖抽象
3、依赖倒转原则的中心思想就是面向接口编程
4、依赖倒转原则基于的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的框架比细节为基础的架构要稳定得多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类。
5、使用接口或者抽象类的目的是制定好规范,而不涉及任何的具体操作,把细节的任务交给实现类去完成。
总结:

  1. 1. 低层模块尽量都要有抽象类或接口,或者两者都有,程序的稳定性更好。
  2. 1. 变量的声明类型尽量是接口或抽象类,这样我们的变量引用和实际对象间,就存在一个缓存层,利于程序的拓展和优化

4)里氏替换原则

面向对象的继承性的思考和说明:
1、继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。
2、继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承带来了侵入性,程序的可移植性降低,增加对象的耦合性,如果一个类被其他类所继承,这当这个类需要修改时,必须要考虑到所有子类,并且父类修改后,所有涉及到的子类的功能都可能产生故障。
综上得出结论:

  1. 1. 在继承时,遵守里氏替换原则,在子类中尽量不要去重写父类的方法。
  2. 1. 里氏替换原则告诉我们,继承实际上让两个类的耦合性增强,如果一个类要使用另一个类的资源,在适当的情况下,尽量不使用继承,可以通过聚合,组合,依赖来解决问题。

组合与聚合:

  1. class A{
  2. //聚合,采用set、get、构造进行赋值
  3. private B b;
  4. //组合
  5. private B b = new B();
  6. }

总结:
1.里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
2.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
3.子类中可以增加自己特有的方法
4.当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松.
5.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格.
6.综上所述,我们尽量用依赖、聚合,组合等关系代替继承的关系

5)开闭原则(ocp)

开闭原则介绍:
1.开闭原则是编程中最基础,最重要的设计原则
2.一个软件实体如类,模块和函数应该对拓展开放(对提供方),对修改关闭(对使用方),用抽象构建框 架,用实现拓展细节.
3.当软件需求要变化时,尽量通过拓展软件的实体的行为来实现变化,而不是通过修改已有的代码来实现变化
4.编程中遵守其他原则,以及使用设计模式的目的就是遵守开闭原则.

6)迪米特法则

基本介绍:
1.一个对象应该对其他对象保持最少的了解。
2.类与类的关系越密切,耦合度越大。
3.迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好,也就是说,对于被依赖的类不管多复杂,都尽量将逻辑封装在类的内部,对外提供public方法,不对外泄露信息。
4.迪米特法则还有个更简单的定义:只与直接朋友通信。
总结:

  1. 1. 迪米特法则的核心是降低类之间的耦合性。
  2. 1. 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间的耦合关系,并不是要求完全没有依赖关系。

7)合成复用原则

定义:软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
问题由来:通常类的复用分为继承复用和合成复用两种,继承复用虽然有简单和易实现的优点,但它也存在以下缺点:
1.继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
2.子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
3.它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

三、设计模式类型

1)创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂方法模式
2)结构型模式:适配器模式、桥接模式、装饰器模式、组合模式、外观模式、享元模式、代理模式
3)行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。

四、详细介绍

(1)单例模式

https://blog.csdn.net/ShuSheng0007/article/details/117266347
定义:在系统中,对某个类只有一个实例,且自行实例化并向整个系统提供此实例。
用途:
例如类A在项目中的很多模块都需要用到,这个时候如果正常的去获取该类的实例new A(),每使用一个new关键字都会去开辟一份空间,占用系统的资源,指同不同的内存地址。
我们能不能想一种解决方案:在模块需要调用A的资源的时候,不需要反复的去创建对象。
单例模式:这个软件系统中你获得的A的实例指向的是同一份内存地址。

a.饿汉式

  1. /**
  2. *单例模式之饿汉式(写法一)
  3. */
  4. public class SingletonTest01 {
  5. /**
  6. * 1、构造方法私有化,不希望在外界可以实例化对象
  7. */
  8. private SingletonTest01(){
  9. }
  10. /**
  11. * 2、在本类实例化本类的实例 static有个很重要的特征,只加载一次
  12. */
  13. private static final SingletonTest01 singletonTest01 = new SingletonTest01();
  14. /**
  15. * 3、提供一个静态方法用于获取该实例
  16. * @return
  17. */
  18. public static SingletonTest01 getInstance(){
  19. return singletonTest01;
  20. }
  21. }
  1. package com.jy.design.singleton;
  2. /**
  3. *单例模式之饿汉式(另一种写法)
  4. */
  5. public class SingletonTest02 {
  6. private static SingletonTest02 singletonTest02;
  7. static {
  8. singletonTest02 = new SingletonTest02();
  9. }
  10. /**
  11. * 将构造私有化
  12. */
  13. private SingletonTest02(){
  14. }
  15. /**
  16. * 提供一个方法用于获取该实例
  17. * @return
  18. */
  19. public static SingletonTest02 getInstance(){
  20. return singletonTest02;
  21. }
  22. }

b.懒汉式

  1. package com.jy.design.singleton;
  2. /**
  3. *单例模式之懒汉式
  4. * 不会直接创建实例,先去判断实例是否为空,如果为空再去创建
  5. *
  6. * 懒汉式会有线程安全的问题:
  7. * 1、直接在方法上加锁,可以解决线程安全问题,但效率不高
  8. * 2、在线程安全的情况下,我们需要考虑效率问题
  9. * 3、在实例还没创建的时候,多线程确实需要同步,但是实例已经创建的话,多线程可以并发的获取实例
  10. * 4、最终解决方案:通过双重校验来实现当实例存在的情况下,多线程可以并发的去获取,不存在的时候,同步的创建
  11. */
  12. public class SingletonTest03 {
  13. /**
  14. * 1.将构造私有化
  15. */
  16. private SingletonTest03(){
  17. }
  18. /**
  19. * 2.聚合本类的引用
  20. * 如不使用volatile关键字,发生t2阻塞在判断语句,t1已经创建实例却无法让t2获知的情况,就会线程不安全
  21. */
  22. private volatile static SingletonTest03 singletonTest03;
  23. /**
  24. * 3. 提供一个方法用于获取该实例
  25. * @return
  26. */
  27. public static SingletonTest03 getInstance(){
  28. if (singletonTest03 == null) {
  29. synchronized (SingletonTest03.class) {
  30. //二次校验是否为空,如有t1、t2两个线程,t1判断为空后创建对象,则此时t2就不用再进行判断
  31. if (singletonTest03 == null) {
  32. singletonTest03 = new SingletonTest03();
  33. }
  34. }
  35. }
  36. return singletonTest03;
  37. }
  38. }