第四部分 设计模型

前言

设计模式的目的是 为了让软件/代码/程序
1)低耦合、高内聚、
2)可扩展性/维护性(当需要增加需求时,非常方便)、
3)可复用性、
4)可读性(即规范性,便于其他程序员的阅读和理解)、
5)可靠性(当新增加功能后,对原有的功能没有影响)
6)提高效率、
7)灵活性、

设计模式在软件中哪里?
面向对象(oo)=>功能模块[设计模式+算法(数据结构)]=>框架[使用到多种设计模式]=>架构 [服务器集群]

一、设计模式常用的七大原则

  1. 单一职责原则
  2. 接口隔离原则
  3. 依赖倒转(倒置)原则
  4. 里氏替换原则
  5. 开闭原则 ocp
  6. 迪米特法则
  7. 合成复用原则

1.单一职责原则

基本介绍

当一个职责发生变化后,不会影响另一个职责。对类来说的,即一个类应该只负责一项职责
e.g. 如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为 A1,A2

class Vehicle: 违反单一原则,需要分解成不同的类->
class RoadVehicle, class AirVehicleclass WaterVehicle:类进行分解,但改动很大,会同时修改动客户端  ->
class Vehicle{没在原来类上进行过大修改,在方法级别遵守单一职责
		public void runRoad() {}
		public void runAir()  {}
		public void runWater(){}
		}

单一职责原则注意事项和细节

  1. 降低类的复杂度,一个类只负责一项职责。
  2. 提高类的可读性,可维护性
  3. 降低变更引起的风险
  4. 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则

2.接口隔离原则

概念

一个类通过接口去依赖另一个类时,把用不上的接口隔离开来 即把接口拆开。

3.依赖倒转(倒置)原则

基本介绍

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

依赖倒转原则的注意事项和细节

  1. 低层模块尽量都要有抽象类或接口,或者他的上一类有,或者两者都有,程序稳定性更好。 (不要有一个类孤零零在哪里 除非迫不得已)
  2. 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
    比如,如果有个obj直接指向对象的实例 (直接应用对象的地址)以后对指向的类进行功能的扩展是不可能的
  3. 继承时遵循里氏替换原则↓

依赖倒转原则

依赖关系传递的三种方式和应用案例
1) 接口传递
2) 构造方法传递
3) setter方式传递

应用实例

  1. 请编程完成Person 接收消息 的功能。
class Email {
	public String getInfo() {
		return "电子邮件信息: hello,world";
		}
	}
	//完成 Person 接收消息的功能
	//方式 1 分析
	//1. 简单,比较容易想到
	//2. 如果我们获取的对象是 微信,短信等等,则新增类,同时 Perons 也要增加相应的接收方法
	//3. 解决思路:引入一个抽象的接口 IReceiver, 表示接收者, 这样 Person 类与接口 IReceiver 发生依赖
	// 因为 Email, WeiXin 等等属于接收的范围,他们各自实现 IReceiver 接口就 ok, 这样我们就符号依赖倒转原则
	class Person {
		public void receive(Email email ) {
			System.out.println(email.getInfo());
		}
	}

public class DependecyInversion {
	public static void main(String[] args) {
	//客户端无需改变
	Person person = new Person();
	person.receive(new Email());
	person.receive(new WeiXin());
	}
}
//定义接口
interface IReceiver {
	public String getInfo();
}
class Email implements IReceiver {
	public String getInfo() {
		return "电子邮件信息: hello,world";
	}
}
//增加微信
class WeiXin implements IReceiver {
	public String getInfo() {
		return "微信信息: hello,ok";
	}
	}
	//方式 2
	class Person {
	//这里我们是对接口的依赖
	public void receive(IReceiver receiver ) {
		System.out.println(receiver.getInfo());
	}
}

依赖关系传递的三种方式和应用案例
1) 接口传递
2) 构造方法传递
3) setter方式传递

public class DependencyPass {
public static void main(String[] args) {
// TODO Auto-generated method stub
ChangHong changHong = new ChangHong();
// OpenAndClose openAndClose = new OpenAndClose();
// openAndClose.open(changHong);
//通过构造器进行依赖传递
// OpenAndClose openAndClose = new OpenAndClose(changHong);
// openAndClose.open();
//通过 setter 方法进行依赖传递
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.setTv(changHong);
openAndClose.open();
}
}
// 方式 1: 通过接口传递实现依赖
// 开关的接口
// interface IOpenAndClose {
// public void open(ITV tv); //抽象方法,接收接口
// }
//
// interface ITV { //ITV 接口
// public void play();
// }
//
// class ChangHong implements ITV {
//
// @Override
// public void play() {
// // TODO Auto-generated method stub
// System.out.println("长虹电视机,打开");
// }
//
// }
 实现接口
// class OpenAndClose implements IOpenAndClose{
// public void open(ITV tv){
// tv.play();
// }
// }
// 方式 2: 通过构造方法依赖传递
// interface IOpenAndClose {
// public void open(); //抽象方法
// }
// interface ITV { //ITV 接口
// public void play();
// }
// class OpenAndClose implements IOpenAndClose{
// public ITV tv; //成员
// public OpenAndClose(ITV tv){ //构造器
// this.tv = tv;
// }
// public void open(){
// this.tv.play();
// }
// }
// 方式 3 , 通过 setter 方法传递
interface IOpenAndClose {
public void open(); // 抽象方法
public void setTv(ITV tv);
}
interface ITV { // ITV 接口
public void play();
}
class OpenAndClose implements IOpenAndClose {
private ITV tv;
public void setTv(ITV tv) {
this.tv = tv;
}
public void open() {
this.tv.play();
}
}
class ChangHong implements ITV {
@Override
public void play() {
// TODO Auto-generated method stub
System.out.println("长虹电视机,打开");
}

4.里氏替换原则

OO 中的继承性的思考和说明

  1. 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
  2. 继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障
  3. 问题提出:在编程中,如何正确的使用继承? => 里氏替换原则

继承导致可复用性低(子改父方法),侵入性等问题(子改父方法)

基本介绍

  1. 里氏替换原则(Liskov Substitution Principle)在 1988 年,由麻省理工学院的以为姓里的女士提出的。
  2. 如果对每个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
  3. 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
  4. 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖 来解决问题。

问题:
我们发现原来运行正常的相减功能发生了错误。原因就是类 B 无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候

解决方法

1)通用的做法是:原来的父类A和子类B都继承一个更通俗的基类原有的继承关系去掉,采用依赖,聚合,组合等关系代替。
第四部分 设计模型_第1张图片
2)B想用A的方法也可以在B类中创建A的实例对象a,并且用a调用A的方法↓

public static void main(String[] args) {
//使用组合仍然可以使用到 A 类相关方法
	System.out.println("11-3=" + b.func3(11, 3));// 这里本意是求出 11-
}

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);
	}
}

5.开闭原则 ocp

1.基本介绍

  1. 开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则
  2. 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。
  3. 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
  4. 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则
    第四部分 设计模型_第2张图片

2. 看一个画图形的功能。方式一:

public class Ocp {
	public static void main(String[] args) {
	//使用看看存在的问题
		GraphicEditor graphicEditor = new GraphicEditor();
		graphicEditor.drawShape(new Rectangle());
		graphicEditor.drawShape(new Circle());
		graphicEditor.drawShape(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)
			drawTriangle(s);
	}
	//绘制矩形
	public void drawRectangle(Shape r) {
		System.out.println(" 绘制矩形 ");
	}
	//绘制圆形
	public void drawCircle(Shape r) {
		System.out.println(" 绘制圆形 ");
	}
	//绘制三角形
	public void drawTriangle(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;
	}
}

3. 方式 1 的优缺点

  1. 优点是比较好理解,简单易操作。
  2. 缺点是违反了设计模式的 ocp 原则,即对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的
    时候,尽量不修改代码,或者尽可能少修改代码. 3) 比如我们这时要新增加一个图形种类 三角形,我们需要做如下修改,修改的地方较多

4.改进的思路分析

思路:把创建 Shape 类做成抽象类,并提供一个抽象的 draw 方法让子类去实现即可,这样我们有新的图形种类时,只需要让新的图形类继承 Shape,并实现 draw 方法即可,使用方的代码就不需要修 -> 满足了开闭原则

改进后的代码:

public class Ocp {
	public static void main(String[] args) {
		//使用看看存在的问题
		GraphicEditor graphicEditor = new GraphicEditor();
		graphicEditor.drawShape(new Rectangle());
		graphicEditor.drawShape(new Circle());
		graphicEditor.drawShape(new Triangle());
		graphicEditor.drawShape(new OtherGraphic());
	}
}
//这是一个用于绘图的类 [使用方]
class GraphicEditor {
	//接收 Shape 对象,调用 draw 方法
	public void drawShape(Shape s) {
		s.draw();
	}
}

//Shape 类,基类
abstract class Shape {
	int m_type;
	public abstract void draw();//抽象方法
}

class Rectangle extends Shape {
	Rectangle() {
		super.m_type = 1;
	}
	@Override
	public void draw() {
	// TODO Auto-generated method stub
		System.out.println(" 绘制矩形 ");
	}
}

class Circle extends Shape {
	Circle() {
		super.m_type = 2;
	}
	@Override
	public void draw() {
	// TODO Auto-generated method stub
		System.out.println(" 绘制圆形 ");
	}
}

//新增画三角形
class Triangle extends Shape {
	Triangle() {
		super.m_type = 3;
	}
	@Override
	public void draw() {
	// TODO Auto-generated method stub
		System.out.println(" 绘制三角形 ");
	}
}

//新增一个图形
class OtherGraphic extends Shape {
	OtherGraphic() {
		super.m_type = 4;
	}
	@Override
	public void draw() {
	// TODO Auto-generated method stub
		System.out.println(" 绘制其它图形 ");
	}
}

6.迪米特法则

1. 基本介绍

  1. 一个对象应该对其他对象保持最少的了解
  2. 类与类关系越密切,耦合度越大
  3. 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息
  4. 迪米特法则还有个更简单的定义:只与直接的朋友通信
  5. 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变
    量的形式出现在类的内部

2应用实例

  1. 有一个学校,下属有各个学院和总部,现要求打印出学校总部员工 ID 和学院员工的 id
  2. 编程实现上面的功能, 看代码演示
  3. 代码演示
//客户端
public class Demeter1 {
	public static void main(String[] args) {
	//创建了一个 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
//3. 违反了 迪米特法则
		//获取到学院员工
		List<CollegeEmployee> list1 = sub.getAllEmployee();
		System.out.println("------------学院员工------------");
		for (CollegeEmployee e : list1) {
			System.out.println(e.getId());
		}
		//获取到学校总部员工
		List<Employee> list2 = this.getAllEmployee();
		System.out.println("------------学校总部员工------------");
		for (Employee e : list2) {
			System.out.println(e.getId());
		}
	}
}

3应用实例改进

  1. 前面设计的问题在于 SchoolManager 中,CollegeEmployee 类并不是SchoolManager 类的直接朋友 (分析)
  2. 按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合
  3. 对代码按照迪米特法则 进行改进. (看老师演示)
  4. 代码演示
//客户端
public class Demeter1 {
	public static void main(String[] args) {
		System.out.println("~~~使用迪米特法则的改进~~~");
	//创建了一个 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;
	}
	//输出学院员工的信息
	public void printEmployee() {
	//获取到学院员工
		List<CollegeEmployee> list1 = getAllEmployee();
		System.out.println("------------学院员工------------");
		for (CollegeEmployee e : list1) {
			System.out.println(e.getId());
		}
	}
}
	//学校管理类
	//分析 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. 将输出学院的员工方法,封装到 CollegeManager
		sub.printEmployee();
	//获取到学校总部员工
		List<Employee> list2 = this.getAllEmployee();
		System.out.println("------------学校总部员工------------");
		for (Employee e : list2) {
			System.out.println(e.getId());
		}
	}
}

4.迪米特法则注意事项和细节

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

7.合成复用原则*

1.基本介绍

原则是尽量使用合成/聚合(后面有讲)的方式,而不是使用继承
第四部分 设计模型_第3张图片

2. 设计原则核心思想

  1. 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
  2. 针对接口编程,而不是针对实现编程。
  3. 为了交互对象之间的松耦合设计而努力

二、UML图

1. UML基本介绍:

即统一建模语言,用符号描述设计思想。

  1. UML——Unified modeling language UML (统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软
    件开发人员进行思考和记录思路的结果

UML图例:
第四部分 设计模型_第4张图片
2) UML本身是一套符号的规定,就像数学符号和化学符号一样,这些符号用于描述软件模型中的各个元素和他们之间的
关系,比如类、接口、实现、泛化、依赖、组合、聚合等,如右图:
UML符号
note: 对你的UML图进行注释
Class: 表示类,可添加属性和方法
Interface: 表示接口
Dependency: 表示依赖(使用)
Association: 表示关联
Generalization: 表示泛化(继承)
Realization: 表示实现
Aggregation: 聚合
Composite: 组合
第四部分 设计模型_第5张图片
3) 使用UML来建模,常用的工具有 Rational Rose, 也可以使用一些插件来建模

2. UML类图

  1. 用于描述系统中的类(对象)本身的组成和类(对象)之间的各种静态关系。
  2. 类之间的关系:依赖、泛化(继承)、实现、关联、聚合与组合
  3. 类图简单举例

2.1 依赖关系(Dependence)

形成依赖关系的条件:
1)只要是在类中用到了对方。比如,A用到了B,即A依赖B。
2)是类的成员属性
3)是方法的返回类型
4)是方法接收的参数类型
5)方法中使用到
第四部分 设计模型_第6张图片

2.2 泛化关系(Generalization)

  1. 泛化关系实际上就是继承关系,是依赖关系的特例
  2. 如果A类继承了B类,我们就说A和B存在泛化关系

UML类图:第四部分 设计模型_第7张图片

2.3 实现关系(Implementation)

A类实现(implements)B类,是依赖关系的特例

UML类图:
第四部分 设计模型_第8张图片

2.4 关联关系(Association)

1)关联关系实际上就是类与类之间的联系,他是依赖关系的特例
2)关联具有多重性:如“1”(表示有且仅有一个),“0…”(表示0个或者多个),“0,1”(表示0个或者一个),“n…m”(表示n到 m个都可以),“m…*”(表示至少m个)。
3)关联具有导航性:即双向关系或单向关
(1)单向一对一关系

//Person类中有IDCard,IDCard中无Person,即单向一对一
public class Person {
	private IDCard card;
}
public class IDCard{}
(2)双向一对一关系
//Person类中有IDCard,IDCard中有Person,即双向一对一
public class Person {
	private IDCard card;
}
public class IDCard{
	private Person person
}

第四部分 设计模型_第9张图片

2.5 聚合关系 (Aggregation)

聚合关系(Aggregation)表示的是整体和部分的关系,整体与部分可以分开。聚合关系是关联关系的特例。

如果整体与部分可以分开,就是聚合关系;不可以分开的就是组合关系

实例:第四部分 设计模型_第10张图片
UML类图:
第四部分 设计模型_第11张图片

2.6 组合(Composite)

如果整体与部分可以分开,就是聚合关系;不可以分开的就是组合关系

e.g. 创建ComputerClass,mouse和monitor就创建了,ComputerClass销毁了,mouse和monitor就销毁了。所以是不可分开的组合关系

实例:
第四部分 设计模型_第12张图片
UML类图:
第四部分 设计模型_第13张图片

e.g.2 人与头、IDCard的关系:
人与头是组合关系:没有头,人就死了。
人与IDCard是聚合关系:没有卡,可以去补。
(除非:如果在程序中Person实体中定义了对IDCard进行级联删除,即删除Person时连同IDCard一起删除,那么IDCard 和 Person 就是组合了.
PS:级联删除即删除A时必须删除B)

UML类图:
第四部分 设计模型_第14张图片

三、常用的设计模式简介

  • 23种设计模式思想
  • 结合源码描述

1. 介绍

1)设计模式,不是代码,而是某类问题的通用解决方案
2)设计模式本质:提高软件的维护性,通用性和扩展性,并降低软件的复杂度

2. 设计模式分为三种类型,共23种

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

四、设计模式

1. 单例模式(Singleton)

1.1 单例设计模式介绍

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

比如 Hibernate 的 SessionFactory,它充当数据存储源的代理,并负责创建 Session 对象。SessionFactory 并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory 就够,这是就会使用到单例模式。

(?
关键点有4个:
1.私有构造函数
2.声明静态单例对象
3.构造单例之前要加锁
4.需要2次检查单例实例是否为空,分别在锁之前和锁之后
单例模式重点在于在整个系统上共享一些创建时较耗资源的对象。整个应用中只维护一个特定类实例,它被所有组件共同使用。Java.lang.Runtime是单例模式的经典例子。从 Java 5 开始你可以使用枚举(enum)来实现线程安全的单例。

1.2 单例设计模式八种方式

1) 饿汉式(静态常量)
2) 饿汉式(静态代码块)

3) 懒汉式(线程不安全)
4) 懒汉式(线程安全,同步方法)
5) 懒汉式(线程安全,同步代码块)
6) 双重检查
7) 静态内部类
8) 枚举

(ps:加粗的是推荐使用的)

1.2.1 饿汉式(静态常量)

概念

在类装载的时候就完成实例化。

步骤

1) 构造器私有化 (防止 new )
2) 类的内部创建对象
3) 向外暴露一个静态的公共方法。getInstance
4) 代码↓

优缺点说明

  1. 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题
  2. 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
  3. 这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果。(实例创建,但没有用它,懒加载就没有意义了:懒加载没有实现)
  4. 结论:这种单例模式可用,可能造成内存浪费。(保证能用到一次时,可以使用)

饿汉式(静态常量)应用实例:

public class SingletonTest01 {
	public static void main(String[] args) {
	//测试
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2); // true
		System.out.println("instance.hashCode=" + instance.hashCode());
		System.out.println("instance2.hashCode=" + instance2.hashCode());//hashcode也相同,说明这两个实例是同一个对象实例
	}
}

//饿汉式(静态变量)
class Singleton {
	//1. 构造器私有化, 外部能 new
	private Singleton() {
	}
	//2.本类内部创建对象实例
	private final static Singleton instance = new Singleton();
	//3. 提供一个公有的静态方法,返回实例对象:向外暴露一个静态的公共方法。getInstance
	public static Singleton getInstance() {
		return instance;
	}
}

1.2.2 饿汉式(静态代码块)

跟静态常量不一样的地方就是:在静态代码块中,创建单例对象,其他不变
优缺点跟上面是一样的。

public class SingletonTest02 {
	public static void main(String[] args) {
		//测试
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2); // true
		System.out.println("instance.hashCode=" + instance.hashCode());
		System.out.println("instance2.hashCode=" + instance2.hashCode());
	}
}
//饿汉式(静态变量)
class Singleton {
	//1. 构造器私有化, 外部能 new
	private Singleton() {
	}
	//2.本类内部创建对象实例
	private static Singleton instance;
	static { //**跟静态常量不一样的地方就是:在静态代码块中,创建单例对象**
		instance = new Singleton();
	}
	//3. 提供一个公有的静态方法,返回实例对象
	public static Singleton getInstance() {
		return instance;
	}
}

1.2.3 懒汉式(线程不安全)

概念

提供一个静态的公有方法,当使用到getInstance这个方法时,才去创建 instance。即懒汉式

优缺点说明:

  1. 起到了 Lazy Loading 的效果,但是只能在单线程下使用。(可以达到懒加载的效果
  2. 如果在多线程下,一个线程进入了 if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式(线程不安全
  3. 结论:在实际开发中,不要使用这种方式
public class SingletonTest03 {
	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2); // true
		System.out.println("instance.hashCode=" + instance.hashCode());//两次返回的实例是同一个
		System.out.println("instance2.hashCode=" + instance2.hashCode());
		}
	}
class Singleton {
	private static Singleton instance;
	private Singleton() {}
	
	//提供一个静态的公有方法,当使用到getInstance这个方法时,才去创建 instance。**即懒汉式**
	public static Singleton getInstance() {
	if(instance == null) {//如果当前的instance实例为空,说明没创建,这时候才创建它
		instance = new Singleton();
	}
	return instance;
	}
}

1.2.4 懒汉式(线程安全,同步方法)

优缺点说明:

  1. 解决了线程安全问题**(线程安全)**
  2. 效率太低了,每个线程在想获得类的实例时候,执行 getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低。(效率低)
  3. 结论:在实际开发中,不推荐使用这种方式**(因为效率低↑)**
public class SingletonTest04 {
	public static void main(String[] args) {
		System.out.println("懒汉式2 , 线程安全~");
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2); // true
		System.out.println("instance.hashCode=" + instance.hashCode());
		System.out.println("instance2.hashCode=" + instance2.hashCode());
	}
}
// 懒汉式(线程安全,同步方法)
class Singleton {
	private static Singleton instance;
	private Singleton() {}
	//提供一个静态的公有方法,加入同步处理的代码(synchronized),解决线程安全问题
	//即懒汉式
	public static synchronized Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

1.2.5 懒汉式(线程安全,同步代码块)

优缺点说明:

  1. 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的的代码块
  2. 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
  3. 结论:在实际开发中,不能使用这种方式

第四部分 设计模型_第15张图片

1.2.6 双重检查(Double-Check)

优缺点说明:

  1. Double-Check 概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if (singleton == null)检查,这样就可以保证线程安全了。
  2. 这样,实例化代码只用执行一次,后面再次访问时,判断 if (singleton == null),直接 return 实例化对象,也避免的反复进行方法同步.
  3. 线程安全延迟加载/懒加载问题效率较高
  4. 结论:在实际开发中,推荐使用这种单例设计模式
public class SingletonTest06 {
	public static void main(String[] args) {
		System.out.println("双重检查");
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2); // true
		System.out.println("instance.hashCode=" + instance.hashCode());
		System.out.println("instance2.hashCode=" + instance2.hashCode());
	}
}
// 懒汉式(线程安全,同步方法)
class Singleton {//volatile 可以把修改值立刻更新到储存, 相当于synchronized
	private static volatile Singleton instance;
	private Singleton() {}
	
	//提供一个静态的公有方法,加入双重检查代码,
	//解决线程安全问题, 
	//同时解决懒加载问题
	//同时保证了效率, 推荐使用
	
	public static synchronized Singleton getInstance() {
		if(instance == null) {//第一次检查
			synchronized (Singleton.class) {//保证只有一个线程在执行,创建;当另一个线程也进来时,发现(instance != null) 就不会再创建。
				if(instance == null) {//双重检查
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

1.2.7 静态内部类

优缺点说明:

  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。(因为类装载时线程安全的)
  2. 静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 SingletonInstance 类(静态内部类),从而完成 Singleton 的实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,因为在类进行初始化时,别的线程是无法进入的
  4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
  5. 结论:推荐使用.
public class SingletonTest07 {
	public static void main(String[] args) {
		System.out.println("使用静态内部类完成单例模式");
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2); // true
		System.out.println("instance.hashCode=" + instance.hashCode());
		System.out.println("instance2.hashCode=" + instance2.hashCode());
	}
}
// 静态内部类, 推荐使用
class Singleton {//Singleton类被装载时,内部的SingletonInstance不会立刻被装载
	private static volatile Singleton instance;
	//构造器私有化
	private Singleton() {}
	
	//写一个静态内部类,该类中有一个静态属性 Singleton
	private static class SingletonInstance {
		private static final Singleton INSTANCE = new Singleton();
	}
	
	//提供一个静态的公有方法,直接返回 SingletonInstance.INSTANCE
	public static synchronized Singleton getInstance() {
	//当静态内部类SingletonInstance 调用getInstance()时,
	//会导致静态内部类的装载,且只会装载一次,且装载时线程安全
		return SingletonInstance.INSTANCE;
	}
}

1.2.8 枚举enum

优缺点说明

  1. 这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
  2. 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式
  3. 结论:推荐使用
public class SingletonTest08 {
	public static void main(String[] args) {
		Singleton instance = Singleton.INSTANCE;
		Singleton instance2 = Singleton.INSTANCE;
		System.out.println(instance == instance2); //true
		System.out.println(instance.hashCode());
		System.out.println(instance2.hashCode()); //hashcode相同 是同一个对象
		instance.sayOK(); //ok~
	}
}
//使用枚举,可以实现单例, 推荐
enum Singleton {
	INSTANCE; //属性(只有一个属性,保证它是单例)
	public void sayOK() {
		System.out.println("ok~");
	}
}

单例模式在 JDK 应用的源码分析

  1. 我们 JDK 中,java.lang.Runtime 就是经典的单例模式(饿汉式)
  2. 代码分析+Debug 源码+代码说明

1.3 单例模式在 JDK 应用的源码分析

  1. 我们 JDK 中,java.lang.Runtime 就是经典的单例模式(饿汉式)
  2. 代码分析+Debug 源码+代码说明

代码说明:
new了一个Runtime(),静态方法getRuntime()返回一个currentRuntime,最后构造器被实例化。符合饿汉式单例模式的特点。
第四部分 设计模型_第16张图片

1.4 单例模式注意事项和细节说明

  1. 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
  3. 单例模式使用的场景需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)

2. 工厂模式(简单工厂、抽象工厂)

核心功能:根据“需求”生产“产品”
核心思想:解耦“需求”“工厂”和“产品”。
实际上根据业务情景不同分为不同的实现方式。一般分3种:简单工厂、工厂、抽象工厂。
	1. 简单工厂(也叫静态工厂)
	2. 抽象工厂

2.1 简单工厂(SimpleFactory)

传统的方式的优缺点

  1. 优点是比较好理解,简单易操作。
  2. 缺点是违反了设计模式的 ocp 原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码.
  3. 比如我们这时要新增加一个 Pizza 的种类(Pepper 披萨),我们需要做如下修改. 如果我们增加一个 Pizza 类,只要是订购 Pizza 的代码都需要修改.
    第四部分 设计模型_第17张图片

第四部分 设计模型_第18张图片
4) 改进的思路分析
分析:修改代码可以接受,但是如果我们在其它的地方也有创建 Pizza 的代码,就意味着,其他地方也需要修改,而创建 Pizza
的代码,往往有多处。
思路:把创建 Pizza 对象封装到一个类中,这样我们有新的 Pizza 种类时,只需要修改该类就可,其它有创建到 Pizza
对象的代码就不需要修改了.-> 简单工厂模式

1. 基本介绍

  1. 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品
    类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
  2. 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
  3. 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式.

2. 使用简单工厂模式

  1. 简单工厂模式的设计方案: 定义一个可以实例化 Pizaa 对象的类,封装创建对象的代
    第四部分 设计模型_第19张图片

2.2 工厂方法模式(Factory)

1. 看一个新的需求

披萨项目新的需求:客户在点披萨时,可以点不同地方不同口味的披萨,比如 北京的奶酪 pizza、北京的胡椒 pizza 或者是伦敦的奶酪 pizza、伦敦的胡椒 pizza。

思路 1
使用简单工厂模式,创建不同的简单工厂类,比如 BJPizzaSimpleFactory、LDPizzaSimpleFactory 等等.从当前
这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性、可扩展性并不是特别好

思路 2
使用工厂方法模式↓

2. 工厂方法模式介绍

1) 工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现。
2) 工厂方法模式:**定义了一个创建对象的抽象方法,由子类决定要实例化的类**。工厂方法模式将**对象的实例化推迟到子类**。

2.2.5工厂方法模式应用案例

  1. 披萨项目新的需求:客户在点披萨时,可以点不同地方、不同口味的披萨,比如 北京的奶酪 pizza、北京的胡椒 pizza 或者是伦敦的奶酪 pizza、伦敦的胡椒 pizza
  2. 思路分析图解
    第四部分 设计模型_第20张图片

3. 抽象工厂(AbsFactory)

1. 基本介绍

  1. 抽象工厂模式:定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
  2. 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
  3. 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
  4. 将工厂抽象成**两层,AbsFactory(抽象工厂) 具体实现的工厂子类。**程序员可以根据创建对象类型使用对应
    的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
  5. 类图

三、代理

四、观察者模式(observer design pattern)

五、适配器模式

六、外观模式

你可能感兴趣的:(第四部分:设计模型,java)