概述

设计模式介绍

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

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

  3. <<设计模式>> 是经典的书,作者是 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides Design(俗 称 “四人组 GOF”)

  4. 设计模式并不局限于某种语言,java,php,c++ 都有设计模式.

设计模式类型

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

  1. 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。

  2. 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

  3. 行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、 解释器模式(Interpreter 模式)、状态模式、策略模式、职责链模式(责任链模式)。

七大原则

设计模式的目的

编写软件过程中,程序员面临着来自 耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性 等多方面的挑战,设计模式是为了让程序(软件),具有更好

  1. 代码重用性 (即:相同功能的代码,不用多次编写)

  2. 可读性 (即:编程规范性, 便于其他程序员的阅读和理解)

  3. 可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)

  4. 可靠性 (即:当我们增加新的功能后,对原来的功能没有影响) 5) 使程序呈现高内聚,低耦合的特性

七大原则

设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础(即:设计模式为什么 这样设计的依据)

设计模式常用的七大原则有:

  1. 单一职责原则

  2. 接口隔离原则

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

  4. 里氏替换原则

  5. 开闭原则

  6. 迪米特法则

  7. 合成复用原则

单一职责原则

基本介绍

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

应用实例

以交通工具案例讲解

  1. 方案 1
public class SingleResponsibility1 {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		Vehicle vehicle = new Vehicle();
		vehicle.run("摩托车");
		vehicle.run("汽车");
		vehicle.run("飞机");
	}
}

// 交通工具类
// 方式1
// 1. 在方式1 的run方法中,违反了单一职责原则
// 2. 解决的方案非常的简单,根据交通工具运行方法不同,分解成不同类即可
class Vehicle {
   
	public void run(String vehicle) {
   
		System.out.println(vehicle + " 在公路上运行....");
	}
}
  1. 方案 2
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("摩托车");
		roadVehicle.run("汽车");
		
		AirVehicle airVehicle = new AirVehicle();
		
		airVehicle.run("飞机");
	}
}

//方案2的分析
//1. 遵守单一职责原则
//2. 但是这样做的改动很大,即将类分解,同时修改客户端
//3. 改进:直接修改Vehicle 类,改动的代码会比较少=>方案3
class RoadVehicle {
   
	public void run(String vehicle) {
   
		System.out.println(vehicle + "公路运行");
	}
}

class AirVehicle {
   
	public void run(String vehicle) {
   
		System.out.println(vehicle + "天空运行");
	}
}

class WaterVehicle {
   
	public void run(String vehicle) {
   
		System.out.println(vehicle + "水中运行");
	}
}
  1. 方案 3
public class SingleResponsibility3 {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		Vehicle2 vehicle2  = new Vehicle2();
		vehicle2.run("汽车");
		vehicle2.runWater("轮船");
		vehicle2.runAir("飞机");
	}
}

//方式3的分析
//1. 这种修改方法没有对原来的类做大的修改,只是增加方法
//2. 这里虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责
class Vehicle2 {
   
	public void run(String vehicle) {
   
		//处理
		System.out.println(vehicle + " 在公路上运行....");
	}
	
	public void runAir(String vehicle) {
   
		System.out.println(vehicle + " 在天空上运行....");
	}
	
	public void runWater(String vehicle) {
   
		System.out.println(vehicle + " 在水中行....");
	}
	
	//方法2.
	//..
}

注意事项和细节

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

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

  3. 降低变更引起的风险

  4. 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则

接口隔离原则

基本介绍

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

  2. 先看一张图:

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

  2. 按隔离原则应当这样处理:

将接口 Interface1 拆分为独立的几个接口(这里我们拆分成 3 个接口),类 A 和类 C 分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则

应用实例

  1. 类 A 通过接口 Interface1 依赖类 B,类 C 通过接口 Interface1 依赖类 D,请编写代码完成此应用实例。

  2. 没有使用接口隔离原则代码

public class Segregation1 {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub

	}
}

//接口
interface Interface1 {
   
	void operation1();
	void operation2();
	void operation3();
	void operation4();
	void operation5();
}

class B implements Interface1 {
   
	public void operation1() {
   
		System.out.println("B 实现了 operation1");
	}
	
	public void operation2() {
   
		System.out.println("B 实现了 operation2");
	}
	public void operation3() {
   
		System.out.println("B 实现了 operation3");
	}
	public void operation4() {
   
		System.out.println("B 实现了 operation4");
	}
	public void operation5() {
   
		System.out.println("B 实现了 operation5");
	}
}

class D implements Interface1 {
   
	public void operation1() {
   
		System.out.println("D 实现了 operation1");
	}
	
	public void operation2() {
   
		System.out.println("D 实现了 operation2");
	}
	public void operation3() {
   
		System.out.println("D 实现了 operation3");
	}
	public void operation4() {
   
		System.out.println("D 实现了 operation4");
	}
	public void operation5() {
   
		System.out.println("D 实现了 operation5");
	}
}

class A {
    //A 类通过接口Interface1 依赖(使用) B类,但是只会用到1,2,3方法
	public void depend1(Interface1 i) {
   
		i.operation1();
	}
	public void depend2(Interface1 i) {
   
		i.operation2();
	}
	public void depend3(Interface1 i) {
   
		i.operation3();
	}
}
  
class C {
    //C 类通过接口Interface1 依赖(使用) D类,但是只会用到1,4,5方法
	public void depend1(Interface1 i) {
   
		i.operation1();
	}
	public void depend4(Interface1 i) {
   
		i.operation4();
	}
	public void depend5(Interface1 i) {
   
		i.operation5();
	}
}

应传统方法的问题和使用接口隔离原则改进

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

  2. 将接口 Interface1 拆分为独立的几个接口,类 A 和类 C 分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则

  3. 接口 Interface1 中出现的方法,根据实际情况拆分为三个接口

1648105802889
public class Segregation1 {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		// 使用一把
		A a = new A();
		a.depend1(new B()); // A类通过接口去依赖B类
		a.depend2(new B());
		a.depend3(new B());

		C c = new C();

		c.depend1(new D()); // C类通过接口去依赖(使用)D类
		c.depend4(new D());
		c.depend5(new D());
	}
}

// 接口1
interface Interface1 {
   
	void operation1();
}

// 接口2
interface Interface2 {
   
	void operation2();
	void operation3();
}

// 接口3
interface Interface3 {
   
	void operation4();
	void operation5();
}

class B implements Interface1, Interface2 {
   
	public void operation1() {
   
		System.out.println("B 实现了 operation1");
	}

	public void operation2() {
   
		System.out.println("B 实现了 operation2");
	}

	public void operation3() {
   
		System.out.println("B 实现了 operation3");
	}
}

class D implements Interface1, Interface3 {
   
	public void operation1() {
   
		System.out.println("D 实现了 operation1");
	}

	public void operation4() {
   
		System.out.println("D 实现了 operation4");
	}

	public void operation5() {
   
		System.out.println("D 实现了 operation5");
	}
}

class A {
    // A 类通过接口Interface1,Interface2 依赖(使用) B类,但是只会用到1,2,3方法
	public void depend1(Interface1 i) {
   
		i.operation1();
	}

	public void depend2(Interface2 i) {
   
		i.operation2();
	}

	public void depend3(Interface2 i) {
   
		i.operation3();
	}
}

class C {
    // C 类通过接口Interface1,Interface3 依赖(使用) D类,但是只会用到1,4,5方法
	public void depend1(Interface1 i) {
   
		i.operation1();
	}

	public void depend4(Interface3 i) {
   
		i.operation4();
	}

	public void depend5(Interface3 i) {
   
		i.operation5();
	}
}

依赖倒转原则

基本介绍

依赖倒转原则(Dependence Inversion Principle)是指:

  1. 高层模块不应该依赖低层模块,二者都应该依赖其抽象

  2. 抽象不应该依赖细节,细节应该依赖抽象

  3. 依赖倒转(倒置)的中心思想是面向接口编程

  4. 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架 构比以细节为基础的架构要稳定的多。在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类

  5. 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完 成

应用实例

请编程完成 Person 接收消息的功能。

  1. 实现方案 1 + 分析说明
public class DependecyInversion {
   

	public static void main(String[] args) {
   
		Person person = new Person();
		person.receive(new Email());
	}
}

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());
	}
}
  1. 实现方案 2(依赖倒转) + 分析说明
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("长虹电视机,打开");
	}
}

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

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

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

  3. 继承时遵循里氏替换原则

里氏替换原则

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

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

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

  3. 问题提出:在编程中,如何正确的使用继承? => 里氏替换原则

基本介绍

  1. 里氏替换原则(Liskov Substitution Principle)在 1988 年,由麻省理工学院的以为姓里的女士提出的。

  2. 如果对每个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都 代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。换句话说,所有引用基类的地 方必须能透明地使用其子类的对象。

  3. 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法

  4. 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。

一个程序引出的问题和思考

该看个程序, 思考下问题和解决思路

public class Liskov {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		A a = new A();
		System.out.println("11-3=" + a.func1(11, 3));
		System.out.println("1-8=" + a.func1(1, 8));

		System.out.println("-----------------------");
		B b = new B();
		System.out.println("11-3=" + b.func1(11, 3));//这里本意是求出11-3
		System.out.println("1-8=" + b.func1(1, 8));// 1-8
		System.out.println("11+3+9=" + b.func2(11, 3));
	}
}

// A类
class A {
   
	// 返回两个数的差
	public int func1(int num1, int num2) {
   
		return num1 - num2;
	}
}

// B类继承了A
// 增加了一个新功能:完成两个数相加,然后和9求和
class B extends A {
   
	//这里,重写了A类的方法, 可能是无意识
	public int func1(int a, int b) {
   
		return a + b;
	}

	public int func2(int a, int b) {
   
		return func1(a, b) + 9;
	}
}

解决方法

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

  2. 通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等 关系代替.

  3. 改进方案

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1jx4Z7sh-1648380440729)(Pic/1648106406154.png)]

public class Liskov {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		A a = new A();
		System.out.println("11-3=" + a.func1(11, 3));
		System.out.println("1-8=" + a.func1(1, 8));

		System.out.println("-----------------------");
		B b = new B();
		//因为B类不再继承A类,因此调用者,不会再func1是求减法
		//调用完成的功能就会很明确
		System.out.println("11+3=" + b.func1(11, 3));//这里本意是求出11+3
		System.out.println("1+8=" + b.func1(1, 8));// 1+8
		System.out.println("11+3+9=" + b.func2(11, 3));
		
		//使用组合仍然可以使用到A类相关方法
		System.out.println("11-3=" + b.func3(11, 3));// 这里本意是求出11-3
	}
}

//创建一个更加基础的基类
class Base {
   
	//把更加基础的方法和成员写到Base类
}

// A类
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);
	}
}

开闭原则

基本介绍

  1. 开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则

  2. 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实 现扩展细节。

  3. 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

  4. 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。

看下面一段代码

看一个画图形的功能。 类图设计,如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VweMHJoe-1648380440731)(Pic/1648106519518.png)]

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

方式1的优缺点

  1. 优点是比较好理解,简单易操作。

  2. 缺点是违反了设计模式的 ocp 原则,即对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的 时候,尽量不修改代码,或者尽可能少修改代码.

  3. 比如我们这时要新增加一个图形种类 三角形,我们需要做如下修改,修改的地方较多

  4. 代码演示

方式 1 的改进的思路分析

改进的思路分析

思路:把创建 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(" 绘制其它图形 ");
	}
}

迪米特法则

基本介绍

  1. 一个对象应该对其他对象保持最少的了解

  2. 类与类关系越密切,耦合度越大

  3. 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于 被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息

  4. 迪米特法则还有个更简单的定义:只与直接的朋友通信

  5. 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间 是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返 回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变 量的形式出现在类的内部。

应用实例

  1. 有一个学校,下属有各个学院和总部,现要求打印出学校总部员工 ID 和学院员工的 id

  2. 代码演示

//客户端
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());
		}
	}
}

应用实例改进

  1. 前面设计的问题在于 SchoolManager 中,CollegeEmployee 类并不是 SchoolManager 类的直接朋友 (分析)

  2. 按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合

  3. 对代码按照迪米特法则 进行改进.

//客户端
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());
		}
	}
}

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

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

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

合成复用原则

基本介绍

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rfMInwcE-1648380440732)(Pic/1648107032977.png)]

设计原则核心思想

  1. 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

  2. 针对接口编程,而不是针对实现编程。

  3. 为了交互对象之间的松耦合设计而努力

UML类图

基本介绍

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

  2. UML 本身是一套符号的规定,就像数学符号和化学符号一样,这些符号用于描述软件模型中的各个元素和他 们之间的关系,比如类、接口、实现、泛化、依赖、组合、聚合等,如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XJon65fh-1648380440733)(Pic/1648107124580.png)]

解释

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VsHVotfA-1648380440734)(Pic/1648107168961.png)]

UML图

画 UML 图与写文章差不多,都是把自己的思想描述给别人看,关键在于思路和条理,UML 图分类:

  1. 用例图(use case)

  2. 静态结构图:类图、对象图、包图、组件图、部署图

  3. 动态行为图:交互图(时序图与协作图)、状态图、活动图

说明:类图是描述类与类之间的关系的,是 UML 图中最核心的

UML类图

  1. 用于描述系统中的类(对象)本身的组成和类(对象)之间的各种静态关系。

  2. 类之间的关系:依赖、泛化(继承)、实现、关联、聚合与组合。

  3. 类图简单举例

public class Person{
    //代码形式->类图 
    private Integer id; 
    private String name; 
    
    public void setName(String name){
    
        this.name=name; 
    }
    
    public String getName(){
    
        return name; 
    } 
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Od6EDjs-1648380440736)(Pic/1648107373925.png)]


依赖关系(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{
   } 

对应的类图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qUTpdvXX-1648380440737)(Pic/1648107514979.png)]

小结

  1. 类中用到了对方

  2. 如果是类的成员属性

  3. 如果是方法的返回类型

  4. 是方法接收的参数类型

  5. 方法中使用到


泛化关系(generalization)

泛化关系实际上就是继承关系,他是依赖关系的特例

public abstract class DaoSupport{
    
    public void save(Object entity){
    }
    public void delete(Object id){
    } 
}
public class PersonServiceBean extends Daosupport{
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XwXfaZ0i-1648380440739)(Pic/1648107614660.png)]

小结

  1. 泛化关系实际上就是继承关系

  2. 如果 A 类继承了 B 类,我们就说 A 和 B 存在泛化关系


实现关系(Implementation)

实现关系实际上就是 A 类实现 B 接口,他是依赖关系的特例

public interface PersonService {
    
    public void delete(Interger id); 
}

public class PersonServiceBean implements PersonService {
    
    public void delete(Interger id){
   } 
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yGYpmTvO-1648380440740)(Pic/1648107696160.png)]


关联关系(Association)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PuCIAeOf-1648380440741)(Pic/1648107751071.png)]


聚合关系(Aggregation)

基本介绍

聚合关系(Aggregation)表示的是整体和部分的关系,整体与部分可以分开。聚合关系是关联关系的特例,所 以他具有关联的导航性与多重性。

如:一台电脑由键盘(keyboard)、显示器(monitor),鼠标等组成;组成电脑的各个配件是可以从电脑上分离出来 的,使用带空心菱形的实线来表示:

应用实例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nKIGFJ25-1648380440742)(Pic/1648107826690.png)]


组合关系(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{
   }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XHBZTNzg-1648380440743)(Pic/1648107935995.png)]

例二

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 {
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4QzrZwIc-1648380440745)(Pic/1648108021300.png)]

单例设计模式

介绍

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

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

八种方式

  1. 饿汉式(静态常量)

  2. 饿汉式(静态代码块)

  3. 懒汉式(线程不安全)

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

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

  6. 双重检查

  7. 静态内部类

  8. 枚举


饿汉式(静态常量)

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

步骤如下:

  1. 构造器私有化 (防止 new )

  2. 类的内部创建对象

  3. 向外暴露一个静态的公共方法。getInstance

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

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

优缺点说明:

  1. 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

  2. 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则 会造成内存的浪费

  3. 这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大 多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静 态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果

  4. 结论:这种单例模式可用,可能造成内存浪费


饿汉式(静态代码块)

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. 结论:这种单例模式可用,但是可能造成内存浪费


懒汉式(线程不安全)

public class SingletonTest03 {
   

	public static void main(String[] args) {
   
		System.out.println("懒汉式1 , 线程不安全~");
		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() {
   }
	
	//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
	//即懒汉式
	public static Singleton getInstance() {
   
		if(instance == null) {
   
			instance = new Singleton();
		}
		return instance;
	}
}

优缺点说明:

  1. 起到了 Lazy Loading 的效果,但是只能在单线程下使用。

  2. 如果在多线程下,一个线程进入了 if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过 了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式

  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() {
   }
	
	//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
	//即懒汉式
	public static synchronized Singleton getInstance() {
   
		if(instance == null) {
   
			instance = new Singleton();
		}
		return instance;
	}
}

优缺点说明:

  1. 解决了线程安全问题

  2. 效率太低了,每个线程在想获得类的实例时候,执行 getInstance()方法都要进行同步。而其实这个方法只执行 一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低

  3. 结论:在实际开发中,不推荐使用这种方式


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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jPtmsF2o-1648380440746)(Pic/1648111351244.png)]

不推荐使用

双重检查

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 {
   
	private static volatile Singleton instance;
	
	private Singleton() {
   }
	
	//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
	//同时保证了效率, 推荐使用
	
	public static synchronized Singleton getInstance() {
   
		if(instance == null) {
   
			synchronized (Singleton.class) {
   
				if(instance == null) {
   
					instance = new Singleton();
				}
			}	
		}
		return instance;
	}
}

优缺点说明:

  1. Double-Check 概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if (singleton == null)检查,这 样就可以保证线程安全了。

  2. 这样,实例化代码只用执行一次,后面再次访问时,判断 if (singleton == null),直接 return 实例化对象,也避 免的反复进行方法同步.

  3. 线程安全;延迟加载;效率较高

  4. 结论:在实际开发中,推荐使用这种单例设计模式


静态内部类

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 {
   
	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() {
   
		
		return SingletonInstance.INSTANCE;
	}
}

优缺点说明:

  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。

  2. 静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才 会装载 SingletonInstance 类,从而完成 Singleton 的实例化。

  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行 初始化时,别的线程是无法进入的。

  4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

  5. 结论:推荐使用.


枚举

public class SingletonTest08 {
   
	public static void main(String[] args) {
   
		Singleton instance = Singleton.INSTANCE;
		Singleton instance2 = Singleton.INSTANCE;
		System.out.println(instance == instance2);
		
		System.out.println(instance.hashCode());
		System.out.println(instance2.hashCode());
		
		instance.sayOK();
	}
}

//使用枚举,可以实现单例, 推荐
enum Singleton {
   
	INSTANCE; //属性
	public void sayOK() {
   
		System.out.println("ok~");
	}
}

优缺点说明:

  1. 这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建 新的对象。

  2. 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式

  3. 结论:推荐使用

应用源码分析

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

  1. 我们 JDK 中,java.lang.Runtime 就是经典的单例模式(饿汉式)

  2. 代码分析+Debug 源码+代码说明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4UaYXfd-1648380440748)(Pic/1648111618775.png)]

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

  1. 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使 用单例模式可以提高系统性能

  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new

  3. 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级 对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)

工厂模式

简单工厂模式

看一个具体的需求

看一个披萨的项目:要便于披萨种类的扩展,要便于维护

  1. 披萨的种类很多(比如 GreekPizz、CheesePizz 等)

  2. 披萨的制作有 prepare,bake, cut, box

  3. 完成披萨店订购功能。

使用传统的方式来完成

  1. 思路分析(类图)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NGA1vPOD-1648380440749)(Pic/1648111712721.png)]

编写 OrderPizza.java 去订购需要的各种 Pizza

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("cheese")) {
   
// pizza = new CheesePizza();
// pizza.setName(" 奶酪披萨 ");
// } else if (orderType.equals("pepper")) {
   
// pizza = new PepperPizza();
// pizza.setName("胡椒披萨");
// } else {
   
// break;
// }
// //输出pizza 制作过程
// pizza.prepare();
// pizza.bake();
// pizza.cut();
// pizza.box();
// 
// } while (true);
// }
}

传统的方式的优缺点

  1. 优点是比较好理解,简单易操作。

  2. 缺点是违反了设计模式的 ocp 原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修 改代码,或者尽可能少修改代码.

  3. 比如我们这时要新增加一个 Pizza 的种类(Pepper 披萨),我们需要做如下修改. 如果我们增加一个 Pizza 类,只要是订购 Pizza 的代码都需要修改

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r7Ubf4Kt-1648380440750)(Pic/1648111810162.png)]

  1. 改进的思路分析

分析:修改代码可以接受,但是如果我们在其它的地方也有创建 Pizza 的代码,就意味着,也需要修改,而创建 Pizza 的代码,往往有多处。

思路:把创建 Pizza 对象封装到一个类中,这样我们有新的 Pizza 种类时,只需要修改该类就可,其它有创建到 Pizza 对象的代码就不需要修改了.-> 简单工厂模式

基本介绍

  1. 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品 类的实例。简单工厂模式是工厂模式家族中最简单实用的模式

  2. 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)

  3. 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式.

使用简单工厂模式

  1. 简单工厂模式的设计方案: 定义一个可以实例化 Pizaa 对象的类,封装创建对象的代码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kFebf84i-1648380440751)(Pic/1648111884881.png)]

//简单工厂类
public class SimpleFactory {
   

	//更加orderType 返回对应的Pizza 对象
	public Pizza createPizza(String orderType) {
   

		Pizza pizza = null;

		System.out.println("使用简单工厂模式");
		if (orderType.equals("greek")) {
   
			pizza = new GreekPizza();
			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 createPizza2(String orderType) {
   

		Pizza pizza = null;

		System.out.println("使用简单工厂模式2");
		if (orderType.equals("greek")) {
   
			pizza = new GreekPizza();
			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 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("cheese")) {
   
// pizza = new CheesePizza();
// pizza.setName(" 奶酪披萨 ");
// } else if (orderType.equals("pepper")) {
   
// pizza = new PepperPizza();
// pizza.setName("胡椒披萨");
// } else {
   
// break;
// }
// //输出pizza 制作过程
// pizza.prepare();
// pizza.bake();
// pizza.cut();
// pizza.box();
// 
// } while (true);
// }

	//定义一个简单工厂对象
	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) {
   
			e.printStackTrace();
			return "";
		}
	}
}

工厂方法模式

看一个新的需求

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

思路 1

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

思路 2

使用工厂方法模式

工厂方法模式介绍

  1. 工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现。

  2. 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例 化推迟到子类。

工厂方法模式应用案例

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

  2. 思路分析图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tPj4bJhI-1648380440753)(Pic/1648112129015.png)]

public abstract class OrderPizza {
   

	//定义一个抽象方法,createPizza , 让各个工厂子类自己实现
	abstract Pizza createPizza(String orderType);
	
	// 构造器
	public OrderPizza() {
   
		Pizza pizza = null;
		String orderType; // 订购披萨的类型
		do {
   
			orderType = getType();
			pizza = createPizza(orderType); //抽象方法,由工厂子类完成
			//输出pizza 制作过程
			pizza.prepare();
			pizza.bake();
			pizza.cut();
			pizza.box();
			
		} 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) {
   
			e.printStackTrace();
			return "";
		}
	}
}
public class BJOrderPizza extends OrderPizza {
   
	
	@Override
	Pizza createPizza(String orderType) {
   
	
		Pizza pizza = null;
		if(orderType.equals("cheese")) {
   
			pizza = new BJCheesePizza();
		} else if (orderType.equals("pepper")) {
   
			pizza = new BJPepperPizza();
		}
		// TODO Auto-generated method stub
		return pizza;
	}
}
public class LDOrderPizza extends OrderPizza {
   
	@Override
	Pizza createPizza(String orderType) {
   
	
		Pizza pizza = null;
		if(orderType.equals("cheese")) {
   
			pizza = new LDCheesePizza();
		} else if (orderType.equals("pepper")) {
   
			pizza = new LDPepperPizza();
		}
		// TODO Auto-generated method stub
		return pizza;
	}
}

抽象工厂模式

基本介绍

  1. 抽象工厂模式:定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类

  2. 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。

  3. 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。

  4. 将工厂抽象成两层,AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以根据创建对象类型使用对应 的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。

  5. 类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bA0MnQk4-1648380440754)(Pic/1648112272731.png)]

抽象工厂模式应用实例

使用抽象工厂模式来完成披萨项目.

//一个抽象工厂模式的抽象层(接口)
public interface AbsFactory {
   
	//让下面的工厂子类来 具体实现
	public Pizza createPizza(String orderType);
}
//这是工厂子类
public class BJFactory implements AbsFactory {
   

	@Override
	public Pizza createPizza(String orderType) {
   
		System.out.println("~使用的是抽象工厂模式~");
		// TODO Auto-generated method stub
		Pizza pizza = null;
		if(orderType.equals("cheese")) {
   
			pizza = new BJCheesePizza();
		} else if (orderType.equals("pepper")){
   
			pizza = new BJPepperPizza();
		}
		return pizza;
	}
}
public class LDFactory implements AbsFactory {
   

	@Override
	public Pizza createPizza(String orderType) {
   
		System.out.println("~使用的是抽象工厂模式~");
		Pizza pizza = null;
		if (orderType.equals("cheese")) {
   
			pizza = new LDCheesePizza();
		} else if (orderType.equals("pepper")) {
   
			pizza = new LDPepperPizza();
		}
		return pizza;
	}
}
public class OrderPizza {
   

	AbsFactory factory;

	// 构造器
	public OrderPizza(AbsFactory factory) {
   
		setFactory(factory);
	}

	private void setFactory(AbsFactory factory) {
   
		Pizza pizza = null;
		String orderType = ""; // 用户输入
		this.factory = factory;
		do {
   
			orderType = getType();
			// factory 可能是北京的工厂子类,也可能是伦敦的工厂子类
			pizza = factory.createPizza(orderType);
			if (pizza != null) {
    // 订购ok
				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) {
   
			e.printStackTrace();
			return "";
		}
	}
}

应用源码分析

JDK 中的 Calendar 类中,就使用了简单工厂模式

public class Factory {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		// getInstance 是 Calendar 静态方法
		Calendar cal = Calendar.getInstance();
	    // 注意月份下标从0开始,所以取月份要+1
	    System.out.println("年:" + cal.get(Calendar.YEAR));
	    System.out.println("月:" + (cal.get(Calendar.MONTH) + 1));       
	    System.out.println("日:" + cal.get(Calendar.DAY_OF_MONTH));
	    System.out.println("时:" + cal.get(Calendar.HOUR_OF_DAY));
	    System.out.println("分:" + cal.get(Calendar.MINUTE));
	    System.out.println("秒:" + cal.get(Calendar.SECOND));   
	}
}
//Calendar.java
public static Calendar getInstance() {
    
    return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT)); 
}
private static Calendar createCalendar(TimeZone zone,Locale aLocale) {
    
    //根据 TimeZone zone, locale 创建对应的实例 
    CalendarProvider provider = LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale) .getCalendarProvider(); 
    if (provider != null) {
    
        try {
    
            return provider.getInstance(zone, aLocale); 
        } catch (IllegalArgumentException iae) {
    
            // fall back to the default instantiation 
        } 
    }
    Calendar cal = null; 
    if (aLocale.hasExtensions()) {
    
        String caltype = aLocale.getUnicodeLocaleType("ca"); 
        if (caltype != null) {
   
            switch (caltype) {
    
                case "buddhist": 
                    cal = new BuddhistCalendar(zone, aLocale); 
                    break; 
                case "japanese": 
                    cal = new JapaneseImperialCalendar(zone, aLocale);
                    break; 
                case "gregory": 
                    cal = new GregorianCalendar(zone, aLocale); 
                    break; 
            } 
        } 
    }
    if (cal == null) {
    
        // If no known calendar type is explicitly specified, 
        // perform the traditional way to create a Calendar: 
        // create a BuddhistCalendar for th_TH locale, 
        // a JapaneseImperialCalendar for ja_JP_JP locale, or 
        // a GregorianCalendar for any other locales. 
        // NOTE: The language, country and variant strings are interned. 
        if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
    
            cal = new BuddhistCalendar(zone, aLocale); 
        } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") {
    
            cal = new JapaneseImperialCalendar(zone, aLocale); 
        } else {
   
        cal = new GregorianCalendar(zone, aLocale); 
        } 
    }return cal; 
}

工厂模式小结

  1. 工厂模式的意义 将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项 目的扩展和维护性。

  2. 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)

  3. 设计模式的依赖抽象原则

创建对象实例时,不要直接 new 类, 而是把这个 new 类的动作放在一个工厂的方法中,并返回。有的书上说, 变量不要直接持有具体类的引用。

不要让类继承具体类,而是继承抽象类或者是实现 interface(接口)

不要覆盖基类中已经实现的方法。

原型模式

克隆羊问题

现在有一只羊 tom,姓名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和 tom 羊 属性完全相同的 10 只羊。

传统方式解决克隆羊问题

  1. 思路分析(图解)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xKQknwBa-1648380440756)(Pic/1648115343701.png)]

public class Client {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		//传统的方法
		Sheep sheep = new Sheep("tom", 1, "白色");
		
		Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
		Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
		Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
		Sheep sheep5 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
		//....
		
		System.out.println(sheep);
		System.out.println(sheep2);
		System.out.println(sheep3);
		System.out.println(sheep4);
		System.out.println(sheep5);
		//...
	}
}

传统的方式的优缺点

  1. 优点是比较好理解,简单易操作。

  2. 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低

  3. 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活

  4. 改进的思路分析

思路:Java 中 Object 类是所有类的根类,Object 类提供了一个 clone()方法,该方法可以将一个 Java 对象复制 一份,但是需要实现 clone 的 Java 类必须要实现一个接口 Cloneable,该接口表示该类能够复制且具有复制的能力 => 原型模式

基本介绍

  1. 原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象

  2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节

  3. 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它 们自己来实施创建,即 对象.clone()

  4. 形象的理解:孙大圣拔出猴毛, 变出其它孙大圣

原理结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vxwk7thc-1648380440757)(Pic/1648117994915.png)]

原理结构图说明

  1. Prototype : 原型类,声明一个克隆自己的接口

  2. ConcretePrototype: 具体的原型类, 实现一个克隆自己的操作

  3. Client: 让一个原型对象克隆自己,从而创建一个新的对象(属性一样)

应用实例

原型模式解决克隆羊问题

使用原型模式改进传统方式,让程序具有更高的效率和扩展性。

public class Sheep implements Cloneable {
   
	private String name;
	private int age;
	private String color;
	private String address = "蒙古羊";
	public Sheep friend; //是对象, 克隆是会如何处理
	public Sheep(String name, int age, String color) {
   
		super();
		this.name = name;
		this.age = age;
		this.color = color;
	}
	public String getName() {
   
		return name;
	}
	public void setName(String name) {
   
		this.name = name;
	}
	public int getAge() {
   
		return age;
	}
	public void setAge(int age) {
   
		this.age = age;
	}
	public String getColor() {
   
		return color;
	}
	public void setColor(String color) {
   
		this.color = color;
	}

	@Override
	public String toString() {
   
		return "Sheep [name=" + name + ", age=" + age + ", color=" + color + ", address=" + address + "]";
	}
    
	//克隆该实例,使用默认的clone方法来完成
	@Override
	protected Object clone()  {
   
		
		Sheep sheep = null;
		try {
   
			sheep = (Sheep)super.clone();
		} catch (Exception e) {
   
			// TODO: handle exception
			System.out.println(e.getMessage());
		}
		// TODO Auto-generated method stub
		return sheep;
	}
}
public class Client {
   

	public static void main(String[] args) {
   
		System.out.println("原型模式完成对象的创建");
		// TODO Auto-generated method stub
		Sheep sheep = new Sheep("tom", 1, "白色");
		
		sheep.friend = new Sheep("jack", 2, "黑色");
		
		Sheep sheep2 = (Sheep)sheep.clone(); //克隆
		Sheep sheep3 = (Sheep)sheep.clone(); //克隆
		Sheep sheep4 = (Sheep)sheep.clone(); //克隆
		Sheep sheep5 = (Sheep)sheep.clone(); //克隆
		
		System.out.println("sheep2 =" + sheep2 + "sheep2.friend=" + sheep2.friend.hashCode());
		System.out.println("sheep3 =" + sheep3 + "sheep3.friend=" + sheep3.friend.hashCode());
		System.out.println("sheep4 =" + sheep4 + "sheep4.friend=" + sheep4.friend.hashCode());
		System.out.println("sheep5 =" + sheep5 + "sheep5.friend=" + sheep5.friend.hashCode());
	}
}

源码分析

Spring 中原型 bean 的创建,就是原型模式的应用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XtPAEnZ-1648380440757)(Pic/1648118170876.png)]

浅拷贝和深拷贝

浅拷贝的介绍

  1. 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。

  2. 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行 引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成 员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值

  3. 前面我们克隆羊就是浅拷贝

  4. 浅拷贝是使用默认的 clone()方法来实现

sheep = (Sheep) super.clone();

深拷贝基本介绍

  1. 复制对象的所有基本数据类型的成员变量值

  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象 可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝

  3. 深拷贝实现方式 1:重写 clone 方法来实现深拷贝

  4. 深拷贝实现方式 2:通过对象序列化实现深拷贝(推荐)

深拷贝应用实例

  1. 使用 重写 clone 方法实现深拷贝

  2. 使用序列化来实现深拷贝

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 deepCloneableTarget;// 引用类型
	public DeepProtoType() {
   
		super();
	}
	
	//深拷贝 - 方式 1 使用clone 方法
	@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 DeepCloneableTarget("大牛", "小牛");
		
		//方式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.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
	
	}
}

注意事项和细节

  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率

  2. 不用重新初始化对象,而是动态地获得对象运行时的状态

  3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码

  4. 在实现深克隆的时候可能需要比较复杂的代码

  5. 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改 其源代码,违背了 ocp 原则,这点请同学们注意.

建造者模式

盖房项目需求

  1. 需要建房子:这一过程为打桩、砌墙、封顶

  2. 房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的.

  3. 请编写程序,完成需求.

传统方式解决盖房需求

  1. 思路分析(图解)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6BbBbFrq-1648380440758)(Pic/1648119054996.png)]

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(" 普通房子砌墙 ");
	}

	@Override
	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();
	}
}

传统方式的问题分析

  1. 优点是比较好理解,简单易操作。

  2. 设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好. 也就是说,这种设计方案,把产 品(即:房子) 和 创建产品的过程(即:建房子流程) 封装在一起,耦合性增强了。

  3. 解决方案:将产品和产品建造过程解耦 => 建造者模式.

基本介绍

  1. 建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出 来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。

  2. 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们, 用户不需要知道内部的具体构建细节。

建造者模式的四个角色

  1. Product(产品角色): 一个具体的产品对象。

  2. Builder(抽象建造者): 创建一个 Product 对象的各个部件指定的 接口/抽象类。

  3. ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。

  4. Director(指挥者): 构建一个使用 Builder 接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作 用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

建造者模式原理类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t5hsZHqs-1648380440759)(Pic/1648119271944.png)]

应用实例

建造者模式解决盖房需求

  1. 需要建房子:这一过程为打桩、砌墙、封顶。不管是普通房子也好,别墅也好都需要经历这些过程,下面我们 使用建造者模式(Builder Pattern)来完成

  2. 思路分析图解(类图)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IFL6bRzc-1648380440760)(Pic/1648119318873.png)]

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("输出流程");
		
		System.out.println("--------------------------");
		//盖高楼
		HighBuilding highBuilding = new HighBuilding();
		//重置建造者
		houseDirector.setHouseBuilder(highBuilding);
		//完成盖房子,返回产品(高楼)
		houseDirector.constructHouse();	
	}
}
public class CommonHouse extends HouseBuilder {
   

	@Override
	public void buildBasic() {
   
		// TODO Auto-generated method stub
		System.out.println(" 普通房子打地基5米 ");
	}

	@Override
	public void buildWalls() {
   
		// TODO Auto-generated method stub
		System.out.println(" 普通房子砌墙10cm ");
	}

	@Override
	public void roofed() {
   
		// TODO Auto-generated method stub
		System.out.println(" 普通房子屋顶 ");
	}
}
public class HighBuilding extends HouseBuilder {
   

	@Override
	public void buildBasic() {
   
		// TODO Auto-generated method stub
		System.out.println(" 高楼的打地基100米 ");
	}

	@Override
	public void buildWalls() {
   
		// TODO Auto-generated method stub
		System.out.println(" 高楼的砌墙20cm ");
	}

	@Override
	public void roofed() {
   
		// TODO Auto-generated method stub
		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() {
   
		return wall;
	}
	public void setWall(String wall) {
   
		this.wall = wall;
	}
	public String getRoofed() {
   
		return roofed;
	}
	public void setRoofed(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();
	}
}

应用源码分析

java.lang.StringBuilder 中的建造者模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2BkVgy8l-1648380440761)(Pic/1648119495199.png)]

源码中建造者模式角色分析

Appendable 接口定义了多个 append 方法(抽象方法), 即 Appendable 为抽象建造者, 定义了抽象方法

AbstractStringBuilder 实现了 Appendable 接口方法,这里的 AbstractStringBuilder 已经是建造者,只是不能 实例化

StringBuilder 即充当了指挥者角色,同时充当了具体的建造者,建造方法的实现是由 AbstractStringBuilder 完 成, 而 StringBuilder 继承了 AbstractStringBuilder

建造者模式的注意事项和细节

  1. 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可 以创建不同的产品对象

  2. 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具 体建造者, 用户使用不同的具体建造者即可得到不同的产品对象

  3. 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰, 也更方便使用程序来控制创建过程

  4. 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭 原则”

  5. 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使 用建造者模式,因此其使用范围受到一定的限制。

  6. 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因 此在这种情况下,要考虑是否选择建造者模式.

  7. 抽象工厂模式 VS 建造者模式

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采 用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定 的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品

适配器模式

现实生活中的适配器例子

插座用的是两孔的(欧标),可以买个多功能转换插头 (适配器) ,这样就可以使用了。

基本介绍

  1. 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本 因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)

  2. 适配器模式属于结构型模式

  3. 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

工作原理

  1. 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容

  2. 从用户的角度看不到被适配者,是解耦的

  3. 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法

  4. 用户收到反馈结果,感觉只是和目标接***互,如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sI072UJd-1648380440763)(Pic/1648119717026.png)]

类适配器模式

类适配器模式介绍

基本介绍:Adapter 类,通过继承 src 类,实现 dst 类接口,完成 src->dst 的适配。

类适配器模式应用实例

  1. 应用实例说明

以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src (即被适配者),我们 的目 dst(即 目标)是 5V 直流电

  1. 思路分析(类图)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H3tpOT6f-1648380440764)(Pic/1648119867462.png)]

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(IVoltage5V 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() {
   
		// TODO Auto-generated method stub
		//获取到220V电压
		int srcV = output220V();
		int dstV = srcV / 44 ; //转成 5v
		return dstV;
	}
}

类适配器模式注意事项和细节

  1. Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dst 必须是接口,有一定局 限性;

  2. src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。

  3. 由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。

对象适配器模式

对象适配器模式介绍

  1. 基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决 兼容性的问题。 即:持有 src 类,实现 dst 类接口,完成 src->dst 的适配

  2. 根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。

  3. 对象适配器模式是适配器模式常用的一种

对象适配器模式应用实例

  1. 应用实例说明 以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src (即被适配者),我们 的目 dst(即目标)是 5V 直流电,使用对象适配器模式完成。

  2. 思路分析(类图):只需修改适配器即可, 如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MMmmR4u1-1648380440765)(Pic/1648120045010.png)]

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(new Voltage220V()));
	}
}
//适配接口 
public interface IVoltage5V {
    
    public int output5V(); 
}
public class Phone {
   

	//充电
	public void charging(IVoltage5V 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  implements IVoltage5V {
   

	private Voltage220V voltage220V; // 关联关系-聚合
	
	//通过构造器,传入一个 Voltage220V 实例
	public VoltageAdapter(Voltage220V voltage220v) {
   
		
		this.voltage220V = voltage220v;
	}

	@Override
	public int output5V() {
   
		
		int dst = 0;
		if(null != voltage220V) {
   
			int src = voltage220V.output220V();//获取220V 电压
			System.out.println("使用对象适配器,进行适配~~");
			dst = src / 44;
			System.out.println("适配完成,输出的电压为=" + dst);
		}
		return dst;	
	}
}

对象适配器模式注意事项和细节

  1. 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst 必须是接口。

  2. 使用成本更低,更灵活。

接口适配器模式

接口适配器模式介绍

  1. 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。

  2. 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供 一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求

  3. 适用于一个接口不想使用其所有的方法的情况。

接口适配器模式应用实例

  1. Android 中的属性动画 ValueAnimator 类可以通过 addListener(AnimatorListener listener)方法添加***, 那么 常规写法如右:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bnPNyEwE-1648380440766)(Pic/1648120247488.png)]

  1. 有时候我们不想实现 Animator.AnimatorListener 接口的全部方法,我们只想监听 onAnimationStart,我们会如下写

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T15zEhWb-1648380440766)(Pic/1648120263608.png)]

  1. AnimatorListenerAdapter 类,就是一个 接口适配器,代码如右图:它空实现了 Animator.AnimatorListener 类(src)的所 有方法.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KB2O7MRs-1648380440767)(Pic/1648120332160.png)]

  1. AnimatorListener 是一个接口.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3yGebycr-1648380440769)(Pic/1648120348113.png)]

案例说明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g7NJsfiQ-1648380440770)(Pic/1648120409084.png)]

public interface Interface4 {
   
	public void m1();
	public void m2();
	public void m3();
	public void m4();
}
//在AbsAdapter 我们将 Interface4 的方法进行默认实现
public abstract class AbsAdapter implements Interface4 {
   

	//默认实现
	public void m1() {
   
	}

	public void m2() {
   
	}

	public void m3() {
   
	}

	public void m4() {
   
	}
}
public class Client {
   
	public static void main(String[] args) {
   
		
		AbsAdapter absAdapter = new AbsAdapter() {
   
			//只需要去覆盖我们 需要使用 接口方法
			@Override
			public void m1() {
   
				// TODO Auto-generated method stub
				System.out.println("使用了m1的方法");
			}
		};
		
		absAdapter.m1();
	}
}

应用源码剖析

适配器模式在 SpringMVC 框架应用的源码剖析

  1. SpringMvc 中的 HandlerAdapter, 就使用了适配器模式

  2. SpringMVC 处理请求的流程回顾

  3. 使用 HandlerAdapter 的原因分析:

可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用 Controller 方 法,需要调用的时候就得不断是使用 if else 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller, 就得修改原来的代码,这样违背了 OCP 原则。

  1. 代码分析+Debug 源码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SPnjLKMS-1648380440772)(Pic/1648120540610.png)]

  1. 动手写 SpringMVC 通过适配器设计模式获取到对应的 Controller 的源码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H47cZrsC-1648380440772)(Pic/1648120600897.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TE1CiZxE-1648380440773)(Pic/1648120624823.png)]

适配器模式的注意事项和细节

  1. 三种命名方式,是根据 src 是以怎样的形式给到 Adapter(在 Adapter 里的形式)来命名的。

  2. 类适配器:以类给到,在 Adapter 里,就是将 src 当做类,继承 对象适配器:以对象给到,在 Adapter 里,将 src 作为一个对象,持有 接口适配器:以接口给到,在 Adapter 里,将 src 作为一个接口,实现

  3. Adapter 模式最大的作用还是将原本不兼容的接口融合在一起工作。

  4. 实际开发中,实现起来不拘泥于我们讲解的三种经典形式

装饰者模式

星巴克咖啡订单项目(咖啡馆):

  1. 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)

  2. 调料:Milk、Soy(豆浆)、Chocolate

  3. 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便

  4. 使用 OO 的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖啡+调料组合。

方案 1-解决星巴克咖啡订单项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mAF9BiBc-1648380440774)(Pic/1648112959986.png)]

方案 1-解决星巴克咖啡订单问题分析

  1. Drink 是一个抽象类,表示饮料

  2. des 就是对咖啡的描述, 比如咖啡的名字

  3. cost() 方法就是计算费用,Drink 类中做成一个抽象方法.

  4. Decaf 就是单品咖啡, 继承 Drink, 并实现 cost

  5. Espress && Milk 就是单品咖啡+调料, 这个组合很多

  6. 问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现 类爆炸

方案 2-解决星巴克咖啡订单(好点)

  1. 前面分析到方案 1 因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这 样就不会造成类数量过多。从而提高项目的维护性(如图)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5mD7x13A-1648380440775)(Pic/1648113024391.png)]

  1. 说明: milk,soy,chocolate 可以设计为 Boolean,表示是否要添加相应的调料.

方案 2-解决星巴克咖啡订单问题分析

  1. 方案 2 可以控制类的数量,不至于造成很多的类

  2. 在增加或者删除调料种类时,代码的维护量很大

  3. 考虑到用户可以添加多份 调料时,可以将 hasMilk 返回一个对应 int

  4. 考虑使用 装饰者 模式

装饰者模式定义

  1. 装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了 开闭原则(ocp)

  2. 这里提到的动态的将新功能附加到对象和 ocp 原则,在后面的应用实例上会以代码的形式体现,请同学们注意 体会。

装饰者模式原理

  1. 装饰者模式就像打包一个快递 主体:比如:陶瓷、衣服 (Component) // 被装饰者 包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)

  2. Component 主体:比如类似前面的 Drink

  3. ConcreteComponent 和 Decorator ConcreteComponent:具体的主体, 比如前面的各个单品咖啡

  4. Decorator: 装饰者,比如各调料. 在如图的 Component 与 ConcreteComponent 之间,如果 ConcreteComponent 类很多,还可以设计一个缓冲层,将 共有的部分提取出来,抽象层一个类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qn14u8l6-1648380440776)(Pic/1648113125750.png)]

装饰者模式解决星巴克咖啡订单

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rarrbJ3z-1648380440777)(Pic/1648113180035.png)]

装饰者模式下的订单:2 份巧克力+一份牛奶的 LongBlack

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGMp5wIF-1648380440778)(Pic/1648113233305.png)]

装饰者模式咖啡订单项目应用实例

//具体的 Decorator, 这里就是调味品 
public class Chocolate extends Decorator {
    
    public Chocolate(Drink obj) {
    
        super(obj); setDes(" 巧克力 "); 
        setPrice(3.0f); // 调味品 的价格 
    } 
}
public class Coffee extends Drink {
    
    @Override public float cost() {
    
        // TODO Auto-generated method stub 
        return super.getPrice(); 
    }
}
public class CoffeeBar {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		// 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack

		// 1. 点一份 LongBlack
		Drink order = new LongBlack();
		System.out.println("费用1=" + order.cost());
		System.out.println("描述=" + order.getDes());

		// 2. order 加入一份牛奶
		order = new Milk(order);

		System.out.println("order 加入一份牛奶 费用 =" + order.cost());
		System.out.println("order 加入一份牛奶 描述 = " + order.getDes());

		// 3. order 加入一份巧克力

		order = new Chocolate(order);

		System.out.println("order 加入一份牛奶 加入一份巧克力 费用 =" + order.cost());
		System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());

		// 3. order 加入一份巧克力

		order = new Chocolate(order);

		System.out.println("order 加入一份牛奶 加入2份巧克力 费用 =" + order.cost());
		System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDes());
	
		System.out.println("===========================");
		
		Drink order2 = new DeCaf();
		
		System.out.println("order2 无因咖啡 费用 =" + order2.cost());
		System.out.println("order2 无因咖啡 描述 = " + order2.getDes());
		
		order2 = new Milk(order2);
		
		System.out.println("order2 无因咖啡 加入一份牛奶 费用 =" + order2.cost());
		System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDes());	
	}
}
public class DeCaf extends Coffee {
    
    public DeCaf() {
    
        setDes(" 无因咖啡 "); 
        setPrice(1.0f); 
    } 
}
public class Decorator extends Drink {
   
	private Drink obj;
	
	public Decorator(Drink obj) {
    //组合
		// TODO Auto-generated constructor stub
		this.obj = obj;
	}
	
	@Override
	public float cost() {
   
		// TODO Auto-generated method stub
		// getPrice 自己价格
		return super.getPrice() + obj.cost();
	}
	
	@Override
	public String getDes() {
   
		// TODO Auto-generated method stub
		// obj.getDes() 输出被装饰者的信息
		return des + " " + getPrice() + " && " + obj.getDes();
	}
}
public abstract class Drink {
   

	public String des; // 描述
	private float price = 0.0f;
	public String getDes() {
   
		return des;
	}
	public void setDes(String des) {
   
		this.des = des;
	}
	public float getPrice() {
   
		return price;
	}
	public void setPrice(float price) {
   
		this.price = price;
	}
	
	//计算费用的抽象方法
	//子类来实现
	public abstract float cost();
}
public class Espresso extends Coffee {
    
    public Espresso() {
    
        setDes(" 意大利咖啡 "); 
        setPrice(6.0f);         
    } 
}
public class LongBlack extends Coffee {
    
    public LongBlack() {
   
        setDes(" longblack "); 
        setPrice(5.0f); 
    } 
}
public class Milk extends Decorator {
    
    public Milk(Drink obj) {
    
        super(obj); 
        // TODO Auto-generated constructor stub 
        setDes(" 牛奶 "); 
        setPrice(2.0f); 
    } 
}
public class ShortBlack extends Coffee{
    
    public ShortBlack() {
    
        setDes(" shortblack "); 
        setPrice(4.0f); 
    }
}
public class Soy extends Decorator{
    
    public Soy(Drink obj) {
    
        super(obj); 
        // TODO Auto-generated constructor stub 
        setDes(" 豆浆 "); 
        setPrice(1.5f); 
    } 
}

应用源码分析

Java 的 IO 结构,FilterInputStream 就是一个装饰者

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e8lZl2jb-1648380440781)(Pic/1648113755985.png)]

public class Decorator {
    
    public static void main(String[] args) throws Exception{
    
        // TODO Auto-generated method stub 
        //说明 
        //1. InputStream 是抽象类, 类似我们前面讲的 Drink 
        //2. FileInputStream 是 InputStream 子类,类似我们前面的 DeCaf, LongBlack 
        //3. FilterInputStream 是 InputStream 子类:类似我们前面 的 Decorator 修饰者 
        //4. DataInputStream 是 FilterInputStream 子类,具体的修饰者,类似前面的 Milk, Soy 等 
        //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(); 
    } 
}

代理模式

基本介绍

  1. 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处 是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

  2. 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象

  3. 代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK 代理、接口代理)和 Cglib 代理 (可以在内存 动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。

  4. 代理模式示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ns13mGv-1648380440782)(Pic/1648113895792.png)]

静态代理

静态代码模式的基本介绍

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

应用实例

具体要求

  1. 定义一个接口:ITeacherDao

  2. 目标对象 TeacherDAO 实现接口 ITeacherDAO

  3. 使用静态代理方式,就需要在代理对象 TeacherDAOProxy 中也实现 ITeacherDAO

  4. 调用的时候通过调用代理对象的方法来调用目标对象.

  5. 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法

思路分析图解(类图)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-61SI4nVf-1648380440783)(Pic/1648113974541.png)]

public class Client {
   
    public static void main(String[] args) {
    
        // TODO Auto-generated method stub 
        //创建目标对象(被代理对象) 
        TeacherDao teacherDao = new TeacherDao(); 
        
        //创建代理对象, 同时将被代理对象传递给代理对象 
        TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao); 
        
        //通过代理对象,调用到被代理对象的方法 
        //即:执行的是代理对象的方法,代理对象再去调用目标对象的方法 
        teacherDaoProxy.teach(); 
    } 
}
//接口 
public interface ITeacherDao {
    
    void teach(); // 授课的方法 
}
public class TeacherDao implements ITeacherDao {
   
    @Override public void teach() {
    
        // TODO Auto-generated method stub 
        System.out.println(" 老师授课中 。。。。。"); 
    } 
}
//代理对象,静态代理
public class TeacherDaoProxy implements ITeacherDao{
   
	
	private ITeacherDao target; // 目标对象,通过接口来聚合
	
	//构造器
	public TeacherDaoProxy(ITeacherDao target) {
   
		this.target = target;
	}
    
	@Override
	public void teach() {
   
		// TODO Auto-generated method stub
		System.out.println("开始代理 完成某些操作。。。。。 ");//方法
		target.teach();
		System.out.println("提交。。。。。");//方法
	}
}

静态代理优缺点

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

  2. 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类

  3. 一旦接口增加方法,目标对象与代理对象都要维护

动态代理

动态代理模式的基本介绍

  1. 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理

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

  3. 动态代理也叫做:JDK 代理、接口代理

JDK 中生成代理对象的 API

  1. 代理类所在包:java.lang.reflect.Proxy

  2. JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h ) 

动态代理应用实例

应用实例要求

将前面的静态代理改进成动态代理模式(即:JDK 代理模式)

思路图解(类图)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1xcs7rdR-1648380440783)(Pic/1648114262145.png)]

public class Client {
    
    public static void main(String[] args) {
    
        // TODO Auto-generated method stub 
        //创建目标对象 
        ITeacherDao target = new TeacherDao(); 
        
        //给目标对象,创建代理对象, 可以转成 ITeacherDao
        ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance(); 
        
        // proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象 
        System.out.println("proxyInstance=" + proxyInstance.getClass()); 
        
        //通过代理对象,调用目标对象的方法 
        //proxyInstance.teach(); 
        proxyInstance.sayHello(" tom "); 
    } 
}
//接口 
public interface ITeacherDao {
    
    void teach(); // 授课方法 
    void sayHello(String name); 
}
public class ProxyFactory {
   

	//维护一个目标对象 , Object
	private Object target;

	//构造器 , 对target 进行初始化
	public ProxyFactory(Object target) {
   
		
		this.target = target;
	} 
	
	//给目标对象 生成一个代理对象
	public Object getProxyInstance() {
   
		
		//说明
		/* * public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) //1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定 //2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型 //3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方***把当前执行的目标对象方法作为参数传入 */
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), 
				new InvocationHandler() {
   
					
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
						// TODO Auto-generated method stub
						System.out.println("JDK代理开始~~");
						//反射机制调用目标对象的方法
						Object returnVal = method.invoke(target, args);
						System.out.println("JDK代理提交");
						return returnVal;
					}
				}); 
	}	
}
public class TeacherDao implements ITeacherDao {
    
    @Override public void teach() {
   
    // TODO Auto-generated method stub 
        System.out.println(" 老师授课中.... "); 
    }
    
    @Override public void sayHello(String name) {
    
        // TODO Auto-generated method stub 
        System.out.println("hello " + name); 
    } 
}

Cglib代理

Cglib 代理模式的基本介绍

  1. 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实 现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理

  2. Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代 理归属到动态代理。

  3. Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的 框架使用,例如 Spring AOP,实现方法拦截

  4. 在 AOP 编程中如何选择代理模式:

目标对象需要实现接口,用 JDK 代理 
目标对象不需要实现接口,用 Cglib 代理 
  1. Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类

Cglib 代理模式实现步骤

  1. 需要引入 cglib 的 jar 文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxEORN3f-1648380440784)(Pic/1648114699376.png)]

  1. 在内存中动态构建子类,注意代理的类不能为 final,否则报错 java.lang.IllegalArgumentException:

  2. 目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

Cglib 代理模式应用实例

应用实例要求 将前面的案例用 Cglib 代理模式实现

思路图解(类图)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eHYtJstz-1648380440786)(Pic/1648114763181.png)]

public class Client {
    
    public static void main(String[] args) {
    
        // TODO Auto-generated method stub 
        //创建目标对象 
        TeacherDao target = new TeacherDao(); 
        //获取到代理对象,并且将目标对象传递给代理对象 
        TeacherDao 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 对象的代理对象
	public Object getProxyInstance() {
   
		//1. 创建一个工具类
		Enhancer enhancer = new Enhancer();
		//2. 设置父类
		enhancer.setSuperclass(target.getClass());
		//3. 设置回调函数
		enhancer.setCallback(this);
		//4. 创建子类对象,即代理对象
		return enhancer.create();
		
	}
	
	//重写 intercept 方***调用目标对象的方法
	@Override
	public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
   
		// TODO Auto-generated method stub
		System.out.println("Cglib代理模式 ~~ 开始");
		Object returnVal = method.invoke(target, args);
		System.out.println("Cglib代理模式 ~~ 提交");
		return returnVal;
	}
}
public class TeacherDao {
    
    public String teach() {
    
        System.out.println(" 老师授课中 , 我是 cglib 代理,不需要实现接口 ");
        return "hello"; 
    } 
}

代理模式几种变体

  1. 防火墙代理 内网通过代理穿透防火墙,实现对公网的访问。

  2. 缓存代理 比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok,如果取不到资源,再到公网或者数据 库取,然后缓存。

  3. 远程代理 远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信 息。

  4. 同步代理:主要使用在多线程编程中,完成多线程间同步工作 同步代理:主要使用在多线程编程中,完成多线程间同步工作

观察者模式

天气预报项目需求,具体要求如下:

  1. 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。

  2. 需要设计开放型 API,便于其他第三方也能接入气象站获取数据。

  3. 提供温度、气压和湿度的接口

  4. 测量数据更新时,要能实时的通知给第三方

天气预报设计方案 1-普通方案

WeatherData 类

传统的设计方案

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TmGMROWQ-1648380440787)(Pic/1648120840923.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2E0nNshB-1648380440788)(Pic/1648120860182.png)]

public class Client {
   
	public static void main(String[] args) {
   
		//创建接入方 currentConditions
		CurrentConditions currentConditions = new CurrentConditions();
		//创建 WeatherData 并将 接入方 currentConditions 传递到 WeatherData中
		WeatherData weatherData = new WeatherData(currentConditions);
		
		//更新天气情况
		weatherData.setData(30, 150, 40);
		
		//天气情况变化
		System.out.println("============天气情况变化=============");
		weatherData.setData(40, 160, 20);
	}
}
/** * 显示当前天气情况(可以理解成是气象站自己的网站) * @author Administrator * */
public class CurrentConditions {
   
	// 温度,气压,湿度
	private float temperature;
	private float pressure;
	private float humidity;

	//更新 天气情况,是由 WeatherData 来调用,我使用推送模式
	public void update(float temperature, float pressure, float humidity) {
   
		this.temperature = temperature;
		this.pressure = pressure;
		this.humidity = humidity;
		display();
	}

	//显示
	public void display() {
   
		System.out.println("***Today mTemperature: " + temperature + "***");
		System.out.println("***Today mPressure: " + pressure + "***");
		System.out.println("***Today mHumidity: " + humidity + "***");
	}
}
/** * 类是核心 * 1. 包含最新的天气情况信息 * 2. 含有 CurrentConditions 对象 * 3. 当数据有更新时,就主动的调用 CurrentConditions对象update方法(含 display), 这样他们(接入方)就看到最新的信息 * @author Administrator * */
public class WeatherData {
   
	private float temperatrue;
	private float pressure;
	private float humidity;
	private CurrentConditions currentConditions;
	//加入新的第三方

	public WeatherData(CurrentConditions currentConditions) {
   
		this.currentConditions = currentConditions;
	}

	public float getTemperature() {
   
		return temperatrue;
	}

	public float getPressure() {
   
		return pressure;
	}

	public float getHumidity() {
   
		return humidity;
	}

	public void dataChange() {
   
		//调用 接入方的 update
		currentConditions.update(getTemperature(), getPressure(), getHumidity());
	}

	//当数据有更新时,就调用 setData
	public void setData(float temperature, float pressure, float humidity) {
   
		this.temperatrue = temperature;
		this.pressure = pressure;
		this.humidity = humidity;
		//调用dataChange, 将最新的信息 推送给 接入方 currentConditions
		dataChange();
	}
}

问题分析

  1. 其他第三方接入气象站获取数据的问题

  2. 无法在运行时动态的添加第三方 (新浪网站)

  3. 违反 ocp 原则=>观察者模式

//在 WeatherData 中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到 dataChange, 不 利于维护,也不是动态加入

public void dataChange() {
    
    currentConditions.update(getTemperature(), getPressure(), getHumidity()); 
}

观察者模式原理

  1. 观察者模式类似订牛奶业务

  2. 奶站/气象局:Subject

  3. 用户/第三方网站:Observer

Subject:登记注册、移除和通知

  1. registerObserver 注册

  2. removeObserver 移除

  3. notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送, 看具体需求定

Observer:接收输入

观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为 Subject,依赖的对象为 Observer,Subject 通知 Observer 变化,比如这里的奶站是 Subject,是 1 的一方。用户时 Observer,是多的一方。

应用实例

观察者模式解决天气预报需求

类图说明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ZpkljoM-1648380440789)(Pic/1648121155891.png)]

public class BaiduSite implements Observer {
   

	// 温度,气压,湿度
	private float temperature;
	private float pressure;
	private float humidity;

	// 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
	public void update(float temperature, float pressure, float humidity) {
   
		this.temperature = temperature;
		this.pressure = pressure;
		this.humidity = humidity;
		display();
	}

	// 显示
	public void display() {
   
		System.out.println("===百度网站====");
		System.out.println("***百度网站 气温 : " + temperature + "***");
		System.out.println("***百度网站 气压: " + pressure + "***");
		System.out.println("***百度网站 湿度: " + humidity + "***");
	}
}
public class Client {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		//创建一个WeatherData
		WeatherData weatherData = new WeatherData();
		
		//创建观察者
		CurrentConditions currentConditions = new CurrentConditions();
		BaiduSite baiduSite = new BaiduSite();
		
		//注册到weatherData
		weatherData.registerObserver(currentConditions);
		weatherData.registerObserver(baiduSite);
		
		//测试
		System.out.println("通知各个注册的观察者, 看看信息");
		weatherData.setData(10f, 100f, 30.3f);
		
		
		weatherData.removeObserver(currentConditions);
		//测试
		System.out.println();
		System.out.println("通知各个注册的观察者, 看看信息");
		weatherData.setData(10f, 100f, 30.3f);
	}
}
public class CurrentConditions implements Observer {
   

	// 温度,气压,湿度
	private float temperature;
	private float pressure;
	private float humidity;

	// 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
	public void update(float temperature,float pressure,float humidity) {
   
		this.temperature = temperature;
		this.pressure = pressure;
		this.humidity = humidity;
		display();
	}

	// 显示
	public void display() {
   
		System.out.println("***Today mTemperature: " + temperature + "***");
		System.out.println("***Today mPressure: " + pressure + "***");
		System.out.println("***Today mHumidity: " + humidity + "***");
	}
}
//观察者接口,有观察者来实现
public interface Observer {
   

	public void update(float temperature, float pressure, float humidity);
}
//接口, 让WeatherData 来实现 
public interface Subject {
   
	
	public void registerObserver(Observer o);
	public void removeObserver(Observer o);
	public void notifyObservers();
}
/** * 类是核心 * 1. 包含最新的天气情况信息 * 2. 含有 观察者集合,使用ArrayList管理 * 3. 当数据有更新时,就主动的调用 ArrayList, 通知所有的(接入方)就看到最新的信息 * @author Administrator * */
public class WeatherData implements Subject {
   
	private float temperatrue;
	private float pressure;
	private float humidity;
	//观察者集合
	private ArrayList<Observer> observers;
	
	//加入新的第三方

	public WeatherData() {
   
		observers = new ArrayList<Observer>();
	}

	public float getTemperature() {
   
		return temperatrue;
	}

	public float getPressure() {
   
		return pressure;
	}

	public float getHumidity() {
   
		return humidity;
	}

	public void dataChange() {
   
		//调用 接入方的 update
		
		notifyObservers();
	}

	//当数据有更新时,就调用 setData
	public void setData(float temperature, float pressure, float humidity) {
   
		this.temperatrue = temperature;
		this.pressure = pressure;
		this.humidity = humidity;
		//调用dataChange, 将最新的信息 推送给 接入方 currentConditions
		dataChange();
	}

	//注册一个观察者
	@Override
	public void registerObserver(Observer o) {
   
		// TODO Auto-generated method stub
		observers.add(o);
	}

	//移除一个观察者
	@Override
	public void removeObserver(Observer o) {
   
		// TODO Auto-generated method stub
		if(observers.contains(o)) {
   
			observers.remove(o);
		}
	}

	//遍历所有的观察者,并通知
	@Override
	public void notifyObservers() {
   
		// TODO Auto-generated method stub
		for(int i = 0; i < observers.size(); i++) {
   
			observers.get(i).update(this.temperatrue, this.pressure, this.humidity);
		}
	}
}

观察者模式的好处

  1. 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。 2) 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类 WeatherData 不会修改代码, 遵守了 ocp 原则。

应用源码分析

  1. Jdk 的 Observable 类就使用了观察者模式

  2. 代码分析+模式角色分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WPZvtAN4-1648380440792)(Pic/1648121364784.png)]

  1. 模式角色分析

Observable 的作用和地位等价于 我们前面讲过 Subject

Observable 是类,不是接口,类中已经实现了核心的方法 ,即管理 Observer 的方法 add… delete … notify…

Observer 的作用和地位等价于我们前面讲过的 Observer, 有 update

Observable 和 Observer 的使用方法和前面讲过的一样,只是 Observable 是类,通过继承来实现观察者模式

策略模式

编写鸭子项目,具体要求如下:

  1. 有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如 叫、飞行等)

  2. 显示鸭子的信息

传统方案解决鸭子问题的分析和代码实现

  1. 传统的设计方案(类图)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J6SYmJwY-1648380440793)(Pic/1648176975014.png)]

public class Client {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		//测试
	}
}
public abstract class Duck {
   

	public Duck() {
   
	
	}

	public abstract void display();//显示鸭子信息
	
	public void quack() {
   
		System.out.println("鸭子嘎嘎叫~~");
	}
	
	public void swim() {
   
		System.out.println("鸭子会游泳~~");
	}
	
	public void fly() {
   
		System.out.println("鸭子会飞翔~~~");
	}
}
public class PekingDuck extends Duck {
   

	@Override
	public void display() {
   
		// TODO Auto-generated method stub
		System.out.println("~~北京鸭~~~");
	}
	
	//因为北京鸭不能飞翔,因此需要重写fly
	@Override
	public void fly() {
   
		// TODO Auto-generated method stub
		System.out.println("北京鸭不能飞翔");
	}
}
public class ToyDuck extends Duck{
   

	@Override
	public void display() {
   
		// TODO Auto-generated method stub
		System.out.println("玩具鸭");
	}

	//需要重写父类的所有方法
	public void quack() {
   
		System.out.println("玩具鸭不能叫~~");
	}
	
	public void swim() {
   
		System.out.println("玩具鸭不会游泳~~");
	}
	
	public void fly() {
   
		System.out.println("玩具鸭不会飞翔~~~");
	}
}
public class WildDuck extends Duck {
   

	@Override
	public void display() {
   
		// TODO Auto-generated method stub
		System.out.println(" 这是野鸭 ");
	}
}

传统的方式实现的问题分析和解决方案

  1. 其它鸭子,都继承了 Duck 类,所以 fly 让所有子类都会飞了,这是不正确的

  2. 上面说的 1 的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有 溢出效应

  3. 为了改进 1 问题,我们可以通过覆盖 fly 方法来解决 => 覆盖解决

  4. 问题又来了,如果我们有一个玩具鸭子 ToyDuck, 这样就需要 ToyDuck 去覆盖 Duck 的所有实现的方法 => 解 决思路 -》 策略模式 (strategy pattern)

策略模式介绍

  1. 策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式 让算法的变化独立于使用算法的客户

  2. 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体 类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。

策略模式原理类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wt8CN7Kv-1648380440793)(Pic/1648177291881.png)]

说明:从上图可以看到,客户 context 有成员变量 strategy 或者其他的策略接口 ,至于需要使用到哪个策略,我们可以在构造器中指定

应用实例

策略模式解决鸭子问题

  1. 应用实例要求

编写程序完成前面的鸭子项目,要求使用策略模式

  1. 思路分析(类图)

策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是: 分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T0wQat8l-1648380440794)(Pic/1648177364606.png)]

public class BadFlyBehavior implements FlyBehavior {
   

	@Override
	public void fly() {
   
		// TODO Auto-generated method stub
		System.out.println(" 飞翔技术一般 ");
	}
}
public class Client {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		WildDuck wildDuck = new WildDuck();
		wildDuck.fly();//
		
		ToyDuck toyDuck = new ToyDuck();
		toyDuck.fly();
		
		PekingDuck pekingDuck = new PekingDuck();
		pekingDuck.fly();
		
		//动态改变某个对象的行为, 北京鸭 不能飞
		pekingDuck.setFlyBehavior(new NoFlyBehavior());
		System.out.println("北京鸭的实际飞翔能力");
		pekingDuck.fly();
	}
}
public abstract class Duck {
   

	//属性, 策略接口
	FlyBehavior flyBehavior;
	//其它属性<->策略接口
	QuackBehavior quackBehavior;
	
	public Duck() {
   
	
	}

	public abstract void display();//显示鸭子信息
	
	public void quack() {
   
		System.out.println("鸭子嘎嘎叫~~");
	}
	
	public void swim() {
   
		System.out.println("鸭子会游泳~~");
	}
	
	public void fly() {
   
		
		//改进
		if(flyBehavior != null) {
   
			flyBehavior.fly();
		}
	}

	public void setFlyBehavior(FlyBehavior flyBehavior) {
   
		this.flyBehavior = flyBehavior;
	}
	
	public void setQuackBehavior(QuackBehavior quackBehavior) {
   
		this.quackBehavior = quackBehavior;
	}	
}
public interface FlyBehavior {
   
	void fly(); // 子类具体实现
}
public class GoodFlyBehavior implements FlyBehavior {
   

	@Override
	public void fly() {
   
		// TODO Auto-generated method stub
		System.out.println(" 飞翔技术高超 ~~~");
	}
}
public class NoFlyBehavior implements FlyBehavior{
   

	@Override
	public void fly() {
   
		// TODO Auto-generated method stub
		System.out.println(" 不会飞翔 ");
	}
}
public class PekingDuck extends Duck {
   
	
	//假如北京鸭可以飞翔,但是飞翔技术一般
	public PekingDuck() {
   
		// TODO Auto-generated constructor stub
		flyBehavior = new BadFlyBehavior();
		
	}
	
	@Override
	public void display() {
   
		// TODO Auto-generated method stub
		System.out.println("~~北京鸭~~~");
	}
}
public interface QuackBehavior {
   
	void quack();//子类实现
}
public class ToyDuck extends Duck{
   

	
	public ToyDuck() {
   
		// TODO Auto-generated constructor stub
		flyBehavior = new NoFlyBehavior();
	}
	
	@Override
	public void display() {
   
		// TODO Auto-generated method stub
		System.out.println("玩具鸭");
	}

	//需要重写父类的所有方法
	
	public void quack() {
   
		System.out.println("玩具鸭不能叫~~");
	}
	
	public void swim() {
   
		System.out.println("玩具鸭不会游泳~~");
	}
}
public class WildDuck extends Duck {
   
	
	//构造器,传入FlyBehavor 的对象
	public  WildDuck() {
   
		// TODO Auto-generated constructor stub
		flyBehavior = new GoodFlyBehavior();
	}
	
	@Override
	public void display() {
   
		// TODO Auto-generated method stub
		System.out.println(" 这是野鸭 ");
	}
}

应用源码分析

策略模式在 JDK-Arrays 应用的源码分析

JDK 的 Arrays 的 Comparator 就使用了策略模式

public class Strategy {
   

	public static void main(String[] args) {
   
		// TODO Auto-generated method stub
		//数组
		Integer[] data = {
    9, 1, 2, 8, 4, 3 };
		// 实现降序排序,返回-1放左边,1放右边,0保持不变
		
		// 说明
		// 1. 实现了 Comparator 接口(策略接口) , 匿名类 对象 new Comparator<Integer>(){..}
		// 2. 对象 new Comparator<Integer>(){..} 就是实现了 策略接口 的对象
		// 3. public int compare(Integer o1, Integer o2){} 指定具体的处理方式
		Comparator<Integer> comparator = new Comparator<Integer>() {
   
			public int compare(Integer o1, Integer o2) {
   
				if (o1 > o2) {
   
					return -1;
				} else {
   
					return 1;
				}
			};
		};
		
		// 说明
		/* * public static <T> void sort(T[] a, Comparator<? super T> c) { if (c == null) { sort(a); //默认方法 } else { if (LegacyMergeSort.userRequested) legacyMergeSort(a, c); //使用策略对象c else // 使用策略对象c TimSort.sort(a, 0, a.length, c, null, 0, 0); } } */
		//方式1 
		Arrays.sort(data, comparator);
		
		System.out.println(Arrays.toString(data)); // 降序排序
		
		//方式2- 同时lambda 表达式实现 策略模式
		Integer[] data2 = {
    19, 11, 12, 18, 14, 13 };
		
		Arrays.sort(data2, (var1, var2) -> {
   
			if(var1.compareTo(var2) > 0) {
   
				return -1;
			} else {
   
				return 1;
			}
		});
		
		System.out.println("data2=" + Arrays.toString(data2));
	}
}

策略模式的注意事项和细节

  1. 策略模式的关键是:分析项目中变化部分与不变部分

  2. 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性

  3. 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为) 即可,避免了使用多重转移语句(if…else if…else)

  4. 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的 Strategy 类中使得你可以独立于其 Context 改 变它,使它易于切换、易于理解、易于扩展

  5. 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞