通过本文章,你可以了解到常用的设计模式,本文的设计模式都是通过java语言进行讲解的,这也是我个人的一个学习笔记,设计模式以及数据结构和算法可能实际开发中用不到,但是这是程序员的基本功,这也就是你看框架源代码的时候头晕,看不懂,不知道别人到底写的什么玩意儿。那么你该静下心来好好学习基础知识了,记住一句话,罗马不少一天建成的,万丈高楼平地其,一切都以基础为准,框架层出不穷,但是万变不离其宗,都是以我们的核心基础知识来的。学完设计模式,你对写代码的一个水平会有一个很好的提升。
设计模式的目的
编写软件过程中,程序员面临着来自 耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性 等多方面的挑战, 设计模式是为了让程序(软件),具有如下更好的特性
设计模式在软件中哪里?
面向对象(oo)=>功能模块[设计模式+算法(数据结构)]=>框架[使用到多种设计模式]=> 架构 [服务器集群]
设计模式原则,其实就是程序员在编程时,应当遵守的原则, 也是各种设计模式的基础(即: 设计模式为什么这样设计的依据)
设计模式常用的七大原则有:
对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误, 所以需要将类A的粒度分解为A1, A2
就比如说我们的javaWeb开发的到层,一个UserDao就对用户表增删改查,一个OrderDao就对我们的订单表增删改查,如果UserDao和OrderDao的功能都放到一个类只能,那么就可能会出现,UserDao出现错误导致OrderDao也不能用。所以我们就需要使用单一职责,让一个类做一个事情,降低代码的耦合度,使其代码之间的依赖性不少那么强。
单一职责案例
错误案例1:
这的逻辑是不对的,造成代码的逻辑混乱
/** 单一职责
* @author compass
* @version 1.0
* @date 2021-07-04 0:34
*/
public class SingleResponsibility1 {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("摩托车");
vehicle.run("汽车");
vehicle.run("飞机");
}
}
/**
* 交通工具类Vehicle
* 1. 这种方式违反了单一职责
* 2.解决的方案非常简单,根据不同的交通工具分解未不同的类 提供run方法
*/
class Vehicle{
public void run(String vehicle){
System.out.println(vehicle+"在公路上运行");
}
}
解决思路:
将业务进行拆分,就是根据不同的业务,构建出不同的类,让不同的类去做不同的事情,让他们互不影响。
正确案例2:
但是这样做改动很大,即将类进行分解,同时会修改客户端,接下来我们看第三种改进的方式。
/** 单一职责(正确示范)
* @author compass
* @version 1.0
* @date 2021-07-04 0:34
*/
public class SingleResponsibility2 {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("汽车");
WaterVehicle waterVehicle = new WaterVehicle();
waterVehicle.run("轮船");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飞机");
}
}
/**
* 公路类
*/
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+"在水中运行");
}
}
改进方案3:
直接修改Vehicle类,改动的代码比较少
/** 单一职责(错误示范)
* @author compass
* @version 1.0
* @date 2021-07-04 0:34
*/
public class SingleResponsibility3 {
public static void main(String[] args) {
Vehicle3 vehicle = new Vehicle3();
vehicle.runARoad("汽车");
vehicle.runAir("飞机");
vehicle.runWater("轮船");
}
}
/**
* 交通工具类Vehicle3
* 1. 这种方式虽然违反了单一职责,但是在方法级别上并没有违法单一职责,仍然遵守我们的单一职责
* 2.在业务量很小的时候,可以这样,但是业务量很多,而且不同类型很多,那就不太建议了
*/
class Vehicle3{
public void runAir(String vehicle){
System.out.println(vehicle+"在天上运行");
}
public void runWater(String vehicle){
System.out.println(vehicle+"在水里运行");
}
public void runARoad(String vehicle){
System.out.println(vehicle+"在公路上运行");
}
}
单一职责原则的注意事项和原则
基本介绍:客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上
举例说明
案例1:
** 接口隔离原则(错误示范)
* @author compass
* @version 1.0
* @date 2021-07-04 11:35
*/
public class Segregation1 {
}
/**
* 接口
*/
interface Interface1{
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
/**
* 实现interface1接口 并且实现其中的五个方法
*/
class B1 implements Interface1{
@Override
public void operation1() {
System.out.println("B1 中实现了operation1");
}
@Override
public void operation2() {
System.out.println("B1 中实现了operation2");
}
@Override
public void operation3() {
System.out.println("B1 中实现了operation3");
}
@Override
public void operation4() {
System.out.println("B1 中实现了operation4");
}
@Override
public void operation5() {
System.out.println("B1 中实现了operation5");
}
}
/**
* 实现interface1接口 并且实现其中的五个方法
*/
class D1 implements Interface1{
@Override
public void operation1() {
System.out.println("D1 中实现了operation1");
}
@Override
public void operation2() {
System.out.println("D2 中实现了operation2");
}
@Override
public void operation3() {
System.out.println("D3 中实现了operation3");
}
@Override
public void operation4() {
System.out.println("D4 中实现了operation4");
}
@Override
public void operation5() {
System.out.println("D5 中实现了operation5");
}
}
/**
* A类通过接口 Interface1 依赖使用B类 但是只会使用到 1,2,3,方法
*/
class A1{
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface1 i){
i.operation2();
}
public void depend3(Interface1 i){
i.operation3();
}
}
/**
* C类通过接口 Interface1 依赖使用D类 但是只会使用到 1,4,2,方法
*/
class C1{
public void depend1(Interface1 i){
i.operation1();
}
public void depend4(Interface1 i){
i.operation4();
}
public void depend5(Interface1 i){
i.operation5();
}
}
案例2:
分析传统方法的问题,使用接口隔离原则改进程序结构
/** 接口隔离原则(正确示范)
* @author compass
* @version 1.0
* @date 2021-07-04 11:35
*/
public class Segregation2 {
public static void main(String[] args) {
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:
未遵循依赖倒置原则,Person 类与 Email 类耦合,如果我们还想获取其他消息,比如微信、短信、QQ 等、则需要添加相应的实现方法
/** 接口依赖倒置原则
* @author compass
* @version 1.0
* @date 2021-07-04 13:37
*/
public class DependencyInversion {
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. 如果我们获取的对象是 微信,短信等等,则新增类,同时Peron也要增加相应的接收方法
//3. 解决思路:引入一个抽象的接口IReceiver, 表示接收者, 这样Person类与接口IReceiver发生依赖
// 因为Email, WeiXin 等等属于接收的范围,他们各自实现IReceiver 接口就ok, 这样我们就符号依赖倒转原则
class Person {
public void receive(Email email) {
System.out.println(email.getInfo());
}
}
案例2:
引入一个抽象的接口 IReceiver,表示接收者(Email、微信、短信、QQ 等),接受者分别实现 IReceiver 接口中的方法,实现各自接收消息的逻辑,Person 类与 IReceiver 接口发生依赖,达到接收消息的功能
/** 接口依赖倒置原则
* @author compass
* @version 1.0
* @date 2021-07-04 13:37
*/
public class DependencyInversion2 {
public static void main(String[] args) {
Person2 person2 = new Person2();
person2.receive(new Email2());
person2.receive(new WeiXin());
}
}
interface IReceiver{
public String getInfo();
}
class Email2 implements IReceiver {
public String getInfo() {
return "电子邮件信息: hello,world";
}
}
//增加微信
class WeiXin implements IReceiver {
public String getInfo() {
return "微信信息: hello,ok";
}
}
class Person2 {
public void receive( IReceiver receiver) {
System.out.println(receiver.getInfo());
}
}
依赖关系传递的三种方式和应用案例:
1、接口传递
public class DependencyPass {
public static void main(String[] args) {
// 通过接口传递
ChangHong changHong = new ChangHong();
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.open(changHong);
}
}
// 方式1: 通过接口传递实现依赖
// 开关的接口
interface IOpenAndClose {
public void open(ITV tv); // 抽象方法,接收接口
}
// ITV接口
interface ITV {
public void play();
}
// 长虹电视:实现 ITV 接口
class ChangHong implements ITV {
@Override
public void play() {
System.out.println("长虹电视机,打开");
}
}
// 设备播放类:实现 IOpenAndClose 接口,调用接口 IITV 的 play() 方法实现播放功能(通过接口注入)
class OpenAndClose implements IOpenAndClose {
public void open(ITV tv) {
tv.play();
}
}
2、构造方法传递
public class DependencyPass {
public static void main(String[] args) {
// 通过构造器进行依赖传递
ChangHong changHong = new ChangHong();
OpenAndClose openAndClose = new OpenAndClose(changHong);
openAndClose.open();
}
}
// 方式2: 通过构造方法依赖传递
interface IOpenAndClose {
public void open(); // 抽象方法
}
interface ITV { // ITV接口
public void play();
}
// 长虹电视:实现 ITV 接口
class ChangHong implements ITV {
@Override
public void play() {
System.out.println("长虹电视机,打开");
}
}
class OpenAndClose implements IOpenAndClose {
public ITV tv; // 成员变量
public OpenAndClose(ITV tv) { // 通过构造器注入实现了 ITV 接口的对象
this.tv = tv;
}
public void open() {
this.tv.play();
}
}
3、setter 方式传递
public class DependencyPass {
public static void main(String[] args) {
// 通过setter方法进行依赖传递
ChangHong changHong = new ChangHong();
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.setTv(changHong);
openAndClose.open();
}
}
// 方式3 , 通过setter方法传递
interface IOpenAndClose {
public void open(); // 抽象方法
public void setTv(ITV tv); // 通过 setter 方法注入
}
interface ITV { // ITV接口
public void play();
}
// 长虹电视:实现 ITV 接口
class ChangHong implements ITV {
@Override
public void play() {
System.out.println("长虹电视机,打开");
}
}
class OpenAndClose implements IOpenAndClose {
private ITV tv;
// 通过 setYv() 方法注入实现了 ITV 接口的对象实例
public void setTv(ITV tv) {
this.tv = tv;
}
public void open() {
this.tv.play();
}
}
总结:无论通过什么方法,目的都是要将实现了接口的具体实现类注入到调用者类中
自己想的案例:学了某个东西之后,最好自己去把这个思想,应用到具体的案例中,无论对错,首先你要试着去尝试应用,这样才不至于纸上谈兵,理解也更加透彻。
1.OO中的继承性的思考和说明
继承包含这样一层含义:父类中凡是已经实现好的方法, 实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障
问题提出:在编程中,如何正确的使用继承? => 里氏替换原则
2.里氏替换原则的基本介绍
我们也可以通过提升的方法,来尽量满足里氏替换原则,假设现在有两个类,A 类和 B 类,如果 B 类继承 A 类,需要重写 A 类中的某些方法,那么,我们在 A 类 和 B 类之上,再抽取出一个更加通用的父类 CommonSuper,让 A 类和 B 类同时去继承CommonSuper,这样 B 类就无须重写 A 类中的某些方法,达到基类的引用对子类对象透明的效果
案例1:
/**
* @author compass
* @version 1.0
* @date 2021-07-04 16:23
*/
@Deprecated
public class Liskov {
public static void main(String[] args) {
A a = new A();
System.out.println("11-3=" + a.function1(11, 3));
System.out.println("1-8=" + a.function1(1, 8));
System.out.println("-----------------------");
B b = new B();
System.out.println("11-3=" + b.function1(11, 3));// 这里本意是求出11-3,结果变成了11+3
System.out.println("1-8=" + b.function1(1, 8));// 这里本意是求出1-8,结果变成了1+8
System.out.println("11+3+9=" + b.sum(11, 3));
}
}
// A类
class A {
// 返回两个数的差
public int function1(int num1, int num2) {
return num1 - num2;
}
}
// B类继承了A
// 增加了一个新功能:完成两个数相加,然后和9求和
class B extends A {
// 这里,重写了A类的方法, 可能是无意识
public int function1(int a, int b) {
return a + b;
}
public int sum(int a, int b) {
return function1(a, b) + 9;
}
}
原因分析与解决方法
我们发现原来运行正常的相减功能发生了错误。原因就是类B无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候
通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替
将类 B 的级别提升至与类 A 平级,他们有一个共同的父类 Base,这样就不会出现类 B 重写类 A 中方法的问题,此时基类的引用能够透明地使用子类的对象
/**
* @author compass
* @version 1.0
* @date 2021-07-04 16:29
*/
public class Liskov2 {
public static void main(String[] args) {
A2 a = new A2();
System.out.println("11-3=" + a.func1(11, 3));
System.out.println("1-8=" + a.func1(1, 8));
System.out.println("-----------------------");
B2 b = new B2();
// 因为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 A2 extends Base {
// 返回两个数的差
public int func1(int num1, int num2) {
return num1 - num2;
}
}
// B类继承了A
// 增加了一个新功能:完成两个数相加,然后和9求和
class B2 extends Base {
// 如果B需要使用A类的方法,使用组合关系
private A2 a = new A2();
// 这里虽然方法名是 fun1(),但由于类 B 集成于类 Base,已和类 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:
未遵循开闭原则,导致新增一个图形类时,需要在【使用方 GraphicEditor】中添加很多代码
/** 开闭原则(错误案例)
* @author compass
* @version 1.0
* @date 2021-07-04 19:18
*/
public class OcpTest1 {
public static void main(String[] args) {
// 使用看看存在的问题
GraphicEditor1 graphicEditor = new GraphicEditor1();
graphicEditor.drawShape(new Rectangle1());
graphicEditor.drawShape(new Circle1());
graphicEditor.drawShape(new Triangle1());
}
}
//这是一个用于绘图的类 [使用方,需使用图形绘图]
class GraphicEditor1 {
// 接收Shape对象,然后根据type,来绘制不同的图形
public void drawShape(Shape1 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(Shape1 r) {
System.out.println(" 绘制矩形 ");
}
// 绘制圆形
public void drawCircle(Shape1 r) {
System.out.println(" 绘制圆形 ");
}
// 绘制三角形
public void drawTriangle(Shape1 r) {
System.out.println(" 绘制三角形 ");
}
}
//Shape类,基类
class Shape1 {
int m_type;
}
// 具体的图形为提供方,提供具体的绘图流程
class Rectangle1 extends Shape1 {
Rectangle1() {
super.m_type = 1;
}
}
class Circle1 extends Shape1 {
public Circle1() {
super.m_type = 2;
}
}
//新增画三角形
class Triangle1 extends Shape1 {
Triangle1() {
super.m_type = 3;
}
}
优缺点分析
改进思路分析:
案例2:
/** 开闭原则(正确案例案例)
* @author compass
* @version 1.0
* @date 2021-07-04 19:18
*/
public class OcpTest2 {
public static void main(String[] args) {
// 使用看看存在的问题
GraphicEditor2 graphicEditor = new GraphicEditor2();
graphicEditor.draw(new Rectangle2());
graphicEditor.draw(new Circle2());
graphicEditor.draw(new Triangle2());
}
}
//Shape类,基类
abstract class Shape2 {
abstract void draw();
}
//这是一个用于绘图的类 [使用方,需使用图形绘图]
class GraphicEditor2 {
// 接收Shape对象,然后根据多态的方式,根据传递过来不同类型的子类实例 来绘制不同的图形
public void draw(Shape2 shape2 ) {
shape2.draw();
}
}
class Circle2 extends Shape2 {
@Override
void draw() {
System.out.println(" 绘制圆形 ");
}
}
class Rectangle2 extends Shape2 {
@Override
void draw() {
System.out.println(" 绘制矩形 ");
}
}
class Triangle2 extends Shape2 {
@Override
void draw() {
System.out.println(" 绘制三角形 ");
}
}
开闭原则的核心点:一个类中的模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。 用抽象构建框架,用实现扩展细节。
迪米特法则的基本介绍:
一个对象应该对其他对象保持最少的了解
只与直接的朋友通信
我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
案例1:
应用实例:有一个学校, 下属有各个学院和总部, 现要求打印出学校总部员工ID和学院员工的id
CollegeEmployee1 类不是 SchoolManager1 类的直接朋友,而是一个陌生类,这样的设计违背了迪米特法则
**
* 迪米特法则(错误示例)
*/
public class Demeter {
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());
}
}
}
案例2:
/**
* 迪米特法则(正确示例)
*/
public class Demeter1 {
public static void main(String[] args) {
System.out.println("~~~使用迪米特法则的改进~~~");
// 创建了一个 SchoolManager 对象
SchoolManager1 schoolManager = new SchoolManager1();
// 输出学院的员工id 和 学校总部的员工信息
schoolManager.printAllEmployee(new CollegeManager1());
}
}
//学校总部员工类
class Employee1 {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//学院的员工类
class CollegeEmployee1 {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//管理学院员工的管理类
class CollegeManager1 {
// 返回学院的所有员工
public List<CollegeEmployee1> getAllEmployee() {
List<CollegeEmployee1> list = new ArrayList<CollegeEmployee1>();
for (int i = 0; i < 10; i++) { // 这里我们增加了10个员工到 list
CollegeEmployee1 emp = new CollegeEmployee1();
emp.setId("学院员工id= " + i);
list.add(emp);
}
return list;
}
// 输出学院员工的信息
public void printEmployee() {
// 获取到学院员工
List<CollegeEmployee1> list1 = getAllEmployee();
System.out.println("------------学院员工------------");
for (CollegeEmployee1 e : list1) {
System.out.println(e.getId());
}
}
}
//学校管理类
class SchoolManager1 {
// 返回学校总部的员工
public List<Employee1> getAllEmployee() {
List<Employee1> list = new ArrayList<Employee1>();
for (int i = 0; i < 5; i++) { // 这里我们增加了5个员工到 list
Employee1 emp = new Employee1();
emp.setId("学校总部员工id= " + i);
list.add(emp);
}
return list;
}
// 该方法完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager1 sub) {
// 分析问题
// 1. 将输出学院的员工方法,封装到CollegeManager
sub.printEmployee();
// 获取到学校总部员工
List<Employee1> list2 = this.getAllEmployee();
System.out.println("------------学校总部员工------------");
for (Employee1 e : list2) {
System.out.println(e.getId());
}
}
}
原则是尽量使用合成/聚合的方式,而不是使用继承
设计模式的层次
设计模式介绍
设计模式分为三种类型 : 共 23 种
单例设计模式介绍
单例模式有八种方式:
饿汉式(静态常量)的具体实现步骤
代码实现
/** 单例模式1:静态常量方式实现
* @author compass
* @version 1.0
* @date 2021-07-06 2:31
*/
public class SingletonTest01 {
public static void main(String[] args) {
Single instance1 = Single.getInstance();
Single instance2 = Single.getInstance();
System.out.println(instance1==instance1);
System.out.println("instance1="+instance1);
System.out.println("instance2="+instance2);
}
}
class Single{
// 私有化构造方法 不让外部new对象
private Single(){
}
// 在类加载的时候就完成对象的创建 不让外部访问,提供一个公用的方法让外部进行访问 保证拿到的都是同一个对象
private static final Single single =new Single();
// 提供静态的构造方法,在外部访问对象
public static Single getInstance(){
return single;
}
}
单例模式静态常量方式实现的优缺点说明:
饿汉式(静态代码块)的具体实现步骤
代码实现
/** 单例模式2:静态代码块的方式去实现
* @author compass
* @version 1.0
* @date 2021-07-06 2:31
*/
public class SingletonTest01 {
public static void main(String[] args) {
Single instance1 = Single.getInstance();
Single instance2 = Single.getInstance();
System.out.println(instance1==instance1);
System.out.println("instance1="+instance1);
System.out.println("instance2="+instance2);
}
}
class Single{
private static final Single single ;
// 在类加载的时候就完成对象的创建 不让外部访问,提供一个公用的方法让外部进行访问 保证拿到的都是同一个对象
static {
single =new Single();
}
// 私有化构造方法 不让外部new对象
private Single(){
}
// 提供静态的构造方法,在外部访问对象
public static Single getInstance(){
return single;
}
}
代码实现
/** 单例模式3:懒汉式(线程不安全)
* @author compass
* @version 1.0
* @date 2021-07-06 2:31
*/
public class SingletonTest01 {
public static void main(String[] args) {
Single instance1 = Single.getInstance();
Single instance2 = Single.getInstance();
System.out.println(instance1==instance1);
System.out.println("instance1="+instance1);
System.out.println("instance2="+instance2);
}
}
class Single{
private static Single single ;
// 私有化构造方法 不让外部new对象
private Single(){
}
// 提供静态的构造方法,在外部访问对象
public static Single getInstance(){
if (single==null){
single=new Single();
}
return single;
}
}
懒汉式(线程不安全)的优缺点说明
代码实现
/** 单例模式4:懒汉式(同步方法 线程安全)
* @author compass
* @version 1.0
* @date 2021-07-06 2:31
*/
public class SingletonTest01 {
public static void main(String[] args) {
Single instance1 = Single.getInstance();
Single instance2 = Single.getInstance();
System.out.println(instance1==instance1);
System.out.println("instance1="+instance1);
System.out.println("instance2="+instance2);
}
}
class Single{
private static Single single ;
// 私有化构造方法 不让外部new对象
private Single(){
}
// 提供静态的构造方法,在外部访问对象 多线程环境下线程是安全了 但是效率比较低,因为每个线程进来都需要进行排队执行
public static synchronized Single getInstance(){
if (single==null){
single=new Single();
}
return single;
}
}
懒汉式(同步方法)的优缺点说明
/** 单例模式:懒汉式(同步代码块 线程不安全)
* @author compass
* @version 1.0
* @date 2021-07-06 2:31
*/
public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance1);
System.out.println("instance1="+instance1.hashCode());
System.out.println("instance2="+instance2.hashCode());
}
}
class Singleton{
private static Singleton singleton;
public static Singleton getInstance(){
if (singleton==null){
synchronized(Singleton.class){
singleton=new Singleton();
}
}
return singleton;
}
}
懒汉式(同步代码块)的优缺点说明
构造器私有化,外部不能 new
在本类内部的 getInstance() 静态方法中,先判断对象是否为空
如果为空,则先加锁,再判断此单例对象是否为空,如果还为空,才创建对象
如果不为空,则直接返回此对象
注意:单例变量需要使用 volatile 关键字进行修饰,保证内存可见性,以及防止指令重排序
代码实现:
/** 单例模式5:懒汉式(双重检查 线程安全)
* @author compass
* @version 1.0
* @date 2021-07-06 2:31
*/
public class SingletonTest01 {
public static void main(String[] args) {
Single instance1 = Single.getInstance();
Single instance2 = Single.getInstance();
System.out.println(instance1==instance1);
System.out.println("instance1="+instance1);
System.out.println("instance2="+instance2);
}
}
class Single{
private static volatile Single single ;
// 私有化构造方法 不让外部new对象
private Single(){
}
// 提供静态的构造方法,在外部访问对象
public static Single getInstance(){
if (single==null){
// 解决多线程下安全问题
synchronized(Single.class){
if (single==null){
single=new Single();
}
}
}
return single;
}
}
懒汉式(双重检查)的优缺点说明
代码实现:
/** 单例模式6:懒汉式(静态内部类 线程安全)
* @author compass
* @version 1.0
* @date 2021-07-06 2:31
*/
public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance1);
System.out.println("instance1="+instance1);
System.out.println("instance2="+instance2);
}
}
// 静态内部类完成, 推荐使用
class Singleton {
//构造器私有化
private Singleton() {}
//写一个静态内部类,该类中有一个静态属性 Singleton
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
懒汉式(静态内部类)的优缺点说明
Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance()方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
通过枚举类实现单例模式
代码实现:
在这里插入代码片
饿汉式(枚举)的优缺点说明
JDK源码:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class Runtime
are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the Runtime
object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
单例模式注意事项
简单工厂模式的实际需求
看一个披萨的项目:要便于披萨种类的扩展,要便于维护
Pizza:
抽象父类/** 将Pizza 做成抽象类
* @author compass
* @version 1.0
* @date 2021-07-06 14:51
*/
public abstract class Pizza {
protected String name;
// 准备披萨 因为不太的披萨会有不同的原材料 所以做成抽象方法
public abstract void prepare();
public void bake(){
System.out.println("开始烘烤");
}
public void cut(){
System.out.println("开始切割");
}
public void box(){
System.out.println("开始包装");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
CheesePizza:
奶酪披萨/** 奶酪披萨
* @author compass
* @version 1.0
* @date 2021-07-06 14:54
*/
public class CheesePizza extends Pizza {
@Override
public void prepare() {
System.out.println("选用奶酪披萨原材料");
}
@Override
public void bake() {
super.bake();
}
@Override
public void cut() {
super.cut();
}
@Override
public void box() {
super.box();
}
}
GreekPizza:
希腊披萨/** 希腊披萨
* @author compass
* @version 1.0
* @date 2021-07-06 14:55
*/
public class GreekPizza extends Pizza{
@Override
public void prepare() {
System.out.println("选用希腊披萨原材料");
}
@Override
public void bake() {
super.bake();
}
@Override
public void cut() {
super.cut();
}
@Override
public void box() {
super.cut();
}
}
OrderPizza:
表示披萨商店,可以根据用户需要的披萨类型,制作相应的披萨/** 披萨商店
* @author compass
* @version 1.0
* @date 2021-07-06 14:57
*/
public class OrderPizza {
/**
* 根据传递的class类型决定做什么类型的披萨
* @param pizzaClazz
* @author compass
* @date 2022/10/4 14:04
* @since 1.0.0
**/
public OrderPizza(Class<?> pizzaClazz ){
Pizza pizza;
if (pizzaClazz==null){
throw new RuntimeException("pizzaClazz不能为空");
}
try {
pizza = (Pizza)pizzaClazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException("pizza创建失败");
}
// 此时披萨商店获取到了对应的披萨工厂,开始制作披萨
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
}
PizzaStore:
相当于客户端,发出订购披萨的请求/** 客户端 发出订购任务
* @author compass
* @version 1.0
* @date 2021-07-06 15:12
*/
public class PizzaStore {
public static void main(String[] args) {
OrderPizza orderPizza = new OrderPizza();
}
}
·传统方式的优缺点分析:
·
ocp
原则,即对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码.改进思路
Pizza, CheesePizza, GreekPizza:代码不变
SimpleFactory:
工厂类,根据用户输入,制作相应的 Pizza,此时 SimpleFactory 为提供方
/**
* @author compass
* @version 1.0
* @date 2021-07-06 16:28
*/
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("奶酪披萨");
}
return pizza;
}
}
OrderPizza :
表示披萨商店,可以根据用户的输入,制作相应的披萨
/** 披萨商店
* @author compass
* @version 1.0
* @date 2021-07-06 14:57
*/
public class OrderPizza {
// 定义一个简单工厂对象
SimpleFactory simpleFactory;
Pizza pizza = null;
// 构造器
public OrderPizza(SimpleFactory simpleFactory) {
setFactory(simpleFactory);
}
public void setFactory(SimpleFactory simpleFactory) {
String orderType = ""; // 用户输入的
this.simpleFactory = simpleFactory; // 设置简单工厂对象
do {
orderType = getType();
pizza = this.simpleFactory.createPizza(orderType);
// 输出pizza
if (pizza != null) { // 订购成功
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
System.out.println();
} else {
System.out.println("订购披萨失败");
break;
}
} while (true);
}
// 写一个方法,可以获取客户希望订购的披萨种类
private String getType() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入需要订购的种类:");
return br.readLine();
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
PizzaStore :
客户端 发出订购任务
/** 客户端 发出订购任务
* @author compass
* @version 1.0
* @date 2021-07-06 15:12
*/
public class PizzaStore {
public static void main(String[] args) {
OrderPizza orderPizza = new OrderPizza(new SimpleFactory());
}
}
两者做比较:
还有一种静态简单工厂模式:也就是将 createPizza(String orderType)
方法改为静态的即可,其他的地方做相应的修改即可
工厂方法模式的实际需求
看一个新的需求
披萨项目新的需求: 客户在点披萨时, 可以点不同口味的披萨, 比如北京的奶酪 pizza、 北京的胡椒 pizza 或者是伦敦的奶酪 pizza、 伦敦的胡椒 pizza。
思路一:简单工厂模式
使用简单工厂模式, 创建不同的简单工厂类, 比如 BJPizzaSimpleFactory、LDPizzaSimpleFactory 等等。
从当前这个案例来说, 也是可以的, 但是考虑到项目的规模, 以及软件的可维护性、 可扩展性并不是特别好,因为过多的工厂类会导致整个项目类膨胀
路二:使用工厂方法模式
工厂方法模式介绍
工厂方法模式应用案例
项目需求:披萨项目新的需求: 客户在点披萨时, 可以点不同口味的披萨, 比如 北京的奶酪 pizza、 北京的胡椒 pizza 或者是伦敦的奶酪 pizza、 伦敦的胡椒 pizza
Pizza:
抽象父类,和之前的定义一样/** 将Pizza 做成抽象类
* @author compass
* @version 1.0
* @date 2021-07-06 14:51
*/
public abstract class Pizza {
protected String name;
// 准备披萨 因为不太的披萨会有不同的原材料 所以做成抽象方法
public abstract void prepare();
public void bake(){
System.out.println(name+":开始烘烤");
}
public void cut(){
System.out.println(name+":开始切割");
}
public void box(){
System.out.println(name+":开始包装");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
BJCheesePizza:
北京的奶酪披萨/**
* @author compass
* @version 1.0
* @date 2021-07-06 14:55
*/
public class BJCheesePizza extends Pizza {
@Override
public void prepare() {
setName("北京的奶酪pizza");
System.out.println("北京的奶酪pizza 准备原材料");
}
}
BJPepperPizza:
北京的胡椒披萨/**
* @author compass
* @version 1.0
* @date 2021-07-06 14:54
*/
public class BJPepperPizza extends Pizza {
@Override
public void prepare() {
setName("北京的胡椒pizza");
System.out.println("北京的胡椒pizza 准备原材料");
}
}
LDCheesePizza:
伦敦的奶酪披萨/**
* @author compass
* @version 1.0
* @date 2021-07-06 14:54
*/
public class LDCheesePizza extends Pizza {
@Override
public void prepare() {
setName("伦敦的奶酪pizza");
System.out.println("伦敦的奶酪pizza 准备原材料");
}
}
LDPepperPizza
:伦敦的胡椒披萨/**
* @author compass
* @version 1.0
* @date 2021-07-06 14:55
*/
public class LDPepperPizza extends Pizza {
@Override
public void prepare() {
setName("伦敦的胡椒pizza");
System.out.println("伦敦的胡椒pizza 准备原材料");
}
}
OrderPizza:
含有抽象方法的工厂父类,其抽象方法待子类去实现
import java.util.HashMap;
/** 披萨商店
* @author compass
* @version 1.0
* @date 2021-07-06 14:57
*/
public abstract class OrderPizza {
// 定义一个抽象方法,createPizza , 让各个工厂子类自己实现
abstract Pizza createPizza(Class<?> pizzaClazz );
/**
* 默认实现创建披萨的方式
* @param pizzaClazz
* @return compass.token.pocket.com.service.demo.Pizza
* @author compass
* @date 2022/10/4 14:22
* @since 1.0.0
**/
public Pizza createDefaultPizza(Class<?> pizzaClazz ){
Pizza pizza;
if (pizzaClazz==null){
throw new RuntimeException("pizzaClazz不能为空");
}
try {
pizza = (Pizza)pizzaClazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException("pizza创建失败");
}
// 此时披萨商店获取到了对应的披萨工厂,开始制作披萨
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
/**
* 创建指定类型的工厂
* @param OrderFactoryClazz 工厂类型
* @param pizzaClazz 披萨类型
* @return compass.token.pocket.com.service.demo.OrderPizza
* @author compass
* @date 2022/10/4 14:28
* @since 1.0.0
**/
public static Pizza getPizza(Class<?> OrderFactoryClazz,Class<?> pizzaClazz){
HashMap<Class<?>, OrderPizza> map = new HashMap<>();
map.put(BJOrderFactory.class,new BJOrderFactory());
map.put(LDOrderFactory.class,new LDOrderFactory());
return map.get(OrderFactoryClazz).createDefaultPizza(pizzaClazz);
}
}
/**
* @author compass
* @version 1.0
* @date 2021-07-06 17:57
*/
public class BJOrderFactory extends OrderPizza{
/**
* 根据传递的class类型决定做什么类型的披萨
* @param pizzaClazz
* @author compass
* @date 2022/10/4 14:04
* @since 1.0.0
**/
public Pizza createPizza(Class<?> pizzaClazz ){
return null;
}
@Override
public Pizza createDefaultPizza(Class<?> pizzaClazz) {
return super.createDefaultPizza(pizzaClazz);
}
}
/**
* @author compass
* @version 1.0
* @date 2021-07-06 17:57
*/
public class LDOrderFactory extends OrderPizza{
/**
* 根据传递的class类型决定做什么类型的披萨
* @param pizzaClazz
* @author compass
* @date 2022/10/4 14:04
* @since 1.0.0
**/
public Pizza createPizza(Class<?> pizzaClazz ){
return null;
}
@Override
public Pizza createDefaultPizza(Class<?> pizzaClazz) {
return super.createDefaultPizza(pizzaClazz);
}
}
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class PizzaStore {
public static void main(String[] args) {
// 调用者只需要告诉我,你需要那个工厂,需要制作披萨的口味类型是什么即可
Pizza pizza = OrderPizza.getPizza(BJOrderFactory.class, BJPepperPizza.class);
System.out.println(pizza);
}
}
抽象工厂模式的基本介绍
使用抽象工厂模式来完成披萨项目
代码实现
Pizza
抽象父类以及Pizza
的子类和上面一样
AbsFactory:工厂抽象层,定义制造 Bean 的抽象方法
/**
* 抽象工厂接口,定义创建披萨的方法,具体实现由具体工厂实现,不创建具体产品
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public interface AbsPizzaFactory {
/**
* 定义创建披萨的抽象方法,由具体工厂去实现(提供默认实现)
* @param pizzaClazz 需要创建的披萨类型
* @return compass.token.pocket.com.service.demo.Pizza
* @author compass
* @date 2022/10/4 14:48
* @since 1.0.0
**/
default Pizza createPizza(Class<?> pizzaClazz){
Pizza pizza;
if (pizzaClazz==null){
throw new RuntimeException("pizzaClazz不能为空");
}
try {
pizza = (Pizza)pizzaClazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException("pizza创建失败");
}
// 此时披萨商店获取到了对应的披萨工厂,开始制作披萨
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
BJFactory:
北京工厂子类(提供方),负责制造北京各种口味的 Pizza
/**
* 北京工厂子类(提供方),负责制造北京各种口味的 Pizza
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class BJFactory implements AbsPizzaFactory{
private BJFactory(){}
private static volatile AbsPizzaFactory single ;
// 提供静态的构造方法,在外部访问对象
public static AbsPizzaFactory getInstance(){
if (single==null){
// 解决多线程下安全问题
synchronized(BJFactory.class){
if (single==null){
single=new BJFactory();
}
}
}
return single;
}
}
LDFactory:
伦敦工厂子类(提供方),负责制造伦敦各种口味的 Pizza
/**
* 伦敦工厂子类(提供方),负责制造伦敦各种口味的 Pizza
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class LDFactory implements AbsPizzaFactory {
private LDFactory(){}
private static volatile AbsPizzaFactory single ;
// 提供静态的构造方法,在外部访问对象
public static AbsPizzaFactory getInstance(){
if (single==null){
// 解决多线程下安全问题
synchronized(LDFactory.class){
if (single==null){
single=new LDFactory();
}
}
}
return single;
}
}
OrderPizza:
使用方,负责消费 Pizza
import java.util.HashMap;
/** 披萨商店
* @author compass
* @version 1.0
* @date 2021-07-06 14:57
*/
public abstract class OrderPizza {
/**
* 创建指定类型工厂,使用指定类型工厂创建出指定类型的pizza
* @param OrderFactoryClazz 工厂类型
* @param pizzaClazz 披萨类型
* @return compass.token.pocket.com.service.demo.OrderPizza
* @author compass
* @date 2022/10/4 14:28
* @since 1.0.0
**/
public static Pizza getPizza(Class<?> OrderFactoryClazz,Class<?> pizzaClazz){
HashMap<Class<?>, AbsPizzaFactory> map = new HashMap<>();
map.put(BJFactory.class, LDFactory.getInstance());
map.put(LDFactory.class, LDFactory.getInstance());
Pizza pizza = map.get(OrderFactoryClazz).createPizza(pizzaClazz);
map.clear();
return pizza;
}
}
PizzaStore:
客户端,发出订购披萨的请求
public class PizzaStore {
public static void main(String[] args) {
Pizza pizza = OrderPizza.getPizza(BJFactory.class, BJPepperPizza.class);
System.out.println(pizza);
}
}
抽象工厂模式总结
JDK Calendar 中使用到了简单工厂模式
/**
* @author compass
* @version 1.0
* @date 2021-07-06 22:14
*/
public class Test {
public static void main(String[] args) {
// 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.getInstance() 方法的实现
/**
* Gets a calendar using the default time zone and locale. The
* Calendar
returned is based on the current time
* in the default time zone with the default
* {@link Locale.Category#FORMAT FORMAT} locale.
*
* @return a Calendar.
*/
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
createCalendar() 方法的实现:如果 provider == null,就会根据 caltype 的值,来创建具体的工厂子类对象
private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
{
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;
}
工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
三种工厂模式
简单工厂模式
工厂方法模式
抽象工厂模式
设计模式的依赖抽象原则
关于工厂模式,我之前也有一篇博客,这个老师讲的感觉创建对象的方式不太灵活,还需要改动代码,我这个,不需要改动任何代码,只需要你将需要创建的对象的类型传入即可,灵活性更强,是基于java的反射机制和泛型写的。
一句话: 不要面向具体编程,需要面向抽象编程,这样扩展性强,使用对象不要直接new,而是在中间加一层,让工厂类去代替你做new对象的这个动作,如果你手动去new,将来你的代码改动会比较大,变量不要直接持有具体类的引用
地址:java工厂模式
克隆羊问题描述
现在有一只羊tom, 姓名为: tom,年龄为: 20, 颜色为:白色,请编写程序创建和tom羊属性完全相同的10只羊
传统模式解决克隆羊问题
Sheep:绵羊类
/** 原型模式: 创建一个绵羊对象 然后克隆该对象10个
* @author compass
* @version 1.0
* @date 2021-07-06 23:28
*/
public class Sheep {
private int age;
private String name;
private String color;
public Sheep(int age, String name,String color) {
this.age = age;
this.name = name;
this.color=color;
}
public Sheep() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"age=" + age +
", name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
Client:客户端(调用者)
/**
* @author compass
* @version 1.0
* @date 2021-07-06 23:29
*/
public class Client {
public static void main(String[] args) {
Sheep sheep1 = new Sheep(20,"tom","white");
// 克隆出10个对象当如到ArrayList集合中
ArrayList<Object> arrayList = new ArrayList<>(10);
for (int i =0;i<9;i++){
arrayList.add(new Sheep(20,"tom","white"));
}
arrayList.add(sheep1);
arrayList.forEach(System.out::println);
}
}
传统的方式的优缺点
改进思路
Java中Object类是所有类的根类, Object类提供了一个clone()方法
,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable
,该接口表示该类能够复制且具有复制的能力 --> 原型模式
就是传入一个对象,复制一份新的这个对象出来
Prototype :原型类,在该类中声明一个克隆自己的接口
ConcretePrototype :具体的原型类,实现一个克隆自己的操作
Client :让一个原型对象克隆自己,从而创建一个新的对象(属性一样)
原型模式解决克隆羊问题的应用实例:使用原型模式改进传统方式,让程序具有更高的效率和扩展性
Sheep :
绵羊实体
/** 原型模式: 实现Cloneable接口 让本类的实例对象可克隆
* @author compass
* @version 1.0
* @date 2021-07-06 23:28
*/
public class Sheep implements Cloneable {
private int age;
private String name;
private String color;
public Sheep(int age, String name,String color) {
this.age = age;
this.name = name;
this.color=color;
}
// 重写克隆方法 返回的是一个新的对象
@Override
protected Object clone() {
Sheep sheep=null;
try {
sheep =(Sheep) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println( e.getMessage());
}
return sheep;
}
public Sheep() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"age=" + age +
", name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
Client :
客户端(调用者)
/**
* @author compass
* @version 1.0
* @date 2021-07-06 23:29
*/
public class Client {
public static void main(String[] args) {
Sheep sheep1 = new Sheep(20,"tom","white");
Sheep sheep2 = (Sheep)sheep1.clone();
Sheep sheep3 = (Sheep)sheep1.clone();
System.out.println(sheep1==sheep2);
System.out.println("sheep1 hashCode="+sheep1.hashCode());
System.out.println("sheep2 hashCode="+sheep2.hashCode());
System.out.println("sheep3 hashCode="+sheep3.hashCode());
}
}
浅拷贝的介绍
对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象
前面我们克隆羊就是浅拷贝,浅拷贝是使用默认的clone()方法来实现:sheep = (Sheep) super.clone();
深拷贝基本介绍
DeepCloneableTarget:
import java.io.*;
/**
* @author compass
* @version 1.0
* @date 2021-07-06 23:29
*/
public class DeepProtoType implements Serializable, Cloneable {
private static final long serialVersionUID = -9181359624793979485L;
// String 属性
private String name;
private DeepCloneableTarget deepCloneableTarget;
public DeepProtoType(String name, DeepCloneableTarget deepCloneableTarget) {
this.name = name;
this.deepCloneableTarget = deepCloneableTarget;
}
public DeepProtoType() {
super();
}
// 深拷贝 - 方式 1 使用clone 方法
@Override
protected DeepProtoType clone() throws CloneNotSupportedException {
Object deep = null;
// 这里完成对基本数据类型(属性)和String的克隆
deep = super.clone();
DeepProtoType deepProtoType = (DeepProtoType) deep;
// 对引用类型的属性,进行单独处理
deepProtoType.deepCloneableTarget = deepCloneableTarget.clone();
return deepProtoType;
}
// 深拷贝 - 方式2 通过对象的序列化实现 (推荐)
public DeepProtoType 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) {
e.printStackTrace();
return null;
} finally {
// 关闭流
try {
if (bos!=null){
bos.close();
}
if (oos!=null){
oos.close();
}
if (bis!=null){
bis.close();
}
if (ois!=null){
ois.close();
}
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public DeepCloneableTarget getDeepCloneableTarget() {
return deepCloneableTarget;
}
public void setDeepCloneableTarget(DeepCloneableTarget deepCloneableTarget) {
this.deepCloneableTarget = deepCloneableTarget;
}
@Override
public String toString() {
return "DeepProtoType{" +
"name='" + name + '\'' +
", deepCloneableTarget=" + deepCloneableTarget +
'}';
}
}
DeepProtoType :
/**
* @author compass
* @version 1.0
* @date 2021-07-06 23:29
*/
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();
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) {
e.printStackTrace();
return null;
} finally {
// 关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
}
原型模式的注意事项和细节
AbstractHouse:
房子的抽象父类,指定建造房子的规范,以及建造房子的具体流程
/** 建造者模式 :传统方式
* @author compass
* @version 1.0
* @date 2021-07-07 11:13
*/
public abstract class AbstractHouse {
/**
* 打地基
*/
public abstract void buildBasic();
/**
* 砌墙
*/
public abstract void buildWalls();
/**
* 封顶
*/
public abstract void roofed();
public void build(){
buildBasic();
buildWalls();
roofed();
}
}
CommonHouse:
普通房子,继承 AbstractHouse 类,实现了建造房子中各个步骤的具体细节
/**
* @author compass
* @version 1.0
* @date 2021-07-07 11:16
*/
public class CommonHouse extends AbstractHouse {
/**
* 打地基
*/
@Override
public void buildBasic() {
System.out.println("普通房顶打地基");
}
/**
* 砌墙
*/
@Override
public void buildWalls() {
System.out.println("普通房顶打地基");
}
/**
* 封顶
*/
@Override
public void roofed() {
System.out.println("普通房顶打封顶");
}
}
HighBuilding:
高楼大厦,继承 AbstractHouse 类,实现了建造房子中各个步骤的具体细节
/**
* @author compass
* @version 1.0
* @date 2021-07-07 11:26
*/
public class HighHouse extends AbstractHouse{
/**
* 打地基
*/
@Override
public void buildBasic() {
System.out.println("高级房子打地基");
}
/**
* 砌墙
*/
@Override
public void buildWalls() {
System.out.println("高级房子砌墙");
}
/**
* 封顶
*/
@Override
public void roofed() {
System.out.println("高级房子封顶");
}
}
传统方式优缺点分析
Product(产品角色)
: 一个具体的产品对象Builder(抽象建造者)
: 创建一个Product对象的抽象接口(或抽象类),抽象建造者主要负责规范建造的流程,不关心具体的建造细节 ConcreteBuilder(具体建造者)
: 实现接口,构建和装配各个部件,具体建造者负责实现具体的建造细节Director(指挥者)
: 构建一个使用Builder接口的具体实现类的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程建造者模式UML类图
Product(产品类)
:一个具体的产品Builder(抽象建造者)
:Builder 中组合了一个 Product 实例 ConcreteBuilder(具体建造者)
:实现了 Builder 中的抽象方法 Director(指挥者)
:将 Builder 的具体实现类聚合到 Director 中,在 Director 中调用具体的 Builder 完成具体产品的制造案例需求
需要建房子:这一过程为打桩、 砌墙、封顶。不管是普通房子也好,别墅也好都需要经历这些过程, 下面我们使用建造者模式(Builder Pattern)
来完成
House:
产品类
package compass.token.pocket.com.service.demo;
/** 建造者模式
* @author compass
* @version 1.0
* @date 2021-07-07 15:17
*/
public class House {
private String base;
private String wall;
private String roofed;
public House(String base, String wall, String roofed) {
this.base = base;
this.wall = wall;
this.roofed = roofed;
}
public House() {
}
public String getBase() {
return base;
}
public void setBase(String base) {
this.base = base;
}
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;
}
@Override
public String toString() {
return "House{" +
"base='" + base + '\'' +
", wall='" + wall + '\'' +
", roofed='" + roofed + '\'' +
'}';
}
}
HouseBuilder:
抽象建造者,规定制造房子的规范,并提供 buildHouse() 方法返回制造好的房子(产品)
package compass.token.pocket.com.service.demo;
/**
* @author compass
* @version 1.0
* @date 2021-07-07 15:30
*/
public class HighBuilding extends HouseBuilder {
/**
* 打地基
*/
@Override
public void buildBasic(String basic) {
super.build().setBase(basic);
System.out.println("高楼房子打地基");
}
/**
* 砌墙
*/
@Override
public void buildWall(String wall) {
super.build().setWall(wall);
System.out.println("高楼房子砌墙");
}
/**
* 封顶
*/
@Override
public void roofed(String fed) {
super.build().setRoofed(fed);
System.out.println("高楼房子封顶");
}
}
CommonHouse:
具体建造者,负责建造普通房子,重写父类 HouseBuilder 中的抽象方法来指定普通房子的建造细节
package compass.token.pocket.com.service.demo;
/**
* @author compass
* @version 1.0
* @date 2021-07-07 15:21
*/
public class CommonHouse extends HouseBuilder {
/**
* 打地基
*/
@Override
public void buildBasic() {
super.build().setBase("普通地基");
System.out.println("普通房子打地基");
}
/**
* 砌墙
*/
@Override
public void buildWall() {
super.build().setWall("普通墙");
System.out.println("普通房子砌墙");
}
/**
* 封顶
*/
@Override
public void roofed( ) {
super.build().setRoofed("普通封顶");
System.out.println("普通房子封顶");
}
}
HighBuilding:
具体建造者,负责建造高楼大厦,重写父类 HouseBuilder 中的抽象方法来指定高楼大厦的建造细节
package compass.token.pocket.com.service.demo;
/**
* @author compass
* @version 1.0
* @date 2021-07-07 15:30
*/
public class HighBuilding extends HouseBuilder {
/**
* 打地基
*/
@Override
public void buildBasic() {
super.build().setBase("高楼地基");
System.out.println("高楼房子打地基");
}
/**
* 砌墙
*/
@Override
public void buildWall() {
super.build().setWall("高楼墙");
System.out.println("高楼房子砌墙");
}
/**
* 封顶
*/
@Override
public void roofed( ) {
super.build().setRoofed("高楼封顶");
System.out.println("高楼房子封顶");
}
}
HouseDirector:
指挥者,指挥具体的 Builder 对象制造产品,可指定制造产品的流程
package compass.token.pocket.com.service.demo;
/**
* @author compass
* @version 1.0
* @date 2021-07-07 15:18
*/
public abstract class HouseBuilder {
private static final House house = new House();
/**
* 打地基
*/
public abstract void buildBasic();
/**
* 砌墙
*/
public abstract void buildWall();
/**
* 封顶
*/
public abstract void roofed();
/**
* 将House 构建好之后就返回
*
* @return
*/
public House build() {
return house;
}
/**
* 根据传入指定的构建者实现实现 完成构建
* @param buildTypeClazz 需要使用的的构建者实现类型
* @return compass.token.pocket.com.service.demo.House
* @author compass
* @date 2022/10/4 19:03
* @since 1.0.0
**/
public static final House buildUtil(Class<?> buildTypeClazz) {
HouseBuilder newInstance = null;
try {
newInstance = (HouseBuilder) buildTypeClazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
HouseDirector director = new HouseDirector(newInstance);
return director.construct();
}
}
Client:
客户端,发出建造房子的命令
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class Main {
public static void main(String[] args) {
House house = HouseBuilder.buildUtil(CommonHouse.class);
System.out.println(house);
}
}
总结
Housebuilder
是抽象建造者,提供需要实现的功能,并且组合了产品House
CommonHouse和HighHouse继承自HouseBuilder,并且实现父类的方法
HouseDirector
中聚合了 Housebuilder
,也就是说只要是 Housebuilder的子类
,他都可以进行指挥调用。
Client
只需要去调用HouseDirector(传入具体的建造者,让指挥者去调用建造者即可)
StringBuilder 的 append() 方法:调用父类
AbstractStringBuilder 的 append() 方法
AbstractStringBuilder
的 append() 方法是由Appendable
接口定义的规范
Appendable
接口:定义了 append() 方法的规范,相当于是一个抽象的建造者
源码中建造者模式角色分析
Appendable 为抽象建造者
,定义了制造产品的抽象方法(规范),抽象定义了 append方法能做的事情
public interface Appendable {
Appendable append(CharSequence csq) throws IOException;
Appendable append(CharSequence csq, int start, int end) throws IOException;
Appendable append(char c) throws IOException;
}
建造者
,只是不能实例化
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
/**
* This no-arg constructor is necessary for serialization of subclasses.
*/
AbstractStringBuilder() {
}
/**
* Creates an AbstractStringBuilder of the specified capacity.
*/
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
/**
* Returns the length (character count).
*
* @return the length of the sequence of characters currently
* represented by this object
*/
@Override
public int length() {
return count;
}
public AbstractStringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
// Documentation in subclasses because of synchro difference
public AbstractStringBuilder append(StringBuffer sb) {
if (sb == null)
return appendNull();
int len = sb.length();
ensureCapacityInternal(count + len);
sb.getChars(0, len, value, count);
count += len;
return this;
}
/**
* @since 1.8
*/
AbstractStringBuilder append(AbstractStringBuilder asb) {
if (asb == null)
return appendNull();
int len = asb.length();
ensureCapacityInternal(count + len);
asb.getChars(0, len, value, count);
count += len;
return this;
}
// Documentation in subclasses because of synchro difference
@Override
public AbstractStringBuilder append(CharSequence s) {
if (s == null)
return appendNull();
if (s instanceof String)
return this.append((String)s);
if (s instanceof AbstractStringBuilder)
return this.append((AbstractStringBuilder)s);
return this.append(s, 0, s.length());
}
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
public AbstractStringBuilder append(char[] str) {
int len = str.length;
ensureCapacityInternal(count + len);
System.arraycopy(str, 0, value, count, len);
count += len;
return this;
}
public AbstractStringBuilder append(boolean b) {
if (b) {
ensureCapacityInternal(count + 4);
value[count++] = 't';
value[count++] = 'r';
value[count++] = 'u';
value[count++] = 'e';
} else {
ensureCapacityInternal(count + 5);
value[count++] = 'f';
value[count++] = 'a';
value[count++] = 'l';
value[count++] = 's';
value[count++] = 'e';
}
return this;
}
@Override
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
value[count++] = c;
return this;
}
public AbstractStringBuilder append(int i) {
if (i == Integer.MIN_VALUE) {
append("-2147483648");
return this;
}
int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1
: Integer.stringSize(i);
int spaceNeeded = count + appendedLength;
ensureCapacityInternal(spaceNeeded);
Integer.getChars(i, spaceNeeded, value);
count = spaceNeeded;
return this;
}
public AbstractStringBuilder append(long l) {
if (l == Long.MIN_VALUE) {
append("-9223372036854775808");
return this;
}
int appendedLength = (l < 0) ? Long.stringSize(-l) + 1
: Long.stringSize(l);
int spaceNeeded = count + appendedLength;
ensureCapacityInternal(spaceNeeded);
Long.getChars(l, spaceNeeded, value);
count = spaceNeeded;
return this;
}
public AbstractStringBuilder append(float f) {
FloatingDecimal.appendTo(f,this);
return this;
}
public AbstractStringBuilder append(double d) {
FloatingDecimal.appendTo(d,this);
return this;
}
}
指挥者角色,同时充当了具体的建造者
, 因为建造方法的实现是由 AbstractStringBuilder 完成,而 StringBuilder 继承了AbstractStringBuilderpublic final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
@Override
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}
@Override
public StringBuilder append(CharSequence s, int start, int end) {
super.append(s, start, end);
return this;
}
@Override
public StringBuilder append(char[] str) {
super.append(str);
return this;
}
}
抽象工厂模式 和 建造者模式的区别
适配器模式工作原理:
在生活中,有的插头是三项的,而插座却是两坐的。比如我们新买一台电视机,电源插头是两相的,不巧的是墙上的插座确实三相的,这时让电视机无法通电使用,我们来看下如何使用适配器模式解决这个问题。
三相插孔接口:
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public interface TriplePin {
/**
* 三相插孔接口
* @param liveLine 火线
* @param nullLine 零线
* @param earthLine 地线
* @return void
* @author compass
* @date 2022/10/4 19:48
* @since 1.0.0
**/
void electrify(int liveLine, int nullLine, int earthLine);
}
两相插孔接口:
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public interface DualPin {
/**
* 两相插孔接口
* @param liveLine 火线
* @param nullLine 零线
* @return void
* @author compass
* @date 2022/10/4 19:48
* @since 1.0.0
**/
void electrify(int liveLine, int nullLine);
}
电视机类:
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class Tv implements DualPin {
@Override
public void electrify(int liveLine, int nullLine) {
System.out.println(String.format("火线:%d,零线:%d",liveLine,nullLine));
System.out.println("电视开机");
}
}
客户端直接使用:
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class Main {
public static void main(String[] args) {
// 会报错 ,类型不匹配
TriplePin tv = new Tv();
}
}
我们可以创建一个适配器类来解决这个问题,让适配器去适配。
适配器类:
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class Adapter implements TriplePin {
// 创建适配器时把需要适配的插座传递进来即可
public Adapter(DualPin dualpin) {
this.dualpin = dualpin;
}
private DualPin dualpin;
@Override
public void electrify(int liveLine, int nullLine, int earthLine) {
// 调用被适配的的两插通电方法,忽略掉第三个参数即可
dualpin.electrify(liveLine, nullLine);
}
}
现在我们再来调用就没有任何问题了
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class Main {
public static void main(String[] args) {
DualPin tv = new Tv();
Adapter adapter = new Adapter(tv);
adapter.electrify(1,0,-1);
}
}
上面这种方法是对象适配器,我们还可以使用类适配器,为特定的实现类进行适配
TV专属适配器:
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class TvAdapter extends Tv implements TriplePin {
@Override
public void electrify(int liveLine, int nullLine, int earthLine) {
super.electrify(liveLine, nullLine);
}
}
客户端调用:
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class Main {
public static void main(String[] args) {
// 使用tv专属适配器
TvAdapter adapter = new TvAdapter();
adapter.electrify(1,0,-1);
}
}
SpringMVC中的HandlerAdapter,就使用了适配器模式,SpringMVC处理请求的流程回顾:
springMVC源码模拟:
Controller:
接口及其实现类/**
* @author compass
* @version 1.0
* @date 2021-07-07 21:40
*/
//多种Controller实现
public interface Controller {
}
class HttpController implements Controller {
public void doHttpHandler() {
System.out.println("http...");
}
}
class SimpleController implements Controller {
public void doSimplerHandler() {
System.out.println("simple...");
}
}
class AnnotationController implements Controller {
public void doAnnotationHandler() {
System.out.println("annotation...");
}
}
HandlerAdapter:
接口及其实现类/**
* @author compass
* @version 1.0
* @date 2021-07-07 21:40
*/
//定义一个Adapter接口
public interface HandlerAdapter {
// 当前 HandlerAdapter 对象是否支持 handler(判断 handler 的类型是否为具体的子类类型)
public boolean supports(Object handler);
// 执行目标方法(将 handler 对象强转后,调用对应的方法)
public void handle(Object handler);
}
// 多种适配器类
class SimpleHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((SimpleController) handler).doSimplerHandler();
}
public boolean supports(Object handler) {
return (handler instanceof SimpleController);
}
}
class HttpHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((HttpController) handler).doHttpHandler();
}
public boolean supports(Object handler) {
return (handler instanceof HttpController);
}
}
class AnnotationHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((AnnotationController) handler).doAnnotationHandler();
}
public boolean supports(Object handler) {
return (handler instanceof AnnotationController);
}
}
- DispatchServlet:
模拟 doDispatch() 方法中获取适配器的流程
/**
* @author compass
* @version 1.0
* @date 2021-07-07 21:41
*/
public class DispatchServlet {
public static void main(String[] args) {
// http...
new DispatchServlet().doDispatch();
}
public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();
// 组合了多个 HandlerAdapter 的实现类
public DispatchServlet() {
handlerAdapters.add(new AnnotationHandlerAdapter());
handlerAdapters.add(new HttpHandlerAdapter());
handlerAdapters.add(new SimpleHandlerAdapter());
}
public void doDispatch() {
// 此处模拟SpringMVC从request取handler的对象,
// 适配器可以获取到希望的Controller
HttpController controller = new HttpController();
// AnnotationController controller = new AnnotationController();
// SimpleController controller = new SimpleController();
// 得到对应适配器
HandlerAdapter adapter = getHandler(controller);
// 通过适配器执行对应的controller对应方法
adapter.handle(controller);
}
public HandlerAdapter getHandler(Controller controller) {
// 遍历:根据得到的controller(handler), 返回对应适配器
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(controller)) {
return adapter;
}
}
return null;
}
}
适配器模式的注意事项和细节
三种命名方式,是根据src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
类适配器:
以类给到,在Adapter里,就是将src当做类,继承
对象适配器:
以对象给到,在Adapter里,将src作为一个对象,持有
接口适配器:
以接口给到,在Adapter里,将src作为一个接口,实现
Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作,相当于是个中转封装站
实际开发中,实现起来不拘泥于我们讲解的三种经典形式
小型的外包项目,给客户A做一个产品展示网站, 客户A的朋友感觉效果不错,也希望做这样产品展示网站,但是要求都有些不同:
方案描述
直接复制粘贴一份,然后根据客户不同要求,进行定制修改
给每个网站租用一个空间
需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、 CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源
对于代码来说, 由于是一份实例,维护和扩展都更加容易
上面的解决思路就可以使用享元模式来解决
FlyWeight
是抽象的享元角色,他是产品的抽象类,定义了对象的外部状态和内部状态(后面介绍) 的接口规范(接口)或默认实现(抽象类)ConcreteFlyWeight
是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务方法UnsharedConcreteFlyWeight
是不可共享的角色,一般不会出现在享元工厂中 FlyWeightFactory
是享元工厂类,用于构建池的容器,提供从池中获取对象的相关方法比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态
举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题
使用享元模式完成, 前面提出的网站外包问题
WebSite:
产品的抽象类,定义了产品对象的内部和外部状态的规范,即前面所说的 FlyWeightpublic abstract class WebSite {
public abstract void use(User user);// 抽象方法
}
ConcreteWebSite:
具体的产品类,继承了 WebSite 抽象类,实现了具体的业务方法,即前面所说的 ConcreteFlyWeight//具体网站
public class ConcreteWebSite extends WebSite {
// 共享的部分,内部状态
private String type = ""; // 网站发布的形式(类型)
// 构造器
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站的发布形式为:" + type + " 在使用中 .. 使用者是" + user.getName());
}
}
WebSiteFactory:
产品工厂类,生产具体的产品(WebSite 对象),并构建产品池,即前面所说的 FlyWeightFactory// 网站工厂类,根据需要返回压一个网站
public class WebSiteFactory {
// 集合, 充当池的作用
private HashMap<String, ConcreteWebSite> pool = new HashMap<>();
// 根据网站的类型,返回一个网站, 如果没有就创建一个网站,并放入到池中,并返回
public WebSite getWebSiteCategory(String type) {
if (!pool.containsKey(type)) {
// 就创建一个网站,并放入到池中
pool.put(type, new ConcreteWebSite(type));
}
return (WebSite) pool.get(type);
}
// 获取网站分类的总数 (池中有多少个网站类型)
public int getWebSiteCount() {
return pool.size();
}
}
User:
实体类public class User {
private String name;
public User(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Client:
客户端public class Client {
public static void main(String[] args) {
// 创建一个工厂类
WebSiteFactory factory = new WebSiteFactory();
// 客户要一个以新闻形式发布的网站
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new User("tom"));
// 客户要一个以博客形式发布的网站
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new User("jack"));
// 客户要一个以博客形式发布的网站
WebSite webSite3 = factory.getWebSiteCategory("博客");
webSite3.use(new User("smith"));
// 客户要一个以博客形式发布的网站
WebSite webSite4 = factory.getWebSiteCategory("博客");
webSite4.use(new User("king"));
System.out.println("网站的分类个数=" + factory.getWebSiteCount());
}
}
总结
利用享元模式,我们能够把外部状态(User)和内部状态(type)分开,对于共享的部分,我们共用即可
比如网站类型(type)不同时,我们才会创建对应的网站实例,再将其放入对象池中,如果网站类型(type)相同,我们直接共享即可(享元)
博客类型相同可以共用,但是我们可以通过传入 User 形参,让不同的使用者,访问同一份博客
public class FlyWeightTest {
public static void main(String[] args) {
// 如果 Integer.valueOf(x) x 在 -128 --- 127 直接,就是使用享元模式返回,如果不在该范围类,则仍然 new
// 小结:
// 1. 在valueOf 方法中,先判断值是否在 IntegerCache 中,如果不在,就创建新的Integer(new), 否则,就直接从 缓存池返回
// 2. valueOf 方法,就使用到享元模式
// 3. 如果使用valueOf 方法得到一个Integer 实例,范围在 -128 - 127 ,执行速度比 new 快
Integer x = Integer.valueOf(127); // 得到 x实例,类型 Integer
Integer y = new Integer(127); // 得到 y 实例,类型 Integer
Integer z = Integer.valueOf(127);// ..
Integer w = new Integer(127);
System.out.println(x.equals(y)); // 大小,true
System.out.println(x == y); // false
System.out.println(x == z); // true
System.out.println(w == x); // false
System.out.println(w == y); // false
Integer x1 = Integer.valueOf(200);
Integer x2 = Integer.valueOf(200);
System.out.println("x1==x2=" + (x1 == x2)); // false
}
}
1.Integer.valueOf()
方法:该方法使用享元模式,如果数字范围在 [IntegerCache.low, IntegerCache.high] 之间,则直接返回缓存池中的对象,否则使用 new 的方式创建
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache
用于为 [-128, 127] 数值的缓存池,事先就已经将 cache[] 缓存池创建好了private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
“享”就表示共享,“元”表示对象
消耗大量内存
, 并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式唯一标识码
判断,如果在内存中有,则返回这个唯一标识码所标识的对象,经常用HashMap
存储共享对象注意划分内部状态和外部状态
,并且需要有一个工厂类加以控制。门面模式(Facade Pattern)又叫外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。其主要特征是定义了一个高层接口,让子系统更容易使用,属于结构性模式
原文: Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use.
解释:要求一个子系统的外部与其内部的通信必须通过一个同一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用
在我们日常的编码工作中,我们都在有意无意地大量使用门面模式,但凡只要高层模块需要调度多个子系统(2 个以上类对象),我们都会自觉地创建一个新类封装这些子系统,提供精简接口,让高层模块可以更加容易间接调用这些子系统的功能。尤其是现阶段各种第三方 SDK,各种开源类库,很大概率都会使用门面模式。尤其是你觉得调用越方便的,门面模式使用的一般更多
门面模式的应用场景
1、子系统越来越复杂,增加门面模式提供简单接口
2、构建多层系统结构,利用门面对象作为每层的入口,简化层间调用
为了更好的理解门面模式,我们先来看一个例子,在早期的相机使用起来非常的麻烦,拍照前总是需要根据场景情况设置一大堆参数,如对焦,调节闪光灯,调光圈等,非专业人士,根本拍不出很好的效果,而现在出现了一种傻瓜式相机,用户再也不需要调试那些复杂的参数,也能拍出比较好的效果,比如现在的智能手机,能智能的设置一些参数,就拍出比较理想的效果。
对焦:
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class Focusing {
/**
* 对焦
* @return void
* @author compass
* @date 2022/10/5 11:20
* @since 1.0.0
**/
public void focus() {
System.out.println("对焦");
}
}
调节闪光:
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class Flash {
/**
* 调节闪光
* @return void
* @author compass
* @date 2022/10/5 11:21
* @since 1.0.0
**/
public void flashController() {
System.out.println("调节闪光");
}
}
调节光圈:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Aperture {
public void controllerAperture() {
/**
*调节光圈
* @return void
* @author compass
* @date 2022/10/5 11:22
* @since 1.0.0
**/
System.out.println("调节光圈");
}
}
开始拍照:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Photograph {
/**
* 开始拍照
* @return void
* @author compass
* @date 2022/10/5 11:24
* @since 1.0.0
**/
public void photo() {
System.out.println("开始拍照");
}
}
门面模式类:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Facade {
/**
* 门面模式方法,把所有操作都封装到一起,让客户端只调用该方法即可
*
* @return void
* @author compass
* @date 2022/10/5 11:28
* @since 1.0.0
**/
public void startPhoto() {
// 对焦
Focusing focusing = new Focusing();
focusing.focus();
// 调节闪光
Flash flash = new Flash();
flash.flashController();
// 调节光圈
Aperture aperture = new Aperture();
aperture.controllerAperture();
// 以上参数设置好 开始拍照
Photograph photograph = new Photograph();
photograph.photo();
}
}
Client调用:
/**
* @author compass
* @date 2022-10-04
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.startPhoto();
}
}
优点:
1、简化了调用过程,无需深入了解子系统,以防给子系统带来风险
2、减少系统依赖、松散耦合
3、更好地划分访问层次,提高了安全性
4、遵循迪米特法则,即最少知道原则
缺点:
1、当增加子系统和扩展子系统行为时,可能容易带来未知风险
2、不符合开闭原则
3、某些情况下可能违背单一职责原则
组合模式解决这样的问题,当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子
通过观察我们可以发现文件结构其实就像是一个树结构,有文件和目录组成,文件不可以继续添加子文件,但是目录可以添加子文件或目录,我们就以类似于树结构的文件系统的目录结构为例。
抽象节点类:
/**
* 节点类
*
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public abstract class Node {
/**
* 节点名称
**/
protected String nodeName;
/**
* 节点类型
**/
protected String nodeType;
/**
* 添加子节点
*
* @param node 节点对象
* @return void
* @author compass
* @date 2022/10/5 12:04
* @since 1.0.0
**/
public abstract void addChild(Node node);
public Node(String nodeName, String nodeType) {
this.nodeName = nodeName;
this.nodeType = nodeType;
}
/**
* 展示节点
* @param space 空格数量
* @return void
* @author compass
* @date 2022/10/5 12:11
* @since 1.0.0
**/
protected void tree(int space) {
for (int i = 0; i < space; i++) {
// 先循环输出空格
System.out.print(" ");
}
// 最后输出自己的名字
System.out.println(nodeName);
}
/**
* 默认从0列开始展示
* @return void
* @author compass
* @date 2022/10/5 12:11
* @since 1.0.0
**/
protected void tree() {
this.tree(0);
}
}
文件类:
/**
* 文件类
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class File extends Node{
/**
* 调用父类构造方法初始一个文件节点
**/
public File(String nodeName) {
super(nodeName, "file");
}
@Override
public void addChild(Node node) {
throw new RuntimeException("文件节点不能有子节点");
}
@Override
protected void tree(int space) {
super.tree(space);
}
}
目录类:
/**
* 文件夹类
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Folder extends Node{
/**
* 用于存放子节点
**/
public List<Node> childNodeList = new ArrayList<>();
/**
* 调用父类构造方法初始一个文件夹节点
**/
public Folder(String nodeName) {
super(nodeName, "dir");
}
@Override
public void addChild(Node node) {
if (node==null){
throw new RuntimeException("node不能为null");
}
childNodeList.add(node);
}
@Override
protected void tree(int space) {
super.tree(space);
// 在循环子节点前 空格要加1
space++;
for (Node node : childNodeList) {
node.tree(space);
}
}
}
客户端使用:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
Folder d = new Folder("D盘");
Folder word = new Folder("文档");
File resume = new File("简历.doc");
File projectInfo = new File("项目介绍.doc");
Folder music = new Folder("音乐");
File balloon = new File("告白气球.mp3");
File paddy = new File("稻香.mp3");
d.addChild(word);
d.addChild(music);
word.addChild(resume);
word.addChild(projectInfo);
music.addChild(balloon);
music.addChild(paddy);
d.tree();
}
}
组合模式的注意事项和细节
装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
这里提到的动态的将新功能附加到对象和ocp原则,在后面的应用实例上会以代码的形式体现
装饰者模式(Decorator)原理
装饰者模式就像打包一个快递
Component 主体类:
比如类似前面的 Drink
ConcreteComponent:
具体的主体,比如前面的各个单品咖啡
Decorator
:装饰者,比如各调料,装饰者里面聚合了一个 Component 的具体实现类
在如图的Component与ConcreteComponent之间
,如果实现类 ConcreteComponent 有很多,还可以设计一个缓冲层
,将共有的部分提取出来,抽象出一个缓冲层
我们都知道现在的女生比较喜欢爱美,这是必然的,素颜的很少,出门前得打点粉底,涂点口红,其实这就是装饰者模式的一种体现,妹妹既可以选择素颜出门,也可以选择稍作打版再出门,那么我们就为妹妹设计一个装饰者模式,让妹妹实现化妆自由。
展示者:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public interface Showable {
/**
* 展示行为
* @return void
* @author compass
* @date 2022/10/5 12:59
* @since 1.0.0
**/
void show();
}
女生类:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Girl implements Showable {
@Override
public void show() {
System.out.print("妹妹的素颜");
}
}
抽象装饰器:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public abstract class Decorator implements Showable {
private Showable showable;
public Decorator(Showable showable) {
this.showable = showable;
}
@Override
public void show() {
// 直接调用不加任何装饰,巧妙之处在于如果子类传递了具体实现,就调用子类具体实现的行为
showable.show();
}
}
粉底类:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class FoundationMakeup extends Decorator {
// 调用父类构造注入
public FoundationMakeup(Showable showable) {
super(showable);
}
@Override
public void show() {
System.out.print("打粉底【");
super.show();
System.out.print("】");
}
}
口红类:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Lipstick extends Decorator {
// 调用父类构造注入
public Lipstick(Showable showable) {
super(showable);
}
@Override
public void show() {
System.out.print("涂口红【");
super.show();
System.out.print("】");
}
}
客户端类:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
Lipstick lipstick = new Lipstick(new FoundationMakeup(new Girl()));
lipstick.show();
}
}
InputStream
是(被)装饰者的抽象父类,类似我们前面讲的 DrinkFileInputStream
是 InputStream
子类,为具体的被装饰者,类似我们前面的 DeCaf,LongBlackFilterInputStream
是InputStream
子类,为装饰者的抽象父类,类似我们前面 的 Decorator
装饰者DataInputStream
是 FilterInputStream
子类,为具体的装饰者,类似前面的 Milk,Soy 等FilterInputStream
类中有protected volatile InputStream in;
代码,即其中含有被装饰者装饰者模式
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
具体要求
ITeacherDao
TeacherDAO
实现接口ITeacherDAO
TeacherDAOProxy
中也实现ITeacherDAO
代码实现:
ITeacherDao:定义功能的接口
/**
* @author compass
* @version 1.0
* @date 2021-07-27 19:36
*/
public interface ITeacher {
/**
* 授课接口
*/
void teach();
}
TeacherDAO:接口的实现类
/** 代理对象,静态代理
* @author compass
* @version 1.0
* @date 2021-07-27 19:37
*/
public class TeacherDao implements ITeacher {
/**
* 授课接口
*/
@Override
public void teach() {
System.out.println("老师正在授课");
}
}
TeacherDAOProxy:代理对象
/**
* @author compass
* @version 1.0
* @date 2021-07-27 19:38
*/
public class TeacherDAOProxy implements ITeacher{
private ITeacher target;
/**
* 授课接口
*/
@Override
public void teach() {
System.out.println("代理开始");
target.teach();
System.out.println("代理结束");
}
public TeacherDAOProxy(ITeacher target) {
this.target = target;
}
}
Client:调用者
/**
* @author compass
* @version 1.0
* @date 2021-07-27 19:37
*/
public class Client {
public static void main(String[] args) {
// 创建具体的对象
TeacherDao teacherDao = new TeacherDao();
// 将对象传递给代理对象,让代理去调用具体的方法,好处就是可以在调用具体方法前后进行其余的功能扩展
TeacherDAOProxy proxy = new TeacherDAOProxy(teacherDao);
// 代理对象调用具体的方法
proxy.teach();
}
}
静态代理优缺点
动态代理模式的基本介绍
JDK代理
、 接口代理JDK中生成代理对象的API
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance
方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance
(ClassLoader loader, Class[] interfaces, InvocationHandler h)
动态代理应用实例
将前面的静态代理改进成动态代理模式(即:JDK代理模式)
代码实现:
Client:
调用者的客户端
/** jdk:动态代理
* @author compass
* @version 1.0
* @date 2021-07-27 19:56
*/
public class Client {
public static void main(String[] args) {
// 创建目标对象
ITeacher target = new TeacherDao();
// 创建代理对象 此处不会去执行目标方法,而是我们的代理对象去调用目标方法的时候才会真正的去调用目标方法
ITeacher proxyInstance = (ITeacher)new ProxyFactory(target).getProxyFactory();
System.out.println(proxyInstance.getClass());
// 让代理对象去调用具体的方法
String result = proxyInstance.teach("小明");
System.out.println("方法执行完后的返回值:"+result);
}
}
ITeacher:
功能接口
/**
* @author compass
* @version 1.0
* @date 2021-07-27 19:56
*/
public interface ITeacher {
/**
* 授课方法
*/
String teach(String name);
}
TeacherDao:
功能接口的实现类
/**
* @author compass
* @version 1.0
* @date 2021-07-27 19:57
*/
public class TeacherDao implements ITeacher{
/**
* 授课方法
*/
@Override
public String teach(String name) {
System.out.println(name+":老师正在授课中...");
return "very nice";
}
}
ProxyFactory:
动态代理对象
/**
* @author compass
* @version 1.0
* @date 2021-07-27 19:57
*/
public class ProxyFactory {
// 被代理的目标对象
private Object target;
// 在创建代理对象的时候对目标对象进行初始化
public ProxyFactory(Object target) {
this.target = target;
}
// 为目标对象生成代理对象
public Object getProxyFactory(){
/**
* loader:指定目标对象使用的类加载器,获取加载方法固定
* interface: 目标对象实现的接口类型,使用泛型方法确认类型
* invocationHandler:情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk代理对象开始");
// 通过反射机制调用目标对象的方法
Object resultValue = method.invoke(target,args);
System.out.println("jdk代理对象结束");
return resultValue;
}
});
}
}
Cglib代理模式的基本介绍
JDK代理
模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理,这就是Cglib代理Cglib代理
也叫作子类代理
,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理AOP
的框架使用,例如Spring AO
P,实现方法拦截
用JDK代理
用Cglib代理
字节码处理框架ASM来转换字节码并生成新的类
Cglib代理模式实现步骤
在内存中动态构建子类, 注意代理的类不能为final,否则报错java.lang.IllegalArgumentException
目标对象的方法如果为final 或 static,那么就不会被拦截,即不会执行目标对象额外的业务方法
Cglib代理模式应用实例
TeacherDao:
被代理类
/**
* @author compass
* @version 1.0
* @date 2021-07-27 22:26
*/
public class TeacherDao {
public String teach() {
System.out.println(" 老师授课中 , 我是cglib代理,不需要实现接口 ");
return "hello";
}
}
ProxyFactory:
代理工厂类
/**
* @author compass
* @version 1.0
* @date 2021-07-27 22:26
*/
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 {
System.out.println("Cglib代理模式 ~~ 开始");
Object returnVal = method.invoke(target, args);
System.out.println("Cglib代理模式 ~~ 提交");
return returnVal;
}
}
Client:
客户端
/** cglib:代理模式,不需要实现接口,被代理的对象可以直接是一个单独类,采用拦截的方式,创建一个子类去调用
* @author compass
* @version 1.0
* @date 2021-07-27 22:26
*/
public class Client {
public static void main(String[] args) {
// 创建目标对象
TeacherDao target = new TeacherDao();
// 获取到代理对象,并且将目标对象传递给代理对象
TeacherDao proxyInstance = (TeacherDao) new ProxyFactory(target).getProxyInstance();
// 执行代理对象的方法,触发intercept 方法,从而实现 对目标对象的调用
String res = proxyInstance.teach();
System.out.println("res=" + res);
}
}
防火墙代理:内网通过代理穿透防火墙,实现对公网的访问
缓存代理:比如:当请求图片文件等资源时,先到缓存代理去取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存
Client:
桥接模式的调用者Abstraction:
抽象类,Abstraction 中维护了一个 Implementor 实现类的实例(聚合关系),Abstraction 充当桥接类RefinedAbstraction:
Abstraction 的具体实现类 Implementor:
定义行为的接口ConcreteImplementor:
Implementor 的具体实现类从 UML 图: 这里的抽象类和接口是聚合的关系, 其实也是调用和被调用关系,抽象在 Abstraction 这一块,行为实现在 Implementor 这一块
举个例子:比如说我们需要画项目结构图,需要尺子和笔进行来绘画,尺子呢有不同类型的,有正方形的,三角形的,圆形的,笔呢也有很多颜色,有白色,黑色,我们可以使用桥接模式把他们组合到一起进行使用。
尺子接口:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public interface Ruler {
/**
* 规定尺子的走向行为
*
* @return void
* @author compass
* @date 2022/10/5 14:22
* @since 1.0.0
**/
String regularize();
}
三角形尺子:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class TriangleRuler implements Ruler {
@Override
public String regularize() {
// 输出三角形
return "△";
}
}
正方形尺子:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class SquareRuler implements Ruler {
@Override
public String regularize() {
// 输出正方形
return "□";
}
}
圆形尺子:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class CircleRuler implements Ruler {
@Override
public String regularize() {
// 输出圆形
return "○";
}
}
画笔类:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public abstract class Pen {
// 将尺子组合进来
protected Ruler ruler;
public Pen(Ruler ruler) {
this.ruler = ruler;
}
// 笔画画的抽象方法
public abstract void draw();
}
黑色画笔:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class BlackPen extends Pen {
public BlackPen(Ruler ruler) {
super(ruler);
}
@Override
public void draw() {
String regularize = super.ruler.regularize();
System.out.println("黑色笔画【"+regularize+"】");
}
}
白色画笔:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class WhitePen extends Pen {
public WhitePen(Ruler ruler) {
super(ruler);
}
@Override
public void draw() {
String regularize = super.ruler.regularize();
System.out.println("黑色笔画【"+regularize+"】");
}
}
客户端调用:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
new WhitePen(new CircleRuler()).draw();
new WhitePen(new SquareRuler()).draw();
new WhitePen(new TriangleRuler()).draw();
}
}
实现了抽象和实现部分的分离, 从而极大的提供了系统的灵活性, 让抽象部分和实现部分独立开来, 这有助于系统进行分层设计, 从而产生更好的结构化系统。
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统, 桥接模式尤为适用
模板方法模式(Template Method Pattern
) , 又叫模板模式(Template Pattern
), 在一个抽象类公开定义了执行它的方法的模板,它的子类可以按需要重写方法实现, 但调用将以抽象类中定义的方式进行
简单说, 模板方法模式定义一个操作中的算法(流程)的骨架, 而将一些步骤延迟到子类中, 使得子类可以不改变一个算法的结构, 就可以重定义该算法的某些特定步骤
模板方法设计模式属于行为型模式
AbstractClass
为抽象类, 类中实现了template()
模板方法, 该方法定义了算法的骨架, 具体子类需要去实现抽象方法 operation 2,3,4
ConcreteClass实现抽象方法
operation 2,3,4`以完成算法中特定子类的步骤
我们人类其实也是动物,和启动动物一样,我们都具有相同的行为,我们可以把这些共同的行为抽象为一个模板,然后由具体的子类去实现这个行为。
生活行为接口:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public abstract class Live {
/**
* 生活方法 只管调用该方法就可以完成具体的生活方式,
* 此处代码完全不用改动,控制权交给子类
*
* @return void
* @author compass
* @date 2022/10/5 14:40
* @since 1.0.0
**/
public void live(){
eat();
exercise();
}
/**
* 吃食物的方法,具体吃什么?怎么吃交给子类去实现
* @return void
* @author compass
* @date 2022/10/5 14:42
* @since 1.0.0
**/
protected abstract void eat();
/**
* 运动的方法。具体怎么运动交给子类实现
* @return void
* @author compass
* @date 2022/10/5 14:42
* @since 1.0.0
**/
protected abstract void exercise();
}
人类:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Person extends Live {
@Override
protected void eat() {
System.out.println("人类在吃米饭");
}
@Override
protected void exercise() {
System.out.println("人类在跑步");
}
}
猫类:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Cat extends Live {
@Override
protected void eat() {
System.out.println("猫咪吃小鱼干");
}
@Override
protected void exercise() {
System.out.println("猫咪在抓老鼠");
}
}
客户端调用:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
Live person = new Person();
person.live();
Live cat = new Cat();
cat.live();
}
}
模板方法模式的注意事项和细节
final
关键字, 防止子类重写模板方法比如我们需要记录行车记录仪中的记录,然后进行查看,这就是可以使用迭代器模式进行查看,假设我们设置最多最多只能查看最近的10条记录,超过10条就行进行覆盖操作。
行车记录仪类:
import java.util.Iterator;
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class DrivingRecorder implements Iterable<String> {
// 当前记录索引位置
private int index;
// 假设只能存放10条记录
private String[] records = new String[10];
/**
* 添加行车记录
*
* @param record 记录
* @return void
* @author compass
* @date 2022/10/5 15:26
* @since 1.0.0
**/
public void append(String record) {
if (index == 9) {
index = 0;
} else {
index++;
}
records[index] = record;
}
@Override
public Iterator<String> iterator() {
return new Itr();
}
// 迭代器实现
private class Itr implements Iterator {
int cursor = index;
int loopCount = 0;
@Override
public boolean hasNext() {
return loopCount<records.length;
}
@Override
public Object next() {
int i = cursor;
if (cursor == 0){
cursor = records.length-1;
}else {
cursor--;
}
loopCount++;
return records[i];
}
}
}
客户端使用:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
DrivingRecorder recorder = new DrivingRecorder();
for (int i = 0; i < 12; i++) {
recorder.append(String.format("第%d条行车记录",i));
}
// 获取迭代器
Iterator<String> iterator = recorder.iterator();
// 使用while的形式
while (iterator.hasNext()){
String item = iterator.next();
System.out.println(item);
}
System.out.println("---------------------");
// 使用增强for
for (String item : recorder) {
System.out.println(item);
}
}
}
优点
缺点
每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类
最近小王啊出差,需要向公司报销出差费用。但是他们公司的报销有一个规则,就是1000元一下由财务报销,2000元以下项目经理报销。超过2000元以上必须有董事长进行报销。搞的这个审批流程很麻烦,他必须得清除这个报销流程,不然的话就会导致他先去财务报销,被驳回,然后他再去找项目经理,然后再驳回,最后找到董事长,如果运气好一次就行,如果运气不好,有不熟悉规则,那么就得被驳回好几次。我们现在就失业责任链模式进行榜小王进行改善,让他只提交一次申请,不用管这个审批流程,由责任链帮他找到对应的审批人。
抽象审批人:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public abstract class Approver {
// 抽象审批人姓名
protected String approveName;
// 下一位审批人
protected Approver nextApprover;
public Approver(String approveName) {
this.approveName = approveName;
}
public Approver setApprover(Approver approver) {
this.nextApprover = approver;
return this.nextApprover;
}
/**
* 抽象审批方法
* @param amount 需要审批的金额
* @return void
* @author compass
* @date 2022/10/6 17:36
* @since 1.0.0
**/
public abstract void approver(int amount);
}
财务专员:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Staff extends Approver {
public Staff(String approveName) {
super(approveName);
}
@Override
public void approver(int amount) {
if (amount<=1000){
System.out.println(String.format("审批通过,金额[%d],审核人[财务]:%s",amount,approveName));
}else {
System.out.println(String.format("无权审批,金额[%d],审核人[财务]:%s",amount,approveName));
this.nextApprover.approver(amount);
}
}
}
项目经理:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class ProjectManager extends Approver {
public ProjectManager(String approveName) {
super(approveName);
}
@Override
public void approver(int amount) {
if (amount<=2000){
System.out.println(String.format("审批通过,金额[%d],审核人[项目经理]:%s",amount,approveName));
}else {
System.out.println(String.format("无权审批,金额[%d],审核人[项目经理]:%s",amount,approveName));
this.nextApprover.approver(amount);
}
}
}
董事长:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class President extends Approver {
public President(String approveName) {
super(approveName);
}
@Override
public void approver(int amount) {
if (amount<=3000){
System.out.println(String.format("审批通过,金额[%d],审核人[董事长]:%s",amount,approveName));
}else {
System.out.println(String.format("无权审批,金额[%d],审核人[董事长]:%s",amount,approveName));
this.nextApprover.approver(amount);
}
}
}
小王去申请:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
Approver approver = new Staff("张飞");
approver.setApprover(new ProjectManager("关羽")).setApprover(new President("刘备"));
// 1000以内找财务
approver.approver(1000);
// 2000以内找项目经理
approver.approver(2000);
// 3000以内找董事长
approver.approver(3000);
}
}
从图中可以看到, 客户 context 有成员变量 strategy 或者其他的策略接口,至于需要使用到哪个策略, 我们可以在构造器中指定
我们知道电脑的use接口,是功能特别强大的,,可以插入鼠标,键盘,u盘等设备,主要是他对外暴露的都是同一个接口,使用者只需要插入即可完成对应的功能。
USB接口:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public interface USB {
/**
* 抽象读取行为
*
* @return java.lang.String
* @author compass
* @date 2022/10/6 18:13
* @since 1.0.0
**/
String read();
}
鼠标类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Mouse implements USB {
@Override
public String read() {
System.out.println("鼠标数据");
return "鼠标数据";
}
}
键盘类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class KeyBoard implements USB {
@Override
public String read() {
System.out.println("键盘指令数据");
return "键盘指令数据";
}
}
摄像头类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Camera implements USB {
@Override
public String read() {
System.out.println("摄像头数据");
return "摄像头数据";
}
}
计算机类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Computer {
// 电脑主机上的USB接口
private USB usb;
public void setUsb(USB usb){
this.usb = usb;
}
public String computer(){
return usb.read();
}
}
客户端使用:
/**
* @author compass
* @date 2022-10-05
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
computer.setUsb(new KeyBoard());
computer.computer();
computer.setUsb(new Mouse());
computer.computer();
computer.setUsb(new Camera());
computer.computer();
}
}
有一位作家平时在写作,然后一不小心去喝水的时候,被调皮的小猫咪按下了del键,把一些内容删除掉,这写作可是靠灵感的啊,这回头在写可能就没那么好的效果了,而且也比较耗时。一般的编辑器都有ctrl+z回退的效果,我们就使用这个状态模式,把作家的每次写入记录都给保存,起来,即使作家不小心删除,或者断电都能找回他的文章,如果要避免断电内容丢失的话,最好的结果就是每隔一段时间把内存中的记录写到磁盘,这才是万无一失的。这里我们只保存到内存。
文档类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Document {
// 标题
private String title;
// 内容
private String content;
public Document(String title) {
this.title = title;
// 内容默认为空
this.content = "";
}
/**
* 创建历史记录
*
* @return compass.token.pocket.com.service.demo.History
* @author compass
* @date 2022/10/6 20:32
* @since 1.0.0
**/
public History createHistory() {
return new History(content);
}
/**
* 恢复历史记录
*
* @return compass.token.pocket.com.service.demo.History
* @author compass
* @date 2022/10/6 20:32
* @since 1.0.0
**/
public void restoreHistory(History history){
this.content = history.getContent();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
编辑器类:
import java.util.ArrayList;
import java.util.List;
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Editor {
private Document document;
private List<History> historyList;
private int historyPosition = -1;
public Editor(Document document) {
System.out.println(">>>打开文档:" + document.getTitle());
this.document = document;
// 初始化历史记录容器
historyList = new ArrayList<>();
// 载入文档后保存一份
backup();
// 显示内容
show();
}
// 显示内容
private void show() {
System.out.println(">>>文档开始");
System.out.println(document.getContent());
System.out.println(">>>文档结束");
}
// 备份
private void backup() {
historyList.add(document.createHistory());
historyPosition++;
}
// 撤销操作
public void undo(){
System.out.println(">>>撤销操作");
if (historyPosition ==0){
System.out.println("已经到最原始记录,不能再撤销");
return;
}
historyPosition--;
History history = historyList.get(historyPosition);
// 取出历史记录并且恢复到编辑器中
document.restoreHistory(history);
show();
}
// 添加内容
public void append(String text) {
System.out.println(">>>插入操作");
document.setContent(document.getContent() + "\n" + text);
// 保存一份显示内容
backup();
// 显示内容
show();
}
// 删除操作
public void delete() {
System.out.println(">>>删除操作");
document.setContent("");
// 删除后保存一份历史记录
backup();
// 显示内容
show();
}
// 保存操作
public void save() {
System.out.println(">>>保存到磁盘");
}
}
历史记录类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class History {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public History(String content) {
this.content = content;
}
}
客户端:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
Editor editor = new Editor(new Document("AI的觉醒"));
editor.append("第一章混沌初开");
editor.append("正文20000字");
editor.append("第二章混沌初开");
editor.append("正文10000字");
editor.delete();
editor.undo();
}
}
Receiver
(命令执行者)解耦比如我们要进行开灯和关灯,其实就是2个指令,我们可以用命令模式来实现,指定2个指令,开和关即可,如果我们想灯是闪烁效果,我们还可以新增一种闪烁命令。
灯泡类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Blub {
public void on(){
System.out.println("开灯");
}
public void off(){
System.out.println("关灯");
}
}
命令接口:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public interface Command {
// 执行命令
void exe();
// 撤销命令
void unexce();
}
开命令类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Open implements Command {
private Blub blub;
public Open(Blub blub) {
this.blub = blub;
}
@Override
public void exe() {
blub.on();
}
@Override
public void unexce() {
blub.off();
}
}
关命令类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Close implements Command {
private Blub blub;
public Close(Blub blub) {
this.blub = blub;
}
@Override
public void exe() {
blub.off();
}
@Override
public void unexce() {
blub.on();
}
}
闪烁命令类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class FlashCommand implements Command {
private Blub blub;
// 闪烁命令运行状态
private volatile boolean neonRun;
public FlashCommand(Blub blub) {
this.blub = blub;
}
@Override
public synchronized void exe() {
// 非命令运行时才启动闪烁线程
if (!neonRun) {
neonRun = true;
System.out.println("闪烁命令开始");
new Thread(() -> {
while (neonRun) {
try {
blub.off();
Thread.sleep(500);
blub.on();
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
@Override
public void unexce() {
neonRun = false;
System.out.println("霓虹灯任务结束");
}
}
开关类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Switcher {
private Command command;
public Switcher(Command command) {
this.command = command;
}
public void setCommand(Command command) {
this.command = command;
}
public void buttonPush(){
System.out.println("按下按钮");
command.exe();
}
public void buttonPop(){
System.out.println("弹起按钮");
command.unexce();
}
}
客户端调用:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Client {
public static void main(String[] args) throws InterruptedException {
Blub blub = new Blub();
FlashCommand flashCommand = new FlashCommand(blub);
Switcher switcher = new Switcher(flashCommand);
switcher.buttonPush();
Thread.sleep(3000);
switcher.buttonPop();
}
}
将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作, 也就是说: ”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用
容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令容易实现对请求的撤销和重做
命令模式不足:可能导致某些系统有过多的具体命令类, 增加了系统的复杂度, 这点在在使用的时候要注意
空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦
命令模式经典的应用场景:界面的一个按钮都是一条命令、 模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制
Visitor 是抽象访问者,定义访问者的行为规范
ConcreteVisitor :是一个具体的访问者,继承(或实现) Visitor,实现 Visitor 中定义的每个方法,实现具体的行为逻辑
Element 定义一个accept 方法,用于接收一个访问者对象(Visitor 的具体实现类)
ConcreteElement 为具体元素, 实现了 Element 接口中 accept 方法
ObjectStructure 能枚举它里面所包含的元素(Element), 可以提供一个高层的接口,目的是允许访问者访问指定的元素
超市有不同类型的商品,商品的计价方式也不同,有的是原价,有的会打折,无论价格计算方式有多么复杂,最终都是由收银员进行统一处理。
商品抽象类:
import java.time.LocalDate;
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class Product {
private String name;
private LocalDate productDate;
private float price;
public Product(String name, LocalDate productDate, float price) {
this.name = name;
this.productDate = productDate;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDate getProductDate() {
return productDate;
}
public void setProductDate(LocalDate productDate) {
this.productDate = productDate;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
}
水果类:
import java.time.LocalDate;
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class Fruit extends Product implements AcceptTable{
public Fruit(String name, LocalDate productDate, float price) {
super(name, productDate, price);
}
@Override
public void accept(Vistor vistor) {
vistor.visit(this);
}
}
酒水类:
import java.time.LocalDate;
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class Wine extends Product implements AcceptTable{
public Wine(String name, LocalDate productDate, float price) {
super(name, productDate, price);
}
@Override
public void accept(Vistor vistor) {
vistor.visit(this);
}
}
糖果类:
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class Candy extends Product implements AcceptTable{
public Candy(String name, LocalDate productDate, float price) {
super(name, productDate, price);
}
@Override
public void accept(Vistor vistor) {
vistor.visit(this);
}
}
访问者接口:
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public interface Vistor {
// 重载糖果方法
float visit(Candy candy);
// 重载酒水方法
float visit(Wine wine);
// 重载水果方法
float visit(Fruit fruit);
}
折扣计价访问者:
import java.time.LocalDate;
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class DiscountVisitor implements Vistor {
// 结算日期
private LocalDate billDate;
public DiscountVisitor(LocalDate billDate) {
System.out.println(String.format("结算日期:%s", billDate.toString()));
this.billDate = billDate;
}
@Override
public float visit(Candy candy) {
float rate = 0;
long days = billDate.toEpochDay() - candy.getProductDate().toEpochDay();
if (days > 180) {
System.out.println(String.format("%s超过半年请勿食用",candy.getName()));
} else {
rate = 0.9f;
}
float discountPrice = candy.getPrice() * rate;
System.out.println(String.format("糖果[%s]打折后的价格是:%f", candy.getName(), discountPrice));
return discountPrice;
}
@Override
public float visit(Wine wine) {
System.out.println(String.format("酒[%s]无折扣", wine.getName()));
return wine.getPrice();
}
@Override
public float visit(Fruit fruit) {
float rate = 0;
long days = billDate.toEpochDay() - fruit.getProductDate().toEpochDay();
if (days > 7) {
System.out.println(String.format("%s超过7天请勿食用",fruit.getName()));
} else if (days > 3) {
rate = 0.5f;
} else {
rate = 1f;
}
float discountPrice = fruit.getPrice() * rate;
System.out.println(String.format("水果[%s]打折后的价格是:%f", fruit.getName(), discountPrice));
return discountPrice;
}
}
接待者接口:
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public interface AcceptTable {
// 主动接待访问者
void accept( Vistor vistor);
}
客户端使用:
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
Candy candy = new Candy("大白兔奶糖", LocalDate.of(2019, 10, 11), 20.f);
Fruit fruit = new Fruit("飞天茅台", LocalDate.of(2019, 10, 11), 50.f);
Wine wine = new Wine("精品香蕉", LocalDate.of(2019, 10, 11), 60.f);
DiscountVisitor visitor = new DiscountVisitor(LocalDate.of(2020, 1, 1));
List<? extends AcceptTable> products = Arrays.asList(candy, fruit, wine);
for (AcceptTable product : products) {
product.accept(visitor);
}
}
}
观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
在观察者模式中有如下角色:
商店老板进货了,很多顾客需要进行购买,老板还没进货的时候,顾客就来问,一直问,很多顾客都来问老板,这样老板就非常的麻烦,老板觉得麻烦,顾客也觉得麻烦,那怎么办?不能反客为主么?让老板来通知所有顾客,让顾客关注他的微信公众号,然后,一旦进货,就给所有人推送到货的消息,这样顾客就知道货到了,直接来店里购买,老板也只需要通知一次就行。
商店类:
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class Phone extends Buyer {
public Phone(String name) {
super(name);
}
@Override
public void inform(String product) {
if (product.contains("手机")){
System.out.println(String.format("购买:%s",product));
}
}
}
抽象买家类:
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public abstract class Buyer {
private String name;
public Buyer(String name) {
this.name = name;
}
// 消息推送
public abstract void inform(String product);
}
手机买家类:
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class Phone extends Buyer {
public Phone(String name) {
super(name);
}
@Override
public void inform(String product) {
if (product.contains("手机")){
System.out.println(String.format("购买:%s",product));
}
}
}
电脑买家类:
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class computer extends Buyer {
public computer(String name) {
super(name);
}
@Override
public void inform(String product) {
if (product.contains("电脑")){
System.out.println(String.format("购买:%s",product));
}
}
}
客户端调用:
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
computer computer = new computer("电脑买家");
Phone phone = new Phone("手机买家");
Shop shop = new Shop();
shop.register(computer);
shop.register(phone);
shop.setProduct("小米笔记本电脑");
shop.setProduct("三星手机");
}
}
解释器模式
应用场景
解释器模式的原理类图
解释器的抽象父类:
import java.util.HashMap;
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public abstract class Expression {
// a + b - c
// 解释公式和数值, key 就是公式(表达式)中的参数[a,b,c], value就是就是具体值
// HashMap {a=10, b=20}
public abstract int interpreter(HashMap<String, Integer> var);
}
变量的解释器:
import java.util.HashMap;
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class VarExpression extends Expression {
private String key; // key=a,key=b,key=c
public VarExpression(String key) {
this.key = key;
}
// var 就是{a=10, b=20}
// interpreter 根据 变量名称,返回对应值
@Override
public int interpreter(HashMap<String, Integer> var) {
return var.get(this.key);
}
}
抽象运算符解释器:
import java.util.HashMap;
/**
* 抽象运算符号解析器 这里,每个运算符号,都只和自己左右两个数字有关系,
* 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression类的实现类
* @author compass
* @date 2022/10/8 16:36
* @since 1.0.0
**/
public abstract class SymbolExpression extends Expression {
protected Expression left;
protected Expression right;
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
// 因为 SymbolExpression 是让其子类来实现,因此 interpreter 是一个默认实现
@Override
public int interpreter(HashMap<String, Integer> var) {
return 0;
}
}
加法运算符的解释器:
import java.util.HashMap;
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class AddExpression extends SymbolExpression {
public AddExpression(Expression left, Expression right) {
super(left, right);
}
// 处理相加
// var 仍然是 {a=10,b=20}..
// 一会我们debug 源码,就ok
public int interpreter(HashMap<String, Integer> var) {
// left.interpreter(var) : 返回 left 表达式对应的值 a = 10
// right.interpreter(var): 返回right 表达式对应值 b = 20
return left.interpreter(var) + right.interpreter(var);
}
}
减法运算符的解释器:
import java.util.HashMap;
import java.util.Stack;
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class Calculator {
// 定义表达式
private Expression expression;
// 构造函数传参,并解析
public Calculator(String expStr) { // expStr = a+b
// 安排运算先后顺序
Stack<Expression> stack = new Stack<>();
// 表达式拆分成字符数组
char[] charArray = expStr.toCharArray();// [a, +, b]
Expression left = null;
Expression right = null;
// 遍历我们的字符数组, 即遍历 [a, +, b]
// 针对不同的情况,做处理
for (int i = 0; i < charArray.length; i++) {
switch (charArray[i]) {
case '+': //
left = stack.pop();// 从stack取出left => "a"
right = new VarExpression(String.valueOf(charArray[++i]));// 取出右表达式 "b"
stack.push(new AddExpression(left, right));// 然后根据得到left 和 right 构建 AddExpresson加入stack
break;
case '-': //
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left, right));
break;
default:
// 如果是一个 Var 就创建一个 VarExpression 对象,并push到 stack
stack.push(new VarExpression(String.valueOf(charArray[i])));
break;
}
}
// 当遍历完整个 charArray 数组后,stack 就得到最后Expression
this.expression = stack.pop();
}
public int run(HashMap<String, Integer> var) {
// 最后将表达式a+b和 var = {a=10,b=20}
// 然后传递给expression的interpreter进行解释执行
return this.expression.interpreter(var);
}
}
客户端调用:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
/**
* @author compass
* @date 2022-10-08
* @since 1.0
**/
public class Client {
public static void main(String[] args) throws IOException {
String expStr = getExpStr(); // a+b
HashMap<String, Integer> var = getValue(expStr);// var {a=10, b=20}
Calculator calculator = new Calculator(expStr);
System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
}
// 获得表达式
public static String getExpStr() throws IOException {
System.out.print("请输入表达式:");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
// 获得值映射
public static HashMap<String, Integer> getValue(String expStr) throws IOException {
HashMap<String, Integer> map = new HashMap<>();
for (char ch : expStr.toCharArray()) {
if (ch != '+' && ch != '-') {
if (!map.containsKey(String.valueOf(ch))) {
System.out.print("请输入" + String.valueOf(ch) + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}
return map;
}
}
状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
Context 类为上下文对象,用于维护State实例,这个实例定义当前状态
State 是抽象的状态角色,定义一个接口封装与Context 的一个特定接口相关行为
ConcreteState 具体的状态角色,每个子类实现一个与Context 的一个状态相关行为,
交通灯的状态切换其实就是一个状态的不断改变,他不像是简单的开和关2个状态,而是多个状态之前的切换,而且切换一定要正确,不然容易造成严重的事故,我们来看下如何使用状态模式完成交通灯的一个切换。
状态接口:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public interface State {
/**
* 切换到绿灯
*
* @return void
* @author compass
* @date 2022/10/6 19:53
* @since 1.0.0
**/
void switchToGreen(TrafficLight trafficLight);
/**
* 切换到黄灯
*
* @return void
* @author compass
* @date 2022/10/6 19:53
* @since 1.0.0
**/
void switchToYellow(TrafficLight trafficLight);
/**
* 切换到红灯
*
* @return void
* @author compass
* @date 2022/10/6 19:53
* @since 1.0.0
**/
void switchToRed(TrafficLight trafficLight);
}
红灯状态:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Red implements State {
@Override
public void switchToGreen(TrafficLight trafficLight) {
System.out.println("红灯不能切换为绿灯");
}
@Override
public void switchToYellow(TrafficLight trafficLight) {
trafficLight.setState(new Yellow());
System.out.println("黄灯亮起5秒");
}
@Override
public void switchToRed(TrafficLight trafficLight) {
System.out.println("已经是红灯状态不能再切换");
}
}
黄灯状态:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Yellow implements State {
@Override
public void switchToGreen(TrafficLight trafficLight) {
trafficLight.setState(new Green());
System.out.println("绿灯亮起60秒");
}
@Override
public void switchToYellow(TrafficLight trafficLight) {
System.out.println("已经是黄灯状态不能再切换");
}
@Override
public void switchToRed(TrafficLight trafficLight) {
trafficLight.setState(new Red());
System.out.println("红灯亮起60秒");
}
}
绿灯状态:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Green implements State {
@Override
public void switchToGreen(TrafficLight trafficLight) {
System.out.println("已经是绿灯不能再切换");
}
@Override
public void switchToYellow(TrafficLight trafficLight) {
trafficLight.setState(new Yellow());
System.out.println("黄灯亮起5秒");
}
@Override
public void switchToRed(TrafficLight trafficLight) {
System.out.println("绿灯不能切换为黄灯");
}
}
交通管理类:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class TrafficLight {
// 默认是红灯状态
private State state = new Red();
public void setState(State state) {
this.state = state;
}
// 切换为通行状态
public void switchToGreen( ) {
state.switchToGreen(this);
}
// 切换为警告状态
public void switchToYellow() {
state.switchToYellow(this);
}
// 切换为禁行状态
public void switchToRed() {
state.switchToRed(this);
}
}
客户端调用:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
TrafficLight trafficLight = new TrafficLight();
trafficLight.switchToYellow();
trafficLight.switchToGreen();
trafficLight.switchToYellow();
trafficLight.switchToRed();
}
}
状态模式的注意事项和细节
优点
缺点
会产生很多类,每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
应用场景
当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式
中介模式定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。
中介模式各个角色描述如下:
Mediator(中介) 共事者之间通信的中介平台接口,定义共事者到通通信标准,如连接注册方法与发送消息方法等。
ConcreteMediator(中介实现)可以有很多种实现,持有共事者对象的列表,并实现中介定义的通信方法。
Colleague(共事者),ConcreteColleague(共事者实现),共事者可以有很多种共事者实现,共事者持有中介对象的引用,以使其在发生消息时可以调用中介。
众所周知对象间显示相互引用越多,意味着依赖性越强,同时独立性也就越弱,不利于代码的扩展和维护,中介模式可以很好的解决这个问题,他能讲多方协助的工作交给中间平台去完成,解除了你中有我,我中有你的一个弊端,让各个模块更加的松散,独立,最终增强系统的一个可用性。
抽象聊天室:
import java.util.ArrayList;
import java.util.List;
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public abstract class ChatRoom {
protected String name;
protected List<User> users = new ArrayList<>();
public ChatRoom(String name){
this.name = name;
}
// 注册用户到聊天室
protected void register(User user){
this.users.add(user);
}
// 将用户从聊天室中移除
protected void deregister(User user){
users.remove(user);
}
// 发送消息
protected abstract void sendMessage(User from,User to,String message);
// 消息加工
protected abstract String processMessage(User from,User to,String message);
}
公共聊天室:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class PublicChatRoom extends ChatRoom {
public PublicChatRoom(String name) {
super(name);
}
@Override
protected void register(User user) {
super.register(user);
System.out.println(String.format("系统消息:欢迎[%s]",user.getName()));
}
@Override
protected void sendMessage(User from, User to, String message) {
// 如果接收者为空,发送给所有人
if (Objects.isNull(to)){
users.forEach(user -> user.listen(from,null,message));
return;
}
// 否则发送给所有人
users.stream().filter(user -> user.equals(to) || user.equals(from))
.forEach(user -> user.listen(from, to, message));
}
@Override
protected String processMessage(User from, User to, String message) {
String toName = "所有人";
if (!Objects.isNull(to)){
toName = to.getName();
}
return from.getName()+"对"+toName+"说"+message;
}
}
单独聊天室:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class PrivateChatRoom extends ChatRoom {
public PrivateChatRoom(String name) {
super(name);
}
@Override
protected void register(User user) {
if (users.size()>2){
System.out.println("系统消息:聊天室已满");
return;
}
super.register(user);
System.out.println(String.format("欢迎%s加入2人聊天室:%s",user.getName(),name));
}
@Override
protected void sendMessage(User from, User to, String message) {
users.forEach(user -> user.listen(from, to, message));
}
@Override
protected String processMessage(User from, User to, String message) {
return String.format("%s说%s",from.getName(),message);
}
@Override
protected void deregister(User user) {
super.deregister(user);
System.out.println(String.format("系统消息:%s离开聊天室",user.getName()));
}
}
用户对象:
import java.util.Objects;
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class User {
private String name;
protected ChatRoom chatRoom;
protected void login(ChatRoom chatRoom){
chatRoom.register(this);
this.chatRoom = chatRoom;
}
protected void loginOut(ChatRoom chatRoom){
chatRoom.deregister(this);
this.chatRoom = null;
System.out.println(String.format("%s退出聊天室",name));
}
protected User(String name) {
this.name = name;
}
protected void talk(User to, String message){
if (Objects.isNull(to)){
System.out.println(String.format("%s的对话框,您还没登录",name));
return;
}
chatRoom.sendMessage(this,to,message);
}
public void listen(User from,User to, String message){
System.out.println(String.format("%s的对话框",name));
// 调用聊天室加工消息
System.out.println(chatRoom.processMessage(from,to,message));
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ChatRoom getChatRoom() {
return chatRoom;
}
public void setChatRoom(ChatRoom chatRoom) {
this.chatRoom = chatRoom;
}
}
客户端使用:
/**
* @author compass
* @date 2022-10-06
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
PublicChatRoom chatRoom = new PublicChatRoom("设计模式");
User admin = new User("admin");
User root = new User("root");
admin.login(chatRoom);
root.login(chatRoom);
chatRoom.sendMessage(admin,null,"大家好我是admin");
chatRoom.sendMessage(admin,root,"你好root");
admin.loginOut(chatRoom);
root.loginOut(chatRoom);
}
}
观察者模式有多种实现方式。虽然经典的实现方式没法彻底解耦观察者和被观察者,观察者需要注册到被观察者中,被观察者状态更新需要调用观察者的 update() 方法。但是,在跨进程的实现方式中,我们可以利用消息队列实现彻底解耦,观察者和被观察者都只需要跟消息队列交互,观察者完全不知道被观察者的存在,被观察者也完全不知道观察者的存在。
中介模式也是为了解耦对象之间的交互,所有的参与者都只与中介进行交互。而观察者模式中的消息队列,就有点类似中介模式中的“中介”,观察者模式的中观察者和被观察者,就有点类似中介模式中的“参与者”。那么,中介模式和观察者模式的区别在哪里呢?什么时候选择使用中介模式?什么时候选择使用观察者模式呢?
在观察者模式中,尽管一个参与者既可以是观察者,同时也可以是被观察者,但是,大部分情况下,交互关系往往都是单向的,一个参与者要么是观察者,要么是被观察者,不会兼具两种身份。也就是说,在观察者模式的应用场景中,参与者之间的交互关系比较有条理。
而中介模式正好相反,多个交互类,观察者同时是被观察者。只有当参与者之间的交互关系错综复杂,维护成本很高的时候,我们才考虑使用中介模式。毕竟,中介模式的应用会带来一定的副作用,它有可能会产生大而复杂的上帝类。除此之外,如果一个参与者状态的改变,其他参与者执行的操作有一定先后顺序的要求,这个时候,中介模式就可以利用中介类,通过先后调用不同参与者的方法,来实现顺序的控制,而观察者模式是无法实现这样的顺序要求的。
中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。