Java 设计模式
来源于 尚硅谷 韩顺平 这个课非常不错,还有配套的资料!!!
Java 设计模式的七大原则
单一职责原则
基本介绍
对类来说的,即一个类应该只负责一项职责如 类A 负责两个不同职责:职责1,职责2。当 职责1 需求变更而改变 A 时,可能造成 职责2 执行错误,所以需要将 类A的 粒度分解为 A1,A2
样例(太过啰嗦)
package com.atguigu.principle.singleresponsibility;
public class SingleResponsibility2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("汽车");
AirVehicle airVehicle = new AirVehicle();
roadVehicle.run("飞机");
WaterVehicle waterVehicle = new WaterVehicl();
roadVehicle.run("轮船");
}
}
class RoadVehicle {
public void run(String vehicle) {
System.out.println(vehice + "公路运行");
}
}
class AirVehicle {
public void run(String vehicle) {
System.out.println(vehice + "空中运行");
}
}
class WaterVehicle {
public void run(String vehicle) {
System.out.println(vehice + "水上运行");
}
}
样例改进
package com.atguigu.principle.singleresponsibility;
public class SingleResponsibility2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Vehicle vehicle = new Vehicle();
vehicle.run("汽车");
vehicle.run("飞机");
vehicle.run("轮船");
}
}
class Vehicle {
public void Roadrun(String vehicle) {
System.out.println(vehice + "公路运行");
}
public void Airrun(String vehicle) {
System.out.println(vehice + "空中运行");
}
public void Waterrun(String vehicle) {
System.out.println(vehice + "水上运行");
}
}
单一职尽原则注意事项和细节
- 降低类的复杂度,一个类只负责一项职责。
- 提高类的可读性,可维护性
- 降低变更引起的风险
- 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则
接口隔离原则(Interface Segregation Principle)
基本介绍
- 客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小接口上
- 按隔离原则应当这样处理:将接口 Interface1 拆分为独立的几个接口,类A 和 类C 分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则
- 类A 通过接口 Interface1 依赖 类B, 类C 通过接口 Interface1 依赖 类D, 如果接口 Interface 对于 类A 和 类C 来说不是最小接口那么 类B和 类D 必须去实现他们不需要的方法。
样例一图胜千言
依赖倒转(倒置原则)Dependence Inversion Principle
概念
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转(倒置)的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念: 相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳得多。在 Java 中,抽象指的是接口或抽象类,细节就是具体得实现类
- 使用接口或抽象类得目的是制定好规范,而不涉及任何具体操作,把展现细节得任务交给他们得实现类去完成
依赖关系传递的三种方式和应用案例
接口传递
interface TV {
void play();
}
class SamsungTV implements TV {
public void play() {
System.out.println("Samsung TV is playing.");
}
}
interface RemoteControl {
void open(TV tv);
}
class BasicRemoteControl implements RemoteControl {
public void open(TV tv) {
tv.play();
}
}
public class Main {
public static void main(String[] args) {
TV myTV = new SamsungTV();
RemoteControl myRemote = new BasicRemoteControl();
myRemote.open(myTV); // 通过接口方法传递依赖对象
}
}
构造方法传递
interface TV {
void play();
}
class SamsungTV implements TV {
public void play() {
System.out.println("Samsung TV is playing.");
}
}
interface RemoteControl {
void open();
}
class AdvancedRemoteControl implements RemoteControl {
private TV tv;
// 通过构造方法传递依赖对象
public AdvancedRemoteControl(TV tv) {
this.tv = tv;
}
public void open() {
tv.play();
}
}
public class Main {
public static void main(String[] args) {
TV myTV = new SamsungTV();
RemoteControl myRemote = new AdvancedRemoteControl(myTV);
myRemote.open();
}
}
setter 方式传递
interface TV {
void play();
}
class SamsungTV implements TV {
public void play() {
System.out.println("Samsung TV is playing.");
}
}
interface RemoteControl {
void setTV(TV tv);
void open();
}
class FlexibleRemoteControl implements RemoteControl {
private TV tv;
// 通过setter方法传递依赖对象
public void setTV(TV tv) {
this.tv = tv;
}
public void open() {
if (tv != null) {
tv.play();
} else {
System.out.println("No TV set.");
}
}
}
public class Main {
public static void main(String[] args) {
TV myTV = new SamsungTV();
FlexibleRemoteControl myRemote = new FlexibleRemoteControl();
myRemote.setTV(myTV); // 通过setter方法传递依赖对象
myRemote.open();
}
}
依赖倒转原则的注意事项和细节
- 底层模块尽量都要有抽象类或接口, 或者两者都有,程序稳定性更好。
- 变量的声明尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。
- 继承时遵循里氏替换原则
里氏替换原则
oo中的继承性的思考和说明
- 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏
- 继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个父类被其他类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障
- 问题提出:在编程中,如何正确的使用继承? => 里氏替换原则
基本介绍
- 氏替换原则(Liskov Substitution Principle)在1988年,由麻省理工学院的以为姓里的女士提出的。
- 果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
- 使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
- 氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。
问题
解决方法
- 我们发现原来运行正常的相减功能发生了错误。原因就是 类B 无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候。
- 通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替.
// 实现一个更加基本的类
class Base {
}
class A extends Base {
// 返回两个数的差
public int func1(int num1, int num2) {
return num1 - num2;
}
}
// B类继承了A
// 增加了一个新功能:完成两个数相加,然后和9求和
class B extends Base {
// 如果B需要使用类A的方法,使用组合关系
private A a = new A();
// 这里,重写了A类的方法,可能是无意识
public int func1(int a, int b) {
return a + b;
}
public int func2(int a, int b) {
return func1(a, b) + 9;
}
// 我们仍然想使用 A 的方法
public int func3(int a, int b) {
return this.a.func1(a, b);
}
}
开闭原则
基本介绍
- 关闭原则(Open Closed Principle) 是编程中最基础,最重要的设计原则
- 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
- 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
样例
优缺点
- 优点是比较好理解,简单易操作
- 缺点是违反了设计模式的OCP原则,即对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码。
- 比如我们这时要新增一个图形种类 三角形, 我们需要做如下修改,修改的地方较多
代码示例
package com.atguigu.principle.ocp;
public class Ocp {
public void main(String[] args) {
// 使用看看存在的问题
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShasp(new Rectangle());
graphicEditor.drawShasp(new Circle());
graphicEditor.drawShasp(new Triangle());
}
}
// 这是一个用于绘图的类[使用方]
class GraphicEditor {
// 接收 Shape 对象,然后根据 type, 来绘制不同的图形
public void drawShape(Shape s) {
if (s.m_type == 1)
drawRectangle(s);
else if (s.m.type == 2) {
drawCircle(s);
} else if (s.m.type == 3) {
drawTrangle(s);
}
}
}
// 绘制矩形
public void drawCircle(Shape r) {
System.out.println("绘制圆形");
}
// 绘制三角形
public void Triangle(Shape r) {
System.out.println("绘制三角形");
}
// Shape类,基类
class Shape {
int m_type;
}
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
}
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
}
// 新增画三角形
class Triangle extends Shape {
Triangle() {
super.m_type = 3;
}
}
改进
思路:把创建 Shape类 做成抽象类,并提供一个 抽象的draw方法,让子类去实现即可,这样我们有新的图形种类时,只需要让 新的图形继承Shape,并实现draw方法 即可,使用方的代码就不需要修->满足了开闭原则
package com.atguigu.principle.ocp;
public class Ocp {
public void main(String[] args) {
// 使用看看存在的问题
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
graphicEditor.drawShape(new OtherGraphic());
}
}
// Shape类,基类
abstract class Shape {
int m_type;
public abstract void draw(); //抽象方法
}
// 这是一个用于绘图的类[使用方]
class GraphicEditor {
// 接收 Shape 对象,调用 draw 方法
public void drawShape(Shape s) {
s.draw();
}
}
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
@Override
// TODO Auto-generated method stub
System.out.println("绘制图形");
}
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
@Override
public void draw() {
// TODO auot-generated method stub
System.out.println("绘制圆形");
}
}
class Triangle extends Shape {
Circle() {
super.m_type = 3;
}
@Override
public void draw() {
// TODO auot-generated method stub
System.out.println("绘制三角型");
}
}
class Triangle extends Shape {
Circle() {
super.m_type = 4;
}
@Override
public void draw() {
// TODO auot-generated method stub
System.out.println("绘制其他图形");
}
}
迪米特法则(Demeter Principle)
基本介绍
- 一个对象应该对其他对象保持最少的了解
- 与类关系越密切,耦合度越大
- 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public 方法,不对外泄露任何信息
- 迪米特法则还有个更简单的定义:只与直接的朋友通信
- **直接的朋友:每个对象都会与其他对象由耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。**其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
代码示例
// 客户端
public class Demeter1 {
// 创建一个 SchoolManager 对象
SchoolManager schoolManager = new SchoolManager();
// 输出学院的员工 id 和 学校总部的员工信息
schoolManager.printAllEmployee(new CollegeManager());
}
// 学校总部员工类
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
// 学院的员工类
class CollegeEmployee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
// 管理学院员工的管理类
class CollegeManager {
// 返回学院的所有员工
public List<CollegeEmployee> getAllEmployee() {
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) { // 这里我们增加了 10 个员工到 list
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id=" + i);
list.add(emp);
}
return list;
}
}
// 学校管理类
// 分析 SchoolManager 类的直接朋友类有哪些 Employee, CollegeManager
// CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则
class SchoolManager {
// 返回学校总部的员工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) { // 这里我们增加了 5 个员工到 list
Employee emp = new Employee();
emp.setId("学校总部员工 id=" + i);
list.add(emp);
}
return list;
}
}
// 该方法啊完成输入学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub) {
// 分析问题
//1.这里的 CollegeEmployee 不是, SchoolManager 的直接朋友
// 2.CollegeEmployee 是以局部变量方式出现在 SchoolManager
// 违反了 迪米特法则
// 获取到学院员工
List<CollegeEmployee> list1 = sub.getAllEmployee();
System.out.println("-----------学院员工----------");
for (CollegeEmployee e : list) {
System.out.println(e.getId());
}
// 获取到学校总部员工
List<Employee> list2 = this.getAllEmployee();
System.out.prinltn("-------------学校总部员工---------------");
for (Employee e : list2) {
System.out.println(s.getId());
}
}
改进
- 前面设计的问题在于 SchoolManager 中,CollegeEmployee类 并不是 SchoolManager类 的直接朋友(分析)
- 按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合
- 对代码按照迪米特法则进行改进.(看老师演示)
// 客户端
public class Demeter1 {
// 创建一个 SchoolManager 对象
SchoolManager schoolManager = new SchoolManager();
// 输出学院的员工 id 和 学校总部的员工信息
schoolManager.printAllEmployee(new CollegeManager());
}
// 学校总部员工类
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
// 学院的员工类
class CollegeEmployee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
// 管理学院员工的管理类
class CollegeManager {
// 返回学院的所有员工
public List<CollegeEmployee> getAllEmployee() {
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) { // 这里我们增加了 10 个员工到 list
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id=" + i);
list.add(emp);
}
return list;
}
}
// 学校管理类
// 分析 SchoolManager 类的直接朋友类有哪些 Employee, CollegeManager
// CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则
class SchoolManager {
// 返回学校总部的员工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) { // 这里我们增加了 5 个员工到 list
Employee emp = new Employee();
emp.setId("学校总部员工 id=" + i);
list.add(emp);
}
return list;
}
}
// 该方法啊完成输入学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub) {
// 分析问题
//1.这里的 CollegeEmployee 不是, SchoolManager 的直接朋友
// 2.CollegeEmployee 是以局部变量方式出现在 SchoolManager
// 违反了 迪米特法则
// 获取到学院员工
List<CollegeEmployee> list1 = sub.getAllEmployee();
System.out.println("-----------学院员工----------");
for (CollegeEmployee e : list) {
System.out.println(e.getId());
}
// 将输出学院的员工方法,封装到 CollegeManager
sub.printEmployee();
}
合成复用原则(Composite Reuse Principle)
基本介绍
原则是尽量使用合成/聚合的方式,而不是使用继承
目的
- 代码重用性 (相同功能的代码,不用多次编写)
- 可读性 (编程规范性,便于其他程序员的阅读和理解)
- 可扩展性 (当需要增加新的功能时,非常的方便,称为可维护)
- 可靠性**(当我们增加新的功能后,对原来的功能没有影响)**
- 使得程序呈现高内聚,低耦合的特性
设计模式包含了面向对象的精髓, “懂了设计模式,你就懂了面向 对象分析和设计(OOA/D) 的精要”
设计原则核心思想
- 找出应用中可能需要变化之处,把他们独立出来,不要和哪些不需要变化的代码混合在一起
- 针对接口编程,而不是针对实现编程
- 为了交互对象之间的松耦合设计而努力
UML类图
- 用于描述系统中的 类(对象) 本身的组成和 类(对象) 之间的各种静态关系;
- 类之间的关系: 依赖,泛化(继承),实现,关联,聚合与组合;
- 类图简单示例;
UML类图基本介绍
- UML – Unified modeling language UML(统一建模语言) 是一种用于软件系统分析合设计的语言工具,它用于帮助软件系统分析合设计的语言工具,它用于帮助软件开发人员的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果
- UML 本身是一套符号的规定,就像数学符号和化学符号一样,这些符号用于描述软件模型中的各个元素和他们呢之间的关系,比如类,接口,实现,泛化,依赖,组合,聚合等。如右图:
- 使用 UML 来建模,常用的工具有 Rational Rose,也可以使用一些插件来建模
UML图
画 UML图 与写文章差不多,都是把自已的思想描述给别人看,关键在于思路和条理,UML图 分类:
- 用例图(use case)
- 静态结构图: 类图,对象图,包图,组件图,部署图
- 动态行为图:交互图(时序图与协作图), 状态图,活动图
说明
- 类图是描述类与类之间的关系的,是 UML图 最核心的
- 在讲解设计模式时,我们必然会使用类图,为了让学员们能够把设计模式学到位,需要先给大家讲解类图
- 温馨提示:如果已经掌握 UML类 图的学员, 可以直接听设计模式的章节
依赖 (Dependence)
只要是在类中用到了对方,那么他们之间就存在依赖关系。如果没有对方,连编译都通过不了。
public class PersonServiceBean {
private PersonDao personDao; // 类
public void save(Person person) {}
public IDCard getIDCard(Integer personid){}
public void modify() {
Department department = new Department();
}
}
public class PersonDao{}
public class IDCard{}
public class Person{}
public class Department{}
泛化 (generalization)
泛化关系实际上就是继承关系,他是依赖关系的特例
public abstract class DaoSupport {
public void save(Object entity) {
}
public void delete(Object id) {
}
}
public class PersonServiceBean extends Daosupport {}
小结
- 泛化关系实际上就是继承关系
- 如果 A类 继承了 B类,我们就说 A 和 B 存在泛化关系
实现关系 (Implementation)
实现关系实际上就是 A类 实现 B类, 他是依赖关系的特例
public interface PersonService {
public void delete(Integer id){};
}
public class PersonSeiviceBean implements PersonService {
public void delete(Integer id){};
}
关联关系 (Association)
关联关系实际上就是类与类中之间的关系,他是依赖关系的特例
- 关联具有导航性:即双向关系或单向关系
- 关系具有多重性:如 “1”(表示有且仅有一个),“0…”(表示 0个 或 多个), “0, 1”(表示 0个 或者 一个),“n…m”(表示 n到 m个都可以), “m…”(表示至少m个)
// 单向一对一关系
public class Person {
private IDcard card;
}
public class IDCard();
// 双向一对一关系
public class Person {
private IDCard card;
}
public class IDCard {
private Person person;
}
聚合关系 (Aggregation)
聚合关系 (Aggragation) 表示的是整体和部分的关系,整体与部分可以分开。聚合关系是关联关系的特例,所以他具有关联的 导航性与多重性。
如:一台电脑由键盘(keyboard), 显示器(monitor), 鼠标等组成; 组成电脑的各个配件是可以从电脑上分离出来的,使用带空心菱形的实现来表示
public class Computer {
private Mouse mouse;
private Monitor monitor;
public void setMouse(Mouse mouse) {
this.mouse = mouse;
}
public void setMonitor (Monitor monitor) {
this.monitor = monitor;
}
}
组合关系 (Composition)
整体与部分的关系,但是整体与部分不可以分开。
再看一个案例:在程序中我们定义实体:Person 与 IDCard、Head,那么 Head 和 Person 就是组合,IDCard 和Person 就是聚合。
但是如果在程序中 Person实体 中定义了对 IDCard进行级联删除,即 删除Person 时连同 IDCard 一起删除,那么 IDCard 和 Person 就是组合了.
public class Person {
private IDCard card;
private Head head = new Head();
}
public class IDCard{}
public class Head{}
public class Computer {
private Mouse mouse = new Mouse(); //鼠标可以和 computer 不能分离
private Moniter moniter = new Moniter(); // 显示器可以和 Computer 不能分离
public void setMouse(Mouse mouse) {
this.mouse = mouse;
}
public void setMoniter(Moniter moniter) {
this.moniter = moniter;
}
}
public class Mouse {
}
public class Moniter {
}
总结:
Java 设计模式
设计模式介绍
- 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设**计模式(Design pattern) 代表了最佳的实践。**这些解决方案是众多软件开发人员经过相当长的一段时间的试错和错误总结出来的。
- 设计模式的本质提高 软件的维护性,通用性和扩展性,并降低软件的复杂度。
- <设计模式> 是经典的书,作者是 Erich Gamma,Richard Helm, Ralph Johnson 和 John Vlissides Design (俗称 “四人组 GOF”)
- 设计模式并不局限于某种语言,java, php, c++ 都有设计模式。
设计模式类型 (23)
-
创建型模式:单例模式,抽象工厂模式,原型模式,建造者模式,工厂模式
-
**结构型模式:**适配型模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式
-
**行为模式:**模板方法模式,命令模式,访问者模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式(Interpreter模式),状态模式,策略模式,职责链模式(责任链模式)
⚠️ 不同的书籍上对分类和名称略有区别
一,单例模式
所谓的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
eg:
Hibernate 的 SessionFactory,它充当数据存储源的代码,并负责创建 Session 对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory 就够,这是就会使用到单例模式
饿汉式(静态常量)
- 构造器私有化(防止 new)
- 类的内部创建对象
- 向外暴露一个静态的公共方法 getInstance
class Singleton {
// 1.构造器私有化,外部能 new
private Singleton(){ }
// 2.本类内部创建对象实例
private final static Singleton instance = new Singleton();
// 3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
reutrn instance;
}
}
优缺点
- 优点:种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
- 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,会造成内存的浪费。
- 这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候 初始化instance 就没有达到 lazy loading 的效果。
- 结论:这种单例模式可用,可能造成内存浪费。
饿汉式(静态代码块)
// 饿汉式(静态变量)
class Singleton {
// 1. 构造器私有化,外部能 new
private Singleton() {
}
// 2.本类内部创建对象实例
private static Singleton instance;
static { // 在静态代码块中,创建单例对象
instance = new Singleton();
}
// 3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
优缺点说明
- 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
- 结论:这种单例模式可用,但是可能造成内存浪费
懒汉式(线程不安全)
class Singleton {
private static Singleton instance;
private Singleton() {}
// 提供一个静态的共有方法,当使用到该方法时,才去创建 instance
// 即懒汉式
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优缺点说明:
- 起到了 Lazy Loading的效果,但是只能在单线程下使用。
- 如果在多线程下,一个线程进入了 if (singleton == null)判 断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
- 结论:在实际开发中,不要使用这种方式. ⚠️
懒汉式(线程不安全,同步方法)
class Singleton {
private static Singleton instance;
pirvate Singleton() { }
// 提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
// 即懒汉式
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优缺点说明:
- 决了 线程安全 问题
- 效率太低了,每个线程在想获得类的实例时候,执行 getInstance() 方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低.
- 结论:在实际开发中,不推荐使用这种方式
懒汉式(线程安全,同步代码块)
⚠️ 不使用: 多线程崩溃和低效率
class Singleton {
private staic Singleton singleton;
private Singleton() { }
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
双重检查
class Singleton {
private static volatile Singleton instance;
pirvate Singleton() { }
// 提供一个静态的共有方法,加入双重检查代码,解决线程安全问题,同时解决 lazy loading 加载问题
// 同时保证了效率, 推荐使用
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
优缺点说明:
- Double-Check概念 是多线程开发中常使用到的,如代码中所示,我们进行了两次 if(singleton==null)检查,这
样就可以保证线程安全了。 - 样,实例化代码只用执行一次,后面再次访问时,判断 if(singleton == null),直接return 实例化对象,也避
免的反复进行方法同步, - 线程安全;延迟加载;效率较高
- 结论:在实际开发中,推荐使用这种单例设计模式
静态内部类
// 静态内部类完成,推荐使用
class Singleton {
private static volatic Singleton instance;
// 构造器私有化
private Singleton() { }
// 写一个静态内部类,该类中有一个静态属性 Singleton
private static class SingletonInastance {
private static final Singleton INSTANCE = new Singleton();
}
// 提供了一个静态方法,直接返回 SingletonInstance.INSTANCE
public static synchronized Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
优缺点说明:
- 这种方式采用了 类装载 的机制来保证初始化实例时只有一个线程。
- 静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化。
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
- 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
- 结论:推荐使用.
枚举
// 使用枚举,可以实现单例,推荐
enum Singleton {
INSTANCE; //属性
public void sayOK() {
System.out.println("oK~");
}
}
优缺点说明:
- 这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
- 种方式是 EffectiveJava 作者 Josh Bloch 提倡的方式
- 结论:推荐使用
单例模式在 JDK 应用的源码分析
- 我们 JDK 中,java.lang.Runtime 就是经典的单例模式(饿汉式)
- 代码分析 + Debug 源码 + 代码说明
单例模式注意事项和细节说明
- 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
- 想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new。
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。
工厂模式
需求
看一个披萨的项目:要便于披萨种类的扩展,要便于维护
- 披萨的种类很多(比如 GreekPizz、CheesePizz 等)
- 披萨的制作有 prepare,bake, cut, box
- 完成披萨店订购功能
使用传统的方式来完成
public class OrderPizza {
// 构造器
public OrderPizza() {
Pizza pizza = null;
String orderType; // 订购披萨的类型
do {
orderType = getType();
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("希腊披萨");
} else if (OrderType.equals("chess")) {
pizza = new CheesePizza();
pizza.setName("奶酪披萨");
} else if (orderType.equals("pepper")) {
pizza = new PepperPizza();
} else {
break;
}
// 输出 pizza 制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} while (true)
}
}
传统的方式的优缺点
-
优点是比较好理解,简单易操纵
-
缺点是违反了设计模式的 ocp 原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码。
-
比如我们这时 **要新增加一个 Pizza 的种类(Pepper披萨),**我们需要做如下修改.
如果我们增加一个Pizza类,只要是订购Pizza的代码都需要修改.
-
改进的思路分析
- 分析:修改代码可以接受,但是如果我们在其它的地方也有创建 Pizza 的代码,就意味着,也需要修改,而创建 Pizza 的代码,往往有多处。
- 思路:把创建 Pizza 对象封装到一个类中,这样我们有新的 Pizza 种类时,只需要修改该类就可,其它有创建到Pizza对象的代码就不需要修改了.->简单工厂模式
简单工厂模式的基本介绍
- 简单工厂模式是属于 创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式。
- 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)。
- 软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式。
使用简单工厂模式
- 简单工厂模式的设计方案:定义一个可以实例化 Pizaa 对象的类,封装创建对象的代码
// 简单工厂类
public class SimpleFactory {
// 更加 orderType 返回对应的 Pizza 对象
public Pizza createPizza(String orderType) {
public Pizza createPizza(String orderType) {
Pizza pizza = null;
System.out.println("使用简单工厂模式");
if (orderType.equals("geek")) {
pizza = new CreekPizza();
pizza.setName("希腊披萨");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName("奶酪披萨");
} else if (orderType.equals("pepper")) {
pizza = new PepperPizza();
pizza.setName("胡椒披萨");
}
return pizza;
}
// 简单工厂模式 也叫 静态工厂模式
public static Pizza createPizza(String orderType) {
Pizza pizza = null;
System.out.println("使用简单工厂模式2");
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("希腊披萨");
} else if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("奶酪披萨");
} else if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("胡椒披萨");
}
return pizza;
}
}
}
// 点披萨
package com.atguigu.factory.simplefactory.pizzastore.order;
public class OrderPizza {
// 构造器
SimpleFactory simpleFactory;
Pizza pizza = null;
// 构造器
public OrderPizza(SimpleFactory simpleFactory) {
setFactory(simpleFactory);
}
public void setFactory(SimpleFactory simpleFactory) {
String orderType = ""; // 用户输入的
this.simpleFactory = simpleFactory; // 设置简单工厂对象
do {
orderType = getType();
pizza = this.simpleFactory.createPizza(orderType);
// 输出pizza
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购披萨失败");
break;
}
} while(true);
// 写一个方法,可以获取客户希望订购的披萨种类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza 种类");
String str = strin.readLine();
return str;
} catch(IOException) {
e.printStackTrace();
return "";
}
}
}
}
工厂方法模式
看一个新的需求
披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪 pizza、北京的胡椒 pizza 或者是伦敦的奶酪 pizza、伦敦的胡椒 pizza。
思路1
使用 简单工厂模式,创建 不同的简单工厂类,比如 BJPizzaSimpleFactory、LDPizzaSimpleFactory 等等.从当前这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性、可扩展性并不是特别好。
思路2
使用工厂方法模式。
工厂方法模式介绍
- 工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现。
- 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例 化推迟到子类。
6.2.5工厂方法模式应用案例
- 披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪pizza、北京的胡椒pizza 或者是伦敦的奶酪 pizza、伦敦的胡椒 pizza
抽象工厂模式
基本介绍
- 抽象工厂模式:定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
- 将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
- 类图
工厂模式在 JDK-Calendar 应用的源码分析
- JDK 中的 Calendar 类中,就使用了简单工厂模式
工厂模式小结
- 工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
- 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
- 设计模式的依赖抽象原则
- 创建对象实例时,不要直接 new 类,而是把这个 new 类的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
- 不要让类继承具体类,而是继承抽象类或者是实现 interface(接口)。
- 不要覆盖基类中已经实现的方法。
原型模式
克隆羊问题
现在有一只羊 tom, 姓名为 tom, 年龄为:1,颜色为:白色,请编写程序创建和tom羊 属性完全相同的 10只羊。
传统方式解决克隆羊问题
传统的方式的优缺点
- 优点是比较好理解,简单易操作。
- 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低。
- 总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活
- 改进的思路分析
- Java中 Object类 是所有类的根类,Object类 提供了一个 clone()方法,该方法可以将一个Java 对象复制一份,但是需要实现 clone的Java 类必须要实现一个接口 Cloneable, 该接口表示该类能够复制且具有复制的能力 ==> 原型模式
原型模式-基本介绍
- 原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
- 原型模式是一种 创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自已来实施创建,即 对象.clone()
- 形象的理解:孙大圣拔出猴毛,变出其它孙大圣
UML类图
原理结构图说明
- Prototype:原型类,声明一个克隆自己的接口。
- ConcretePrototype: 具体的原型类,实现一个克隆自己的操作。
- Client: 让一个原型对象克隆自己,从而创建一个新的对象(属性一样)。
原型模式解决克隆羊问题的应用实例
使用原型模式改进传统方式,让程序具有更高的效率和扩展性。
原型模式在 Spring 框架中源码分析
- Spring 中原型 bean 的创建,就是原型模式的应用。
- 代码分析 + Debug 源码
深入讨论-浅拷贝和深拷贝
浅拷贝的介绍
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
- 前面我们克隆羊就是浅拷贝
- 浅拷贝是使用默认的clone(O方法来实现 sheep = (Sheep) super.clone();
深拷贝
- 复制对象的所有基本数据类型的成员变量值。
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝。
- 深拷贝实现方式1:重写clone方法来实现深拷贝。
- 深拷贝实现方式2:通过对象序列化实现深拷贝(推荐)
深拷贝应用实例
- 使用 重写clone方法 实现深拷贝。
- 使用 序列化 来实现深拷贝
public class DeepCloneableTarget implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
private String cloneName;
private String cloneClass;
// 构造器
public DeepCloneableTarget(String cloneName, String cloneClass) {
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}
// 因为该类的属性 都是 String,因此我们这里使用默认的 clone 完成即可
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class DeepProtoType implements Serializable Cloneable {
public String name; // String属性
public DeepCloneableTarget deepClonableTarget; // 引用类型
public DeepProtoType() {
super();
}
// 深拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
// 这里完成对基本数据类型(属性)和String的克隆
deep = super.clone();
// 对引用类型的属性,进行单独处理
DeepProtoType deepProtoType = (DeepProtoType) deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone();
// TODO Auto-generated method stub
return deepProtoType;
}
// 深拷贝 - 方式 2 通过对象的序列化实现(推荐)
public Object deepClone() {
// 创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
// 序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); // 当前这个对象以对象流的方式输出
// 反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copyObj = (DeepProtoType) ois.readObject();
return copyObj;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return null;
} finally {
// 关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch(Exception e2) {
// TODO: handle exception
System.out.println(e2.getMessage());
}
}
}
}
public class Client {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
DeepProtoType p = new DeepProtoType();
p.name = "宋江";
p.deepCloneableTarget = new DeepCloeableTarget("大牛", "小牛");
// 方式 1 完成拷贝
DeepProtoType p2 = (DeepProtoType) p.clone();
System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
// 方式 2 完成深拷贝
DeepProtoType p2 = (DeepProtoType) p.deepClone();
System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
System.out.println("p2.name=" + p.name + "p2.name" + "p2.deepCloneqableTarget=" + p2.deepClonableTarget.hashCode());
}
}
原型模式的注意事项和细节
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
- 不用重新初始化对象,而是动态地获得对象运行时的状态。
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码。
- 在实现深克隆的时候可能需要比较复杂的代码。
- **缺点:**需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改
其源代码,违背了 ocp原则,这点请同学们注意.
建造者模式
盖房项目需求
- 需要建房子:这一过程为打桩、砌墙、封顶。
- 房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的,
- 请编写程序,完成需求
传统方式解决盖房需求
由UML图可以较为轻松的得出Java代码
public abstract class AbstractHouse {
// 打地基
public abstract void buildBasic();
// 砌墙
public abstract void buildWalls();
// 封顶
public abstract void roofed();
public void build() {
buildBasic();
buildWalls();
roofed();
}
}
public class CommonHouse extends AbstractHouse {
@Override
public void buildBasic() {
// TODO Auto-generated method stub
System.out.println("普通房子打地基");
}
@Override
public void buildWalls() {
// TODO Auto-generated method stub
System.out.println("普通房子砌墙");
}
@Overiride
public void roofed() {
// TODO Auto-generated method stub
System.out.println("普通房子封顶");
}
}
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
CommonHouse commonHouse = new CommonHouse();
commonHouse.build();
}
}
传统方式的问题分析
- 优点是比较好理解,简单易操作。
- 设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好.也就是说,这种设计方案,把产品(即:房子)和 创建产品的过程(即:建房子流程) 封装在一起,耦合性增强了。
- 解决方案:将产品和产品建造过程解耦 => 建造者模式.
建造者模式基本介绍
基本模式
- 建造者模式(BuilderPattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
- 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
建造者模式的四个角色
- Product (产品角色) : 一个具体的产品对象。
- Builder (抽象类建造者): 创建一个 Product 对象的各个部件指定的 接口/抽象类
- ConcreteBuilder (具体建造者): 时间接口,构建和装配各个部件
- Director (指挥者): 建一个使用 Builder 接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
建造者模式原理类图
建造者模式解决盖房需求应用实例
- 需要建房子:这一过程为打桩、砌墙、封顶。不管是普通房子也好,别墅也好都需要经历这些过程,下面我们使用建造者模式(BuilderPattern) 来完成。
- 思路分析图解(类图)
public class Client {
public static void main(String[] args) {
// 该普通房子
CommonHouse commonHouse = new CommonHouse();
// 准备创建房子的指挥者
HouseDirector houseDirector = new HouseDirector(commonHouse);
// 完成盖房子,返回产品(普通房子)
House house = houseDirector.constructHouse();
// System.out.println("输出流程");
// 盖高楼
HighBuilding highBuilding = new HighBuilding();
// 重置建造者
houseDirector.setHouseBuilder(highBuilding);
// 完成盖房子, 放回产品(高楼)
houseDirector.constructHouse();
}
}
public class CommonHouse extends HouseBuilder {
@Override
public void buildBaseic() {
// TODO Auto-generatd method stub;
System.out.println("普通房子打地基 5 米");
}
@Override
public void buildBasic() {
// TODO Auto-generated method stub
System.out.println("普通房子打地基 5 米");
}
@Overiride
public void buildWalls() {
System.out.println("普通房子砌墙 10 cm");
}
@Overiride
public void buildWalls() {
System.out.println("普通房子屋顶");
}
}
public class HighBuilding extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("高楼的打地基 100 米");
}
@Override
public void buildBasic() {
System.out.println("高楼的砌墙 20米");
}
@Override
public void buildBasic() {
System.out.println("高楼的透明屋顶");
}
}
// 产品->Product
public class House {
private String baise;
private String wall;
private String roofed;
public String getBaise() {
return baise;
}
public void setBaise(String baise) {
this.baise = baise;
}
public String getWall() {
this.wall = wall;
}
public String getRoofed() {
return roofed;
}
public void setRootfed(String roofed) {
this.roofed = roofed;
}
}
public abstract class HouseBuilder {
protected House house = new House();
// 将建造的流程写好,抽象的方法
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
// 建造房子好,将产品(房子) 返回
public House buildHouse() {
return house;
}
}
// 指挥者 这里去指定制作流程,返回产品
public class HouseDirector {
HouseBuilder houseBuilder = null;
// 将构造器传入 houseBuilder
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
// 通过 setter 传入 houseBuilder
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
// 如何处理建造房子的流程,交给指挥者
public House constructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
建造者模式在 JDK 的应用和源码分析
- java.lang.StringBuilder 中的建造者模式
- 代码说明 +Debug 源码
- Appendable 接口定义了多个 append 方法(抽象方法),即 Appendable 为抽象建造者,定义了抽象方法.
- AbstractStringBuilder 实现了Appendable 接口方法,这里的 AbstractStringBuilderr 已经是建造者,只是不能实例化
- StringBuilder 即充当了指挥者角色,同时充当了具体的建造者,建造方法的实现是由 AbstractStringBuilder 完成,而 StringBuilder 继承了 AbstractStringBuilder.
建造者模式的注意事项和细节
- 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
- 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”。
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.
- 抽象工厂模式 VS 建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
适配器设计模式
现实生活中的适配器例子
泰国插座用的是两孔的(欧标),可以买个多功能转换插头(适配器),这样就可以使用了。
基本介绍
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为 包装器(Wrapper)。
- 适配器模式属于结构型模式。
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。
工作原理
- 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
- 从用户的角度看不到被适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口交互,如图
类适配器模式
类适配器模式介绍
Adapter 类,通过继承 src 类,实现 dst 类接口,完成 src->dst 的适配
类适配器模式应用实例
-
应用实例说明
以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V交流电 相当于 src(即被适配者),我们的目 dst(即目标) 是5V直流电
-
思路分析(类图)
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("=====类适配器=====");
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
// 适配器接口
public interface IVoltage5V {
public int output5V();
}
public class Phone {
// 充电
public void charging(IVoltagev iVoltage5V) {
if (iVoltage5V.output5V() == 5) {
System.out.println("电压为5v可以充电");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压为大于 5V 不能充电");
}
}
public class Voltage220V {
// 输出 220V 的电压
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
// 适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
// 获取到 220V 电压
int srcV = output220V();
int dstV = srcV / 44; //转成 5v
return dstV;
}
}
类适配器模式注意事项和细节
- Java是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点,因为这 要求dst必 须是接口,有一定局限性;
- src 类 的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
- 由于其继承了src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。
对象适配器模式
对象适配器模式介绍
- 基本思路和类的适配器模式相同,只是 将Adapter类 作修改,不是 继承 src 类,而是 持有 src 类的实例,以解决兼容性的问题。即:持有src 类,实现dst 类接口,完成 src->dst 的适配
- 根据 “合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。
- 对象适配器模式是适配器模式常用的一种
对象适配器模式注意事项和细节
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承src的局限性 问题,也不再 要求dst必须 是接口。
- 使用成本更低,更灵活。
接口适配器模式
接口适配器模式介绍
- 一些书籍称为:适配器模式(DefaultAdapterPattern)或缺省适配器模式。
- 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
- 适用于一个接口不想使用其所有的方法的情况。
接口适配器模式应用实例
- Android 中的属性动画 ValueAnimator 类可以通过 addListener(AnimatorListener listener)方法添加监听器,那么常规写法如右:
- 有时候我们不想实现 Animator.AnimatorListener 接口的全部方法,我们只想监听 onAnimationStart,我们会如下写
- AnimatorListenerAdapter 类,就是一个接口适配器,代码如右图:它空实现了Animator.AnimatorListener类(src的所有方法.
- AnimatorListener 是一个接口
- 程序里的匿名内部类就是 Listener 具体实现类
- 案例说明
适配器模式在 SpringMVC 框架应用的源码剖析
- SringMvc 中的 HandlerAdapter,就使用了适配器模式
- SpringMVC 处理请求的流程回顾
- 使用HandlerAdapter 的原因分析:
可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用 Controller方法,需要调用的时候就得不断是使用 if else来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样违背了OCP原则。
- 动手写 SpringMVC 通过适配器设计模式获取到对应的 Controller 的源码
- Spring定义了一个适配接口,使得每一种 Controller 有一种对应的适配器实现类。
- 适配器代替 controller 执行相应的方法。
- 扩展 Controller时,只需要增加一个适配器类就完成了 SpringMvc 的扩展了
- 这就是设计模式的力量
适配器模式的注意事项和细节
- 三种命名方式,是根据 src 是以怎样的形式给到 Adapter(在Adapter里的形式)来命名的。
- 类适配器:以类给到,在 Adapter 里,就是将 src 当做类,继承
对象适配器:以对象给到,在 Adapter 里,将 src 作为一个对象,持有
接口适配器:以接口给到,在 Adapter 里,将 src 作为一个接口,实现 - Adapter模式 最大的作用还是将原本不兼容的接口融合在一起工作。
- 实际开发中,实现起来不拘泥于我们讲解的三种经典形式。
桥接模式
手机操作问题
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:
传统方案解决手机操作问题
传统方案解决手机操作问题分析
- 扩展性问题**(类爆炸)**,如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
- 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本.
- 解决方案-使用桥接模式
桥接模式(Bridge)-基本介绍
- 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
- 是一种结构型设计模式。
- Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是**把抽象(Abstraction)与行为实现(Implementation)**分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
桥接模式(Bridge)-原理类图
上图做了说明
- Client类:桥接模式的调用者.
- 抽象类(Abstraction):维护了 Implementor / 即它的实现类 ConcreteImplementorA.,二者是聚合关系,Abstraction充当桥接类.
- RefinedAbstraction:是 Abstraction 抽象类的子类。
- Implementor:行为实现类的接口。
- ConcretelmplementorA/B:行为的具体实现类。
- 从 UML 图:这里的抽象类和接口是聚合的关系,其实调用和被调用关系
桥接模式解决手机操作问题
使用桥接模式改进传统方式,让程序具有搞好的扩展性,利用程序维护
- 应用实例说明(和前面要求一样)
- 使用桥接模式对应的类图
桥接模式在JDBC的源码剖析
- Jdbc 的 Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有 MySQL 的 Driver,Oracle 的Driver,这些就可以当做实现接口类
桥接模式的注意事项和细节
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
- 桥接模式要求**正确识别出系统中两个独立变化的维度(抽象、和实现),**因此其使用范围有一定的局限性,即需要有这样的应用场景。桥接模式其它应用场景
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.
常见的应用场景:
- JDBC驱动程序
- 银行转账系统
- 转账分类:网上转账,柜台转账,AMT转账 转账用户类型:普通用户,银卡用户,金卡用户.
- 消息管理: 消息类型:即时消息,延时消息
消息分类:手机短信,邮件消息,QQ消息…
装饰者设计模式
星巴克咖啡订单项目(咖啡馆):
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
- 调料:Milk、Soy(豆浆)、Chocolate
- 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便.
- 使用OO的来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合。
方案1-解决星巴克咖啡订单项目
方案1-解决星巴克咖啡订单问题分析
- Drink 是一个抽象类,表示饮料。
- des 就是对咖啡的描述,比如咖啡的名字
- cost() 方法就是计算费用,Drink 类中做成一个抽象方法
- Decaf 就是单品咖啡,继承 Drink,并实现 cost
- Espress &&Milk 就是单品咖啡+调料,这个组合很多
- 问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸
方案 2-解决星巴克咖啡订单(好点)
- 前面分析到方案1因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内 置到Drink类,这样就不会造成类数量过多。从而提高项目的维护性(如图)
- 说明:milk, soy, chocolate 可以设计为 Boolean, 表示是否要添加相应的调料.
方案2-解决星巴克咖啡订单问题分析
- 方案2 可以控制类的数量,不至于造成很多的类
- 在增加或者删除调料种类时,代码的维护量很大
- 考虑到用户可以添加多份 调料时,可以将 hasMilk 返回一个对应 int
- 考虑使用 装饰者 模式
装饰者模式定义
- 装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
- 这里提到的动态的将新功能附加到对象和 ocp 原则,在后面的应用实例上会以代码的形式体现,请同学们注意体会。
装饰者模式原理
-
装饰者模式就像打包一个快递
主体:比如:陶瓷、衣服(Component )//被装饰者
包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator) -
Component主体:比如类似前面的 Drink
-
ConcreteComponent 和 Decorator ConcreteComponent:具体的主体,比如前面的各个单品咖啡
-
Decorator:装饰者,比如各调料.
在如图的 Component 与 ConcreteComponent 之间,如果 ConcreteComponent 类很多,还可以设计一个缓冲层,将
共有的部分提取出来,抽象层一个类。
装饰者模式解决星巴克咖啡订单
装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
代码
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDes("巧克力");
setPrice(3.0f);//调味品 的价格
}
}
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
public class CofferBar {
public static void main(String[] args) {
// 装饰模式下的订单 2份巧克力+一份牛奶的LongBlack
...
}
}
public class DeCaf extends Coffee {
public DeCaf() {
setDes("无因咖啡");
setPrice(1.0f);
}
}
public class Decorator extends Drink {
private Drink obj;
public Decorator(Drink obj) { // 组合
this.obj = obj;
}
return super.getPrice() + obj.cost();
}
装饰者模式在 JDK 应用的源码分析
Java 的 IO 结构,FilterInputStream 就是一个装饰者, Java的lO 结构,FilterlnputStream就是一个装饰者
public class Decorator {
public static void main(String[] args) throws Exception {
// 说明
// 1. InputStream 是抽象类,类似我们前面讲的 Drink
//2. FileInputStream 是 InputStream 子类,类似我么前面的 DeCaf, LongBlack
//3. FilterInputStream 是 InputStream 子类,类似我们前面的 Decorator 修饰者
//4. DataInputStream 是 FilterInputStream 子类,具体的修饰者,类似前面的 Milk, Sory等
//5. FilterInputStream 类 有 protected volatile InputStream in; 即含有被装饰者
//6. 分析得出在 jdk 的 io体系中,就是使用装饰者模式
DataInputStream dis = new DataInputStream(new FileInputStream("d:\\abc.txt"));
System.out.println(dis.read());
dis.close();
}
}
组合模式
看一个学校院系展示需求
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。如图:
传统方案解决学校院系展示(类图)
传统方案解决学校院系展示存在的问题分析
- 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
- 实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系,因此这种方案,不能很好实现的管理的操作,比如对学院、系的添加,删除,遍历等
- 解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作。=>组合模式
组合模式基本介绍
基本介绍
- 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示**“整体-部分”**的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次
- 这种类型的设计模式属于结构型模式。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象
组合模式原理类图
- Component: 这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component 子部件,Component 可以是抽象类或者接口
- **Leaf:**在组合中表示叶子节点,叶子节点没有子节点
- **Composite:**非叶子节点,用于存储子部件,在 Component 接口中实现 子部件的相关操作,比如增加(add), 删除。
组合模式解决学校院系展示的应用实例
- 编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。
组合模式在JDK 集合的源码分析
- Java 的集合类-HashMap 就使用了组合模式
- 代码分析 + Debug 源码
类图
组合模式的注意事项和细节
- 简化客户端操纵。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
- 要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式.
- 求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
外观模式
影院管理项目
组建一个家庭影院:
DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为:直接用遥控器:统筹各设备开关
开爆米花机
放下屏幕
开投影仪
开音响
开DVD,选dvd
去拿爆米花
调暗灯光
播放
观影结束后,关闭各种设备
传统方式解决影院管理
传统方式解决影院管理问题分析
- ClientTest 的main 方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程,
- 不利于在ClientTest 中,去维护对子系统的操作
- 解决思路:定义一个高层接口,给**子系统中的一组接口提供一个一致的界面(**比如在高层接口提供四个方法ready,play,pause,end),用来访问子系统中的一群接口
- 也就是说就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节=>外观模式
外观模式基本介绍
- 外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
- 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
外观模式原理类图
- 外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
- 调用者(Client) :外观接口的调用者
- 子系统的集合:指模块或者子系统,处理Facade 对象指派的任务,他是功能的实际提供者
外观模式解决影院管理
传统方式解决影院管理说明
- 外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能达到目的。比如:在pc上安装软件的时候经常有一键安装选项(省去选择安装目录、安装的组件等等),还有就是手机的重启功能(把关机和启动合为一个操作)。
- 外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用
- 示意图说明
外观模式应用实例
- 应用实例要求
- 使用外观模式来完成家庭影院项目
- 思路分析和图解(类图)
外观模式在 MyBatis 框架应用的源码分析
- MyBatis 中的 Configuration 去创建 MetaObject 对象使用到外观模式
外观模式的注意事项和细节
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
- 外观模式对客户端与子系统的耦合关系-解耦,让子系统内部的模块更易维护和扩展
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
- 当系统需要进行分层设计时,可以考虑使用 Facade 模式
- 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facad类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类 交互,提高复用性
- 能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。
享元模式
展示网站项目需求
小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:
- 客户要求以新闻的形式发布。
- 有客户人要求以博客的形式发布。
- 客户希望以微信公众号的形式发布
传统方案解决网站展现项目
- 直接复制粘贴一份,然后根据客户不同要求,进行定制修改。
- 给每个网站租用一个空间
- 方案设计示意图
传统方案解决网站展现项目-问题分析
- 要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
- 解决思路:整合到一个网站中,共享其相关的代码和数据,对于 硬盘、内存、CPU、数据库空间等服务器资源 都可以达成共享,减少服务器资源
- 对于代码来说,由于是一份实例,维护和扩展都更加容易
- 上面的解决思路就可以使用享元模式来解决
享元模式基本介绍
- 享元模式(Flyweight Pattern)也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象
- 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
- 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
- 享元模式经典的应用场景就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式
享元模式的原理类图
对类图的说明
- FlyWeight 是抽象的享元角色,他是产品的抽象类,同时定义出对象的外部状态和内部状态(后面介绍)的接口或实现
- ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
- UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。
- FlyWeightFactory 享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象方法
内部状态和外部状态
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态
- 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态
- 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
- 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
- 举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题
享元模式解决网站展现项目
- 使用享元模式完成 前面提出的网站外包问题
享元模式在 JDK-Interger 的应用源码分析
- Integer中的享元模式
public class FlyWeight {
public static void main(String[] args) {
// 如果 Integer.valueOf(x) x 在 128 -- 127 直接,就是使用享用模式返回,如果不在
// 范围类,则仍然 new
// 小结:
// 1. 在 valueOf 方法中,先判断值是否在 IntergerCache 中,如果不在,就创建新的 Integer(new),否则,就直接从 缓存池返回
// 2. valueOf 方法,就使用到享元模式
// 3. 如果使用 valueOf() 方法得到一个 Integer 实例,范围在-128 - 127,执行速度比 new 快
Integer x = Integer.valueOf(127); // 得到 x实例,类型 Integer
Integer y = new Integer(127);
Integer z = Integer.valueOf(127);
Integer w = new Integer(127);
System.out.println(x.equals(y));// 大小 true
System.out.println(x == y); // false
System.out.prinltn(x == z); // true
System.out.println(w == z);// false
System.out.println(w == y); //false
Integer x1 = Integer.valueOf(200);
Integer x2 = Integer.valueOf(200);
System.out.println("x1 == x2" + (x1 == x2)); // false
}
}
享元模式的注意事项和细节
- 在享元模式这样理解,“享”就表示共享,“元”表示对象
- 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
- 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
- 享元模式提高了 系统的复杂度。需要分离出 **内部状态和外部状态,**而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
- 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
- 享元模式经典的应用场景是需要缓冲池的场景,比如 String 常量池、数据库连接池
代理模式
代理模式 (Proxy)
代理模式的基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 理模式有不同的形式,主要有三种 静态代理、动态代理(JDK代理、接口代理)和 Cglib 代理(可以在内存动态的创建对象,而不需要实现接口,他是属于动态代理的范畴)。
- 代理模式示意图
静态代理
静态代码模式的基本介绍
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
应用实例
- 定义一个接口: ITeacherDao
- 目标对象 TeacherDAO 实现接口 ITeacherDAO
- 使用静态代理方式,就需要在代理对象 TeacherDAOProxy 中也实现 ITeacherDAO
- 调用的时候通过调用代理对象的方法来调用目标对象.
- 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
静态代理优缺点
- 优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
- 一旦接口增加方法,目标对象与代理对象都要维护
动态代理
动态代理模式的基本介绍
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK 代理、接口代理
JDK 中生成代理对象的 API
- 代理类所在包 :java.lang.reflect.Proxy
- JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
动态代理应用实例
将前面的静态代理类改进成动态代理模式(即:JDK 代理模式)
Cglib 代理
Cglib 代理模式的基本介绍
- 静态代理 和 JDK代理模式 都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib代理
- Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理。
- Cglib 是一个**强大的高性能的代码生成包,**它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如 SpringAOP,实现方法拦截
- 在AOP编程中如何选择代理模式:
1.目标对象需要实现接口,用 JDK 代理
2.目标对象不需要实现接口,用 Cglib 代理 - Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
Cglib代理模式实现步骤
- 引入 Jar 包依赖, Maven的话直接加依赖就好了
- 在内存中动态构建子类,注意代理的类不能为 final,否则报错 java.lang.IllegalArgumentException
- 目标对象的方法如果为 final/static, 那么就不会被拦截,即不会执行目标对象额外的业务方法.
Cglib 代理模式应用实例
public class Client {
public static void main(String[] args) {
// 创建目标对象
TeacherDao target = new TeacherDao();
// 获取到代理对象,并且将目标对象传递给代理对象
Teacher proxyInstance = (TeacherDao) new ProxyFactory(target).getProxyInstance();
// 执行代理对象的方法,触发 intecept 方法,从而实现 对目标对象的调用
String res = proxyInstance.teach();
System.out.println("res=" + res);
}
}
public class ProxyFactory implements MethodInterceptor {
// 维护一个目标对象
private Object target;
// 构造器,传入一个被代理的对象
public ProxyFactory(Object target) {
this.target = target;
}
// 返回一个代理对象:是target 对象的代理对象
// 1.创建一个工具类
Enhancer enhancer = new Enhancer();
// 2. 设置父类
enhancer.setSuperclass(target.getClass());
// 3. 设置回调函数
enhancer.setCallback(this);
// 4. 创建子类对象,即代理对象
return enhancer.create();
}
// 重写 intercept 方法,会调用目标对象的方法
@Override
public Object intercept(Object arg(), Method method, Object[] args, MethodProxy arg3) throws Throwable {
System.out.println("Cglib 代理模式 ~~ 开始“);
Object returnVal = method.invoke(target, args);
System.out.println("Cglib 代理模式 ~~~提交");
return returnVal;
}
public class TeacherDao {
public Stirng teach() {
System.out.println("老师授课中,我是 cglib 代理, 不需要实现接口”);
return "hello";
}
}
几种常见的代理模式介绍一几种变体
- 防火墙代理
- 内网通过代理穿透防火墙,实现对公网的访问。
- 缓存代理
- 比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok,如果取不到资源,再到公网或者数据库取,然后缓存。
- 远程代理
- 远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
- 同步代理:主要使用在多线程编程中,完成多线程间同步工作
模板方法模式
豆浆制作问题
- 制作豆浆的流程选材—>添加配料—>浸泡—>放到豆浆机打碎
- 过添加不同的配料,可以制作出不同口味的豆浆
- 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
- 请使用模板方法模式完成(说明:因为模板方法模式,比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式)
模板方法模式基本介绍
- 模板方法模式(Template Method Pattern),又叫模板模式(Template Patterm),z在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
- 简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
- 这种类型的设计模式属于行为型模式。
模板方法模式原理类图
- 对原理类图的说明-即(模板方法模式的角色及职责)
- AbstractClass 抽象类,类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现其它的抽象方法 operationr2,3,4
- ConcreteClass 实现抽象方法 operationr2,3,4,以完成算法中特点子类的步骤
模板方法模式解决豆浆制作问题
- 应用实例要求
编写制作豆浆的程序,说明如下:
制作豆浆的流程选材—>添加配料—>浸泡–>放到豆浆机打碎
通过添加不同的配料,可以制作出不同口味的豆浆
选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的(红豆、花生豆浆。。。) - 思路分析和图解(类图)
模板方法模式在 Spring 框架应用的源码分析
- Spring IOC 容器初始化时运用到的模板方法模式
- 代码分析+角色分析+说明类图
模板方法模式的注意事项和细节
- 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
- 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步
骤的实现。 - 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
- 一般模板方法都加上 final关键字,防止子类重写模板方法.
- 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理
命令模式
智能生活项目需求
- 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装 app 就可以控制对这些家电工作。
- 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个 App,分别控制,我们希望只要一个 app就可以控制全部智能家电。
- 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给 app 调用,这时 就可以考虑使用命令模式。
- 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来
- 在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品
命令模式基本介绍
- 命令模式(CommandPattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
- 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
- 命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
- 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。Invoker 是调用者(将军),Receiver 是被调用者(士兵),MyCommand 是命令,实现了 Command 接口,持有接收对象
命令模式的原理类图
- 对原理类图的说明-即(命名模式的角色及职责)
- Invoker 是调用者角色
- Command: 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
- Receiver: 接受者角色,知道如何实施和执行一个请求相关的操作
- ConcreteCommand: 将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现 execute
命令模式解决智能生活项目
- 编写程序,使用命令模式 完成前面的智能家电项目
- 思路分析和图解
命令模式在 Spring 框架 JdbcTemplate 应用的源码分析
- Spring 框架的 JdbcTemplate 就使用到了命令模式
- Spring 框架的 JdbcTemplate 就使用到了命令模式 代码分析
- 模式角色分析说明
- StatementCallback 接口,类似命令接口(Command)
- class QueryStatementCallback implements StatementCallback,SqlProvider,匿名内部类,实现了命令接口,同时也充当命令接收者
- 命令调用者 是 JdbcTemplate,其中 execute(StatementCallbackaction)方法中,调用 action.dolnStatement 方法.不同的实现 StatementCallback 接口的对象,对应不同的 doInStatemnt 实现逻辑
- 另外实现 StatementCallback 命令接口的子类还有 QueryStatementCallback、
命令模式的注意事项和细节
- 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的executeO方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
- 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
- 容易实现对请求的撤销和重做
- **命令模式不足:**可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
- 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
- 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟 CMD(DOS命令)订单的撤销/恢复、触发-反馈机制
访问者模式
测评系统的需求
- 将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如成功、失败 等)
传统方式的问题分析
- 如果系统比较小,还是ok的,但是考虑系统增加越来越多新的功能时,对代码改动较大,违反了ocp 原则,不利于维护
- 扩展性不好,比如 增加了 新的人员类型,或者管理方法,都不好做
- 引出我们会使用新的设计模式- 访问者模式
访问者模式基本介绍
- 访问者模式(VisitorPattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
- 主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题
- 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
- 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决.
- Visitor是抽象访问者,为该对象结构中的 ConcreteElement 的每一个类声明一个visit操作
- ConcreteVisitor:是一个具体的访问值实现每个有 Visitor 声明的操作,是每个操作实现的部分
- ObjectStructure 能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素
- Element 定义一个accept 方法,接收一个访问者对象
- ConcreteElement 为具体元素,实现了accept 方法
访问者模式应用实例
- 将人分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如成功、失败等),请使用访问者模式来说实现
- 思路分析和图解(类图)
// 说明
//1. 这里我们使用了 双分派, 即首先在客户端程序中,将具体状态作为参数传递 Woman 中(第一次分派)
//2. 然后 Woman 类调用作为参数的"具体方法" 中方法 getWoamnResult, 同时将自己(this)作为参数
// 传入,完成第二次的分派
public class Woman extends Person {
@Override
public void accept(Action action) {
action.getWomanResult(this);
}
}
上面提到了双分派,所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型
- 以上述实例为例,**假设我们要添加一个 Wait 的状态类,考察 Man 类和 Woman类 的反应,**由于使用了双分派,只需增加一个 Action 子类即可在客户端调用即可,不需要改动任何其他类的代码。
访问者模式的注意事项和细节
- 优点
- 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
- 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统
- 缺点
- 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的,这样造成了具体元素变更比较困难
- 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
- 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的.
迭代器模式
看一个具体的需求
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,
一个学院有多个系。如图:
传统的设计方案
传统的方式的问题分析
- 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
- 实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系,因此这种方案,不能很好实现的遍历的操作
- 解决方案:=>选代器模式
迭代器模式基本介绍
- 代器模式(IteratorPattern)是常用的设计模式,属于行为型模式
- 如果我们的集合元素是用不同的方式实现的,有数组,还有 java 的集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
- 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道**集合对象的底层表示,**即:不暴露其内部的结构。
选代器模式的原理类图
对原理类图的说明-即(迭代器模式的角色及职责)
- Iterator:选代器接口,是系统提供,含义 hasNext, next,r emove
- ConcreteIterator:具体的选代器类,管理迭代
- Aggregate:一个统一的聚合接口,将客户端和具体聚合解耦
- ConcreteAggreage:具体的聚合持有对象集合,并提供一个方法,返回一个迭代器,该迭代器可以正确遍历
集合 - Client:客户端,通过 Iterator 和 Aggregate 依赖子类
应用实例要求
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。
迭代器模式在 JDK-ArrayList 集合应用的源码分析
- JDK 的 ArrayList 集合中就使用了迭代器模式
- 代码分析+类图+说明
对类图的角色分析和说明
- 内部类 Itr 充当具体实现迭代器 Iterator 的类,作为 ArrayList 内部类
- List 就是充当了聚合接口,含有一个iterator()方法,返回一个迭代器对象
- ArrayList 是实现聚合接口 List 的子类,实现了iterator()
- Iterator 接口系统提供
- 选代器模式解决了不同集合 (ArrayList, LinkedList)统一遍历问题
19.8迭代器模式的注意事项和细节
-
优点
1)提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。2)隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。
3)提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。在聚合类中,我们把选代器分开,就是要把管理 对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。
4)当要展示一组相似对象,或者遍历一组相同对象时使用,适合使用迭代器模式
-
缺点
每个聚合对象都要一个迭代器,会生成多个选代器不好管理类
第20章观察者模式
20.1 天气预报项目需求,具体要求如下:
1)气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
2)需要设计开放型 API,便于其他第三方也能接入气象站获取数据。
3)提供温度、气压和湿度的接口
4)测量数据更新时,要能实时的通知给第三方
天气预报设计方案1-普通方案
WeatherData 类
问题分析
-
其他第三方接入气象站获取数据的问题
-
无法在运行时动态的添加第三方 (新浪网站)
-
违反 ocp 原则=>观察者模式
//在 WeatherData 中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到dataChange,不利于维护,也不是动态加入
public void dataChangeO {
currentConditions.update(getTemperatureO), getPressureO, getHumidityO);
}
观察者模式原理
-
观察者模式类似订牛奶业务
-
奶站/气象局:Subject
-
用户/第三方网站:Observer
Subject:登记注册、移除和通知
-
registerObserver 注册
-
removeObserver 移除
-
notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送,看具体需求定
Observer:接收输入
观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为 Subject,依赖的对象为 Observer,Subject
通知 Observer 变化,比如这里的奶站是 Subject,是 1 的一方。用户时 Observer,是多的一方。
观察者模式解决天气预报需求
20.4.1类图说明
观察者模式的好处
1)观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
2)这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类 WeatherData不会修改代码,
遵守了ocp 原则。
观察者模式在 Jdk 应用的源码分析
1)Jdk 的 Observable 类就使用了观察者模式
2)代码分析+模式角色分析
3)模式角色分析
- Observable 的作用和地位等价于 我们前面讲过 Subject
- Observable 是类,不是接口,类中已经实现了核心的方法,即管理 Observer 的方法 add…delete… notif…
- Observer 的作用和地位等价于我们前面讲过的 Observer,有 update
- Observable 和 Observer 的使用方法和前面讲过的一样,只是 Observable 是类,通过继承来实现观察者模式
第 21 章中介者模式
21.1智能家庭项目
1)智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘等
2)主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流程为:闹铃响起->咖啡机开始做咖啡->窗帘自动落下->电视机开始播放
传统方案解决智能家庭管理问题
21.3 传统的方式的问题分析
1)当各电器对象有多种状态改变时,相互之间的调用关系会比较复杂
2)各个电器对象彼此联系,你中有我,我中有你,不利于松耦合.
3)各个电器对象之间所传递的消息(参数),容易混乱
4)当系统增加一个新的电器对象时,或者执行流程改变时,代码的可维护性、扩展性都不理想考虑中介者模式
21.4 中介者模式基本介绍
基本介绍
1)中介者模式(MediatorPattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其**耦合松散,**而且可以独立地改变它们之间的交互
2)中介者模式属于行为型模式,使代码易于维护
3)比如 MVC模式,C(Controller控制器)是 M(Model模型)和 V(View视图)的中介者,在前后端交互时起到了中间人的作用
21.5 中介者模式的原理类图
对原理类图的说明-即(中介者模式的角色及职责)
1)Mediator 就是抽象中介者,定义了同事对象到中介者对象的接口
2)Colleague 是抽象同事类
3)ConcreteMediator 具体的中介者对象,实现抽象方法,他需要知道所有的具体的同事类,即以一个集合来管理 HashMap,并接受某个同事对象消息,完成相应的任务
4)ConcreteColleague 具体的同事类,会有很多,每个同事只知道自己的行为,而不了解其他同事类的行为(方法),但是他们都依赖中介者对象
21.6 中介者模式应用实例-智能家庭管理
1)应用实例要求
完成前面的智能使用中介者模式
21.7 中介者模式的注意事项和细节
1)多个类相互耦合,会形成网状结构,使用中介者模式将网状结构分离为星型结构,进行解耦
2)减少类间依赖,降低了耦合,符合迪米特原则
3)中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
4)如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意
第22章 备忘录模式
22.1 游戏角色状态恢复问题
游戏角色有攻击力和防御力,在大战 Boss 前保存自身的状态(攻击力和防御力),当大战Boss 后攻击力和防御力下降,从备忘录对象恢复到大战前的状态
22.2 传统方案解决游戏角色恢复
22.3传统的方式的问题分析
1)一个对象,就对应一个保存对象状态的对象,这样当我们游戏的对象很多时,不利于管理,开销也很大.
2)传统的方式是简单地做备份,new 出另外一个对象出来,再把需要备份的数据放到这个新对象,但这就暴露了对象内部的细节
3)解决方案:=> 备忘录模式
22.4备忘录模式基本介绍
基本介绍
1)备忘录模式(Memento Pattermn)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
2)可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
3)备忘录模式属于行为型模式
22.5 备忘录模式的原理类图
对原理类图的说明-即(备忘录模式的角色及职责)
1)originator:对象(需要保存状态的对象)
2)Memento:备忘录对象,负责保存好记录,即Originator内部状态
3)Caretaker: 守护者对象,负责保存多个备忘录对象,使用集合管理,提高效率
4)说明:如果希望保存多个 originator 对象的不同时间的状态,也可以,只需要要 HashMap<String,集合>
22.6 游戏角色恢复状态实例
1)应用实例要求
游戏角色有攻击力和防御力,在大战 Boss 前保存自身的状态(攻击力和防御力),当大战 Boss 后攻击力和防御力下降,从备忘录对象恢复到大战前的状态
22.7 备忘录模式的注意事项和细节
1)给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
2)实现了信息的封装,使得用户不需要关心状态的保存细节
3)如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存,这个需要注意
4)适用的应用场景:1、后悔药。2、打游戏时的存档。3、Windows 里的 ctri + z。4、IE 中的后退。4、数据库的事务管理
5)为了节约内存,备忘录模式可以和原型模式配合使用
第23章解释器模式
23.1四则运算问题
通过解释器模式来实现四则运算,如计算a + b - c 的值,具体要求
1)先输入表达式的形式,比如 a + b + c - d + e,要求表达式的字母不能重复
2)在分别输入 a, b, c, d, e 的值
3)最后求出结果:如图
23.2 传统方案解决四则运算问题分析
1)编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果
2)问题分析:如果加入新的运算符,比如 * / (等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,不够清晰.
3)解决方案:可以考虑使用解释器模式,即:表达式 -> 解释器(可以有多种)->结果
23.3 解释器模式基本介绍
基本介绍
1)在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
2)解释器模式(Interpreter Patterm):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
3)应用场景
-应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
-一些重复出现的问题可以用一种简单的语言来表达
一个简单语法需要解释的场景
4)这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等
23.4 解释器模式的原理类图
对原理类图的说明-即 (解释器模式的角色及职责)
1)Context: 是环境角色,含有解释器之外的全局信息.
2)AbstractExpression: 抽象表达式,声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享
3)TerminalExpression: 为终结符表达式,实现与文法中的终结符相关的解释操作
4)NonTermialExpression: 为非终结符表达式,为文法中的非终结符实现解释操作.
5)说明:输入 Context he TerminalExpression 信息通过 Client 输入即可
23.5 解释器模式来实现四则
1)应用实例要求
通过解释器模式来实现四则运算,
如计算 a+b-c 的值
23.6 解释器模式在 Spring 框架应用的源码剖析
- Spring框架中 SpelExpressionParser 就使用到解释器模式
- 代码分析+Debug源码
23.7 解释器模式的注意事项和细节
1)当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
2)应用场景:编译器、运算表达式计算、正则表达式、机器人等
3)使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低.
第24章状态模式
24.1 APP抽奖活动问题
请编写程序完成 APP 抽奖活动具体要求如下:
1)假如每参加一次这个活动要扣除用户 50 积分,中奖概率是 10%
2)奖品数量固定,抽完就不能抽奖
3)活动有四个状态:可以抽奖、不能抽奖、发放奖品和奖品领完
4)活动的四个状态转换关系图(右图)
24.2状态模式基本介绍
基本介绍
1)状态模式**(State Pattern)**:它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
2)当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
24.3 状态模式的原理类图
对原理类图的说明-即(状态模式的角色及职责)
1)Context 类为环境角色,用于维护 State 实例,这个实例定义当前状态
2)State 是抽象状态角色,定义一个接口封装与 Context 的一个特点接口相关行为
3)ConcreteState 具体的状态角色,每个子类实现一个与 Context 的一个状态相关行为
24.4 状态模式解决 APP 抽奖问
1)应用实例要求
完成 APP 抽奖活动项目,使用状态模式.
2)思路分析和图解(类图)
- 定义出一个接口叫状态接口,每个状态都实现它。
- 接口有扣除积分方法、抽奖方法、发放奖品方法
24.5状态模式在实际项目-借贷平台源码剖析
1)借贷平台的订单,有审核-发布-抢单等等步骤,随着操作的不同,会改变订单的状态,项目中的这个模块实
现就会使用到状态模式
2)通常通过 if/else 判断订单的状态,从而实现不同的逻辑,伪代码如下
24.6 状态模式的注意事项和细节
1)代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
2)方便维护。将容易产生问题的 if-else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,而且容易出错
3)符合“开闭原则”。容易增删状态
4)会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
5)应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式
第25章策略模式
25.1 编写鸭子项目,具体要求如下:
1)有各种鸭子(比如 野鸭、北京鸭、水鸭等,鸭子有各种行为,比如 叫、飞行等)
2)显示鸭子的信息
25.2 传统方案解决鸭子问题的分析和代码实现
1)传统的设计方案(类图)
25.3传统的方式实现的问题分析和解决方案
1)其它鸭子,都继承了Duck类,所以 fly 让所有子类都会飞了,这是不正确的
2)上面说的 1 的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有溢出效应
3)为了改进 1 问题,我们可以通过覆盖 fly 方法来解决 => 覆盖解决
4)问题又来了,如果我们有一个玩具鸭子 ToyDuck, 这样就需要 ToyDuck 去覆盖 Duck 的所有实现的方法=>解决思路 -> 策略模式 (strategy pattern)
25.4 策略模式基本介绍
1)策略模式(StrategyPattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
2)这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
说明:从上图可以看到,客户 context 有成员变量 strategy 或者其他的策略接口,至于需要使用到哪个策略,我们可以在构造器中指定
25.6 策略模式解决鸭子问题
1)应用实例要求
编写程序完成前面的鸭子项目,要求使用策略模式
2)思路分析(类图)
策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者
25.8 策略模式的注意事项和细节
1)策略模式的关键是:分析项目中变化部分与不变部分
2)策略模式的核心思想是:多用组合/聚合少用继承;用行为类组合,而不是行为的继承。更有弹性
3)体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if.elseif…else)
4)提供了可以替换继承关系的办法:策略模式将算法封装在独立的 Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
**5)需要注意的是:**每添加一个策略就要增加一个类,当策略过多是会导致类数目庞
第26章职责链模式
26.1 学校 OA 系统的采购审批项目:需求是
采购员采购教学器材
1)如果金额小于等于5000,由教学主任审批(0<=x<=5000)
2)如果金额小于等于10000,由院长审批(5000<x<=10000)
3)如果金额小于等于30000,由副校长审批(10000<x<=30000)
4)如果金额超过30000以上,有校长审批(30000<x)
请设计程序完成采购审批项目
26.2传统方案解决 OA 系统审批,传统的设计方案(类图)
26.3 传统方案解决 OA 系统审批问题分析
1)传统方式是:接收到一个采购请求后,根据采购金额来调用对应的 Approver(审批人)完成审批。
2)传统方式的问题分析:客户端这里会使用到分支判断(比如 switch)来对不同的采购请求处理,这样就存在如下问题(1)如果各个级别的人员审批金额发生变化,在客户端的也需要变化(2)客户端必须明确的知道有多少个审批级别和访问
3)这样对一个采购请求进行处理和 Approver(审批人)就存在强耦合关系,不利于代码的扩展和维护
4)解决方案=》职责链模式
26.4 职责链模式基本介绍
基本介绍
1)职责链模式(Chain ofResponsibilityPattern),又叫责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
2)职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
3)这种类型的设计模式属于行为型模式
26.5职责链模式的原理类图
对原理类图的说明-即(职责链模式的角色及职责)
1)Handler:抽象的处理者,定义了一个处理请求的接口,同时含义另外 Handler
2)ConcreteHandler A, B 是具体的处理者,处理它自己负责的请求,可以访问它的后继者(即下一个处理者),如果可以处理当前请求,则处理,否则就将该请求交个后继者去处理,从而形成一个职责链
3)Request,含义很多属性,表示一个请求
26.6 职责链模式解决 OA 系统采购审批
应用实例要求
编写程序完成学校OA系统的采购审批项目:需求、
采购员采购教学器材
如果金额小于等于5000,由教学主任审批
如果金额页小于等于10000,由院长审批
如果金额小于等于30000,由副校长审批
如果金额超过30000以上,有校长审批
2)思路分析和图解(类图)
5)对源码总结
-
springmvc 请求的流程图中,执行了拦截器相关方法 interceptor, preHandler 等等
-
在处理 SpringMvc 请求时,使用到职责链模式还使用到适配器模式
-
SHandlerExecutionChain 主要负责的是请求拦截器的执行和请求处理,但是他本身不处理请求,只是将请求分配给链上注册处理器执行,这是职责链实现方式,减少职责链本身与处理逻辑之间的耦合,规范了处理流程
-
HandlerExecutionChain 维护了HandlerInterceptor 的集合,可以向其中注册相应的拦截器.
26.8 职责链模式的注意事项和细节
1)将请求和处理分开,实现解耦,提高系统的灵活性
2)简化了对象,使对象不需要知道链的结构
3)性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在 Handler 中设置一个最大节点数量,在 setNext() 方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
4)调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
5)最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、JavaWeb 中 Tomcat 对 Encoding 的处理、拦截器
行等)
2)显示鸭子的信息
25.2 传统方案解决鸭子问题的分析和代码实现
1)传统的设计方案(类图)
[外链图片转存中…(img-9AMkZDLL-1728524773666)]
25.3传统的方式实现的问题分析和解决方案
1)其它鸭子,都继承了Duck类,所以 fly 让所有子类都会飞了,这是不正确的
2)上面说的 1 的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有溢出效应
3)为了改进 1 问题,我们可以通过覆盖 fly 方法来解决 => 覆盖解决
4)问题又来了,如果我们有一个玩具鸭子 ToyDuck, 这样就需要 ToyDuck 去覆盖 Duck 的所有实现的方法=>解决思路 -> 策略模式 (strategy pattern)
25.4 策略模式基本介绍
1)策略模式(StrategyPattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
2)这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
[外链图片转存中…(img-WDAXVFrg-1728524773666)]
说明:从上图可以看到,客户 context 有成员变量 strategy 或者其他的策略接口,至于需要使用到哪个策略,我们可以在构造器中指定
25.6 策略模式解决鸭子问题
1)应用实例要求
编写程序完成前面的鸭子项目,要求使用策略模式
2)思路分析(类图)
策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者
[外链图片转存中…(img-Yf0xn640-1728524773666)]
[外链图片转存中…(img-DF9Sa1Zx-1728524773666)]
25.8 策略模式的注意事项和细节
1)策略模式的关键是:分析项目中变化部分与不变部分
2)策略模式的核心思想是:多用组合/聚合少用继承;用行为类组合,而不是行为的继承。更有弹性
3)体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if.elseif…else)
4)提供了可以替换继承关系的办法:策略模式将算法封装在独立的 Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
**5)需要注意的是:**每添加一个策略就要增加一个类,当策略过多是会导致类数目庞
第26章职责链模式
26.1 学校 OA 系统的采购审批项目:需求是
采购员采购教学器材
1)如果金额小于等于5000,由教学主任审批(0<=x<=5000)
2)如果金额小于等于10000,由院长审批(5000<x<=10000)
3)如果金额小于等于30000,由副校长审批(10000<x<=30000)
4)如果金额超过30000以上,有校长审批(30000<x)
请设计程序完成采购审批项目
26.2传统方案解决 OA 系统审批,传统的设计方案(类图)
[外链图片转存中…(img-hLgSTWHl-1728524773666)]
26.3 传统方案解决 OA 系统审批问题分析
1)传统方式是:接收到一个采购请求后,根据采购金额来调用对应的 Approver(审批人)完成审批。
2)传统方式的问题分析:客户端这里会使用到分支判断(比如 switch)来对不同的采购请求处理,这样就存在如下问题(1)如果各个级别的人员审批金额发生变化,在客户端的也需要变化(2)客户端必须明确的知道有多少个审批级别和访问
3)这样对一个采购请求进行处理和 Approver(审批人)就存在强耦合关系,不利于代码的扩展和维护
4)解决方案=》职责链模式
26.4 职责链模式基本介绍
基本介绍
1)职责链模式(Chain ofResponsibilityPattern),又叫责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
2)职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
3)这种类型的设计模式属于行为型模式
26.5职责链模式的原理类图
[外链图片转存中…(img-gC6Tpry5-1728524773666)]
对原理类图的说明-即(职责链模式的角色及职责)
1)Handler:抽象的处理者,定义了一个处理请求的接口,同时含义另外 Handler
2)ConcreteHandler A, B 是具体的处理者,处理它自己负责的请求,可以访问它的后继者(即下一个处理者),如果可以处理当前请求,则处理,否则就将该请求交个后继者去处理,从而形成一个职责链
3)Request,含义很多属性,表示一个请求
26.6 职责链模式解决 OA 系统采购审批
应用实例要求
编写程序完成学校OA系统的采购审批项目:需求、
采购员采购教学器材
如果金额小于等于5000,由教学主任审批
如果金额页小于等于10000,由院长审批
如果金额小于等于30000,由副校长审批
如果金额超过30000以上,有校长审批
2)思路分析和图解(类图)
[外链图片转存中…(img-iITaGVPl-1728524773667)]
[外链图片转存中…(img-h9lQXj71-1728524773667)]
5)对源码总结
-
springmvc 请求的流程图中,执行了拦截器相关方法 interceptor, preHandler 等等
-
在处理 SpringMvc 请求时,使用到职责链模式还使用到适配器模式
-
SHandlerExecutionChain 主要负责的是请求拦截器的执行和请求处理,但是他本身不处理请求,只是将请求分配给链上注册处理器执行,这是职责链实现方式,减少职责链本身与处理逻辑之间的耦合,规范了处理流程
-
HandlerExecutionChain 维护了HandlerInterceptor 的集合,可以向其中注册相应的拦截器.
26.8 职责链模式的注意事项和细节
1)将请求和处理分开,实现解耦,提高系统的灵活性
2)简化了对象,使对象不需要知道链的结构
3)性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在 Handler 中设置一个最大节点数量,在 setNext() 方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
4)调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
5)最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、JavaWeb 中 Tomcat 对 Encoding 的处理、拦截器
标签:万字,Java,对象,void,模式,class,接口,设计,public From: https://blog.csdn.net/2301_79083000/article/details/142813297