Java进阶(二):Java设计模式
目录
设计模式
一.建模语言
二.类之间的关系
1.依赖关系
2.关联关系
3.聚合关系
4.组合关系
5.继承关系
6.实现关系
三.面向对象设计原则
单一职责原则
开闭原则
里氏替换原则
依赖倒置
接口隔离原则
迪米特原则
组合/聚合(关联关系)复用原则
四.23种设计模式
五.单例模式(只创建一个对象)
六.工厂模式
简单工厂模式(一个工厂对应多类产品)
工厂方法模式(一个工厂对应一类产品)
抽象工厂(一个工厂对应多类商家工厂)
七.原型模式
八.代理模式
九.模板方法模式
十.策略模式
设计模式
设计模式产生背景
设计模式概念首先起源于建筑领域,1990在软件领域也诞生设计模式概念。
直到 1995 年,艾瑞克·伽马(ErichGamma)、理査德·海尔姆(Richard Helm)、拉尔夫·约翰森(Ralph Johnson)、约翰·威利斯迪斯(JohnVlissides),在《设计模式:可复用面向对象软件的基础》中收纳总结了23种设计模式。
什么是设计模式?
在长期编程的过程中,针对某一类问题经过反复的优化,最终总结出一个固定的解决方案,这些方案经过反复的使用,具有普遍性。
为什么要学习设计模式?
①学习设计模式就是学习好的编程思想,学习前辈们的经验。
②可以提高程序员的思维能力、编程能力和设计能力。
③使程序设计更加标准化、使软件开发效率大大提高。
④使设计的代码可重用性高、可扩展性提高。
⑤能够更好的去理解源码架构。
一.建模语言
统一建模语言(Unified Modeling Language,UML),是一套软件设计和分析的语言工具
用图形化的方式,记录表示类与类,类与接口,接口与接口之间的关系, 一般把图形化方式也称为UML类图.
类图中两个基本的要素:
1.类:是对具有相同属性和行为的一组对象的抽象描述。
2.接口:是一种特殊的类,它具有类的结构但不可被实例化,只可以被子类实现。
二.类之间的关系
1.依赖关系
在一个类中的方法,把另一个类作为参数进行使用,具有临时性,方法执行结束后,依赖关系就不存在了。
一般把xxx类用到了xxx类,这种关系,称为依赖关系,也称为 use-a关系。例如:下图的人与手机的关系,人类中的方法call()中的参数MobilePhone mp就是作为依赖关系。方法结束,依赖关系结束。用虚线箭头表示。
2.关联关系
是一种has-a的关系 ,xxx 有 xxx
在一个类中,把另一个当做自己的成员。用实心三角实线箭头表示。
有单向关联,双向关联,自关联,一对一关联,一对多关联。
关联关系根据强弱又分为:聚合关系和组合关系。
3.聚合关系
聚合关系也是一种关联关系,是强关联关系,是一种整体和部分的关系
学校包含老师, 即使学校不存在了, 老师可以依然独立的存在。
在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。
4.组合关系
聚合关系也是一种关联关系, 是一种整体和部分的关系, 是一种更强烈的关联关系。
头和嘴关系,头如果不在了,嘴也会跟着销毁。
在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。
、
5.继承关系
继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的
关系,是一种继承关系,是 is-a 的关系。
在 UML 类图中,继承关系用带空心三角箭头的实线来表示,箭头从子类指向父类。
6.实现关系
实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接
口中所声明的所有的抽象操作。
在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。
三.面向对象设计原则
单一职责原则
一个类只负责某一个具体功能,细化类的粒度。比如:Person类只负责关于人的信息,Address类只负责关于地址的信息。
开闭原则
对修改关闭,对扩展开放(不修改,多使用多态的思想)
尽可能的在扩展功能时,不要修改已有的代码,尽可能扩展一个新的类来实现新功能。
里氏替换原则
继承优势: 提高代码复用性,子类继承父类的功能,提高代码的扩展性,子类还可以扩展自己的功能,不影响其他类,重写父类方法。
继承劣势: 继承使得类的体系结构变得复杂了。
里氏替换:首先时由里斯科夫女士提出的。
其次是关于继承使用的,当子类继承了父类后, 在使用时,用子类替换父类后,要确保父类中的功能不受影响。(子类重写父类方法时,不得改变改变方法的结果)
主要的思想: 就保证程序的稳定性。
依赖倒置
面向抽象编程,不要面向具体的实现编程。
具体实现应该依赖抽象层 (使用多态,抽象层用来表示,定义--> 具体的由子实现类实现)
接口隔离原则
不要把所有的功能都定义到一个总的接口中,应该把不同的种类的功能,定义在不同的接口中,让实现类,根据自己的需要去灵活的选择。(不同功能,不同接口)
迪米特原则
只跟朋友联系,不跟"陌生人”说话。
在程序间相互调用时,如果两个类直接没有直接联系,但是想相互调用,可以通过第三方进行转发调用。(两个类没有直接联系,通过第三方将两者联系,如:明星与粉丝,通过经纪人交流)
降低模块之间的耦合度。
组合/聚合(关联关系)复用原则
继承使得类的体系变得复杂, 如果我们只是想使用某个类中的方法时,也可以优先选择关联关系/ 依赖关系 降低类与类之间的耦合度。
四.23种设计模式
五.单例模式(只创建一个对象)
在一个项目中,如何确保一个类始终只有一个对象。
特点:
1. 单例类只有一个实例对象;(虽然多次创建,但只能产生一个)2. 该单例对象必须由单例类自行创建;(在单例类内部,在第一次使用时才创建)3. 单例类对外提供一个访问该单例的全局访问点;(需要提供一个公共的方法)
单例模式通常两种实现
①饿汉式单例:单例模式中的饿汉式(急切式单例) 在加载此类时,就已经将唯一的一个对象创建出来。
好处:不会存在线程安全问题。
不足: 在类加载时,就会创建单例对象,有可能一段时间内还用不到它。
public class MyWindow {
//在内部自己创建的一个单例对象
private static MyWindow myWindow = new MyWindow();
private MyWindow() {
}
/*
对外提供这唯一的对象
*/
public static MyWindow getMyWindow() {
return myWindow;
}
}
②懒汉式单例:在类加载时,并没有创建单例对象,在第一次获取单例对象时,才去创建了单例对象。
好处: 类加载时先不创建,在第一次使用获取时才会创建。
不足: 会出现线程安全问题,加锁解决(synchronized锁)。
※ 双重检索 + volatile(可见性,避免重排序)
例如:A a = new A();
创建对象这一条语句编译为指令时,可以分为三个指令顺序:
1. new 申请空间
2. 调用构造方法初始化对象
3. 把对象地址赋给引用变量
如果按照这个正常的顺序执行,是没有问题的,但是执行时,如果2,3条指令顺序发生变化,导致把没有初始化完成的对象地址返回了,拿去使用了,这么做会出问题,因为对象没有初始化完成。所有需要使用volatile关键修饰单例成员变量,确保对其赋值时,指令不重新排序。
public class MyWindow {
private volatile static MyWindow myWindow;
public static MyWindow getMyWindow() {
//第一重检索,隔绝后面大量的线程进入
if (myWindow == null) {
//加锁,只能一个一个进入
synchronized (MyWindow.class) {
//第二重检索,隔绝前面少量的线程进入
if (myWindow == null) {
myWindow = new MyWindow();
}
}
}
return myWindow;
}
}
六.工厂模式
解决的就是在项目将创建对象和使用对象分离的问题,(结合Spring),如何更好的组织类与类之间的关系。
简单工厂模式(一个工厂对应多类产品)
简单工厂并不是一种设计模式,违背了开闭原则,主要是引出工厂方法和抽象工厂模式
涉及的角色:
工厂角色: 根据我们的需求,创建对应的对象。
抽象产品: 具体产品的抽象,具体产品实现 / 继承抽象产品,可以使用上层的抽象父类,表示任意的子类对象。
具体产品: 具体的对象。
优点: 创建对象和使用对象分离了。
缺点: 只能创建实现了同一个父类 / 接口的子类对象, 扩展新的类型,需要修改工厂,违背了开闭原则。
适合简单的,子类较少的场景。
工厂方法模式(一个工厂对应一类产品)
由于简单工厂中,一个工厂,可以造同一类型的所有具体产品,导致简单工厂比较复杂,扩展一个新类型时,需要修改工厂代码。
工厂方法模式,为工厂也进行抽象,并且为同类型每个具体产品都创建了一个具体的工厂。
每一个工厂负责创建一个具体的产品(类型)对象,这样扩展一个新的产品,与之对应就要扩展一个产品工厂,就不需要修改工厂,遵守了开闭原则,单一职责原则。
好处: 遵守了开闭原则。
不足: 类的数量增多了。
抽象工厂(一个工厂对应多类商家工厂)
如:一个工厂可分为小米工厂,华为工厂。小米工厂可造汽车,手机。华为工厂可造汽车,手机。
工厂方法模式,是按照产品类型进行分类的,一类产品,对应一类工厂,不同类型产品之间,相互隔离的。例如: 华为和小米,既要汽车,又要造手机,都是属于同一家的产品,但是工厂方法这种设计,同一个公司产品与产品之间没有联系。
抽象工厂模式对工厂重新进行分类,以公司为单位进行工厂的抽象(提取),一个工厂内,可以创建不同的产品,这样我们就可以创建出像华为工厂,小米工厂这样的具体工厂,一个工厂内,可以创建不同公司的各种产品。
七.原型模式
在某些场景下,为(解决的问题:)避免自己手动的new对象,我们可以使用对象克隆方式,创建并返回一个新的对象。这种克隆新对象的效率比我们自己new的效率要高。
对象克隆实现方式有两种:
1.实现Cloneable接口,重写clone()
2.使用对象序列化 反序列化重新生成对象
注意深克隆和浅克隆问题。
八.代理模式
早在spring aop思想中,已经用到了代理思想。在不修改原来代码的前提下,为我们方法添加额外的功能。通过代理对象帮助我们进行调用。
有些时候,目标对象(汽车厂)不想或不能直接与客户打交道,通过代理对象进行访问,代理对象可以保护目标对象,对目标对象功能进行扩展,降低了模块之间的耦合度。
涉及到三个主题:
抽象主题: (抽取的功能,让目标对象进行实现,以及代理对象进行实现)
具体主题: 真正要实现功能的类。
代理对象
代理模式实现方式又有两种:
1.静态代理
创建一个代理类,代理实现与具体对象实现相同的接口 / 抽象类,重写抽象方法。还有一个成员变量,可以用接收具体的主题,在代理类中重写的抽象方法中调用真实主题方法,这样就可以在调用之前和之后添加额外的功能。
不足: 一个代理对象,只能代理一个接口类型的对象,不灵活。
2.动态代理
只需要写一次代理类,就可以通过反射机制动态获得类的信息,可以为任何类提供代理功能。
动态代理实现分为两种:
①jdk代理
jdk代理实现是通过反射机制实现的, 目标类必须要实现一个接口,通过接口动态获得目标类中的信息。
②cglib代理
是spring中提供的一种代理技术,目标类可以不实现任何接口,采用字节码生成子类的方式,对方法进行拦截,实现机制不同。
注意:cglib不能代理final修饰的类以及fnal和static修饰的方法。
目前spring中两种动态代理都支持,如果目标类没有实现接口,默认使用cglib代理,如果目标类有实现接口,采用jdk代理。
九.模板方法模式
模版方法模式,是在一个类中,定义好一个算法骨架,设定好实现步骤,把一些公共的通用的在父类中实现,然后将一些不确定的实现在具体的子类中实现。
例如,去银行办理业务一般要经过以下 4 个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
结构:
抽象类: 负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
模版方法: 定义好执行顺序的算法骨架,确定好执行流程顺序。
抽象方法: 不确定的功能,定义为抽象的,交给子类实现。
具体方法: 都一样的公共的通用的方法,在抽象父类中实现。
具体子类: 实现抽象类中的抽象方法的具体类,有不同的实现方式,就可以用多个子类。
new 具体子类对象,用具体子类对象调用模版方法,把父类中具体方法与自己实现的抽象方法一起执行。
将变化部分定义为抽象的,让子类去扩展实现,满足开闭原则,适合流程相对比较固定的,其中有变化的场景。
十.策略模式
将不同的实现算法进行封装,将功能的实现与使用相分离。(多选一策略)在使用时,可以用不同的策略实现类进行替换。重点用到的知识点:继承,多态。
结构
抽象类:抽象方法
具体实现类(多个实现类实现抽象类的抽象方法,在多个实现类中多选一)
环境类(使用者)
感谢你的阅读与关注,如有问题欢迎探讨!💓