设计模式
学习视频来自B站,感谢狂神的分享:B站视频地址
参考学习网站:语言中文网
设计模式概述
设计模式:不是语法规定,是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案
GOF23,就是常说的23种设计模式:
设计模式的本质:是对面向对象设计原则的实际运用,是对类的封装、继承、多态以及类的关联关系和组合关系的充分理解
使用设计模式,有以下优点:
- 提高程序员的思维能力、编程能力和设计能力
- 使程序设计更加标准化、代码编制更加工程化,提高开发效率
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强
设计模式的基本要素:模式名称、解决的问题、解决方案、效果(优缺点)
GOF23 : 一种思维,一种态度,一种进步
设计模式分为三大类:
模式分类 | 作用 | 具体模式 |
---|---|---|
创建型模式 | 描述怎么创建一个对象 创建和使用分离 | 单例模式、工厂模式、抽象工厂模式、 建造者模式、原型模式 |
结构型模式 | 描述如何将类和对象按照某种类型, 组成更大的结构 | 适配器模式、桥接模式、装饰模式、 组合模式、外观模式、享元模式、代理模式 |
行为型模式 | 描述类和对象如何相互协作, 完成单个类无法完成的任务 | 模板方法模式、命令模式、迭代器模式、观察者模式、 中介者模式、备忘录模式、解释器模式、状态模式、 策略模式、职责链模式、访问者模式 |
OOP7大原则
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:继承必须确保,超类所拥有的的性质,在子类中仍然成立(子类尽量不重写父类方法,可以添加新方法实现功能–正方形不是长方形)
- 依赖导致原则:要面向接口编程,不要面向实现编程(抽象不依赖细节,细节依赖抽象)
- 单一职责原则:控制类的粒度大小,将对象解耦、提高其内聚性(一个对象(方法)不应该做太多的事。原子性:单一的方法做单一的事情)
- 接口隔离原则:要为各个类建立它们需要的专用接口
- 迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话(降低耦合度:A与B关联,B与C关联,则A尽量不与C直接联系)
- 合成复用原则:尽量先使用组合、聚合等关联关系实现,其次才考虑使用继承关系来实现
单例模式(single)
核心作用 | 保证一个类只有一个实例, 并提供一个访问该实例的全局访问点 |
---|---|
常见场景 | 1.windows的任务管理器 2.windows的回收站 3.项目中,读取配置文件的类,一般只有一个对象 4.网站的计数器一般也会采用单例模式 5.Servlet编程中,每个Servlet也是单例的 6.在Spring中,每个Bean默认就是单例的 |
饿汉模式
/**
* 饿汉式单例
* 缺点:类加载期间就创建对象,容易造成资源浪费
*/
public class SingleHungry {
//构造器私有化
public SingleHungry() {}
/*类加载时,创建对象:
即使静态变量,如果不加 final 修饰,在类加载第一步赋初始值的时候,也会给类型的默认值。比如:
public static int i = 666;//被类加载器加载到内存时会执行,赋予一个初始值(i为0)
public static Integer ii = new Integer(666);//也被赋值一个初始值(ii为null)
如果加上了 final 修饰词,那么初始值就是设定的值
public static final int max_age = 20; //max_age的初始值就是20
*/
private final static SingleHungry singleHungry= new SingleHungry();
//公有方法返回对象
public static SingleHungry getInstance(){
return singleHungry;
}
}
懒汉模式
/**
* 普通--懒汉式单例
* 单线程下能实现单例,多线程不行
*/
public class SingleLazy {
//构造器私有化
private SingleLazy() {}
private static SingleLazy singleLazy;
//判断对象是否为空,如果为空则创建对象然后返回
//单线程下,单例OK;多线程不行
public static SingleLazy getInstance(){
if(singleLazy == null){
singleLazy = new SingleLazy();
}
return singleLazy;
}
}
/**
* 懒汉式单例--DCL
*/
public class SingleLazy {
//构造器私有化
private SingleLazy() {
System.out.println(Thread.currentThread().getName()+" 执行了构造器");
}
//关键字 volatile 避免指令重排
private volatile static SingleLazy singleLazy;
//DCL 双重检测锁
public static SingleLazy getInstance(){
if(singleLazy == null){ //第一重检测
synchronized (SingleLazy.class){ //锁住当前对象
if(singleLazy == null){ //第一重检测
singleLazy = new SingleLazy(); //这不是一个原子性操作
/**
* 分为以下三步:
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象(singleLazy)的地址,指向这个空间
*
* 上面三步,可能指令重排。执行顺序可能是 1 2 3,也可能是 1 3 2
* 如果线程A 按照 1 3 2 执行,执行3 的时候线程 B 进来,则对象还未被初始化
* 需要用关键字 volatile 避免指令重排
*/
}
}
}
return singleLazy;
}
/**
* 静态内部类,实现单例
*/
public class SingleHolder {
private SingleHolder() {}
public static SingleHolder getInstance(){
return InnerClass.singleHolder;
}
//静态内部类:注意class关键字大小写
public static class InnerClass{
public static final SingleHolder singleHolder = new SingleHolder();
}
}
/**
* 通过反射破坏 DCL,和通过构造器加锁防止破坏
*/
public class SingleLazy {
//构造器私有化
private SingleLazy() {
/* ------2.预防------- 防止反射破坏 DCL 模式 -----------
在构造器中加锁,确保同一时间构造器只能执行一次
如果对象已经创建,则抛出异常
*/
synchronized (SingleLazy.class){
if(singleLazy != null){
throw new RuntimeException("不要用反射,破坏单例模式");
}
}
}
//关键字 volatile 避免指令重排
private volatile static SingleLazy singleLazy;
//DCL 双重检测锁
public static SingleLazy getInstance(){
if(singleLazy == null){ //第一重检测
synchronized (SingleLazy.class){ //锁住当前对象
if(singleLazy == null){ //第一重检测
singleLazy = new SingleLazy(); //这不是一个原子性操作
}
}
}
return singleLazy;
}
// ------1.破坏------- 通过反射,破解 DCL 单例 --------------
public static void main(String[] args) throws Exception {
SingleLazy singleLazy = SingleLazy.getInstance();
// 反射获取SingleLazy的Class对象
Class lazyClass = singleLazy.getClass();
// 获取构造方法(getDeclaredConstructor, 获取所有构造方法,包含私有)
Constructor<SingleLazy> constructor = lazyClass.getDeclaredConstructor(null);
// 设置构造地可见性,并创建对象
constructor.setAccessible(true);
SingleLazy singleLazy2 = constructor.newInstance();
System.out.println(singleLazy);
System.out.println(singleLazy2);
}
}
/**
* 1.不调用getInstance方法,全部使用反射创建对象,破坏DCL
* 2.使用红绿灯法,阻止这种破坏
* 3.通过反射找到这个红绿灯标志,依然可破坏 DCL
*/
public class SingleLazy {
private static boolean flag = false;
//构造器私有化
private SingleLazy() {
/* ------4.预防------- 防止反射破坏 DCL 模式 -----------
采用红绿灯法,避免全部使用反射破坏 DCL
*/
synchronized (SingleLazy.class){
if(!flag){
flag = true; //将flag作为红绿灯,也可以用UUID或者别的
}else{
throw new RuntimeException("不要用反射,破坏单例模式");
}
}
}
//关键字 volatile 避免指令重排
private volatile static SingleLazy singleLazy;
//DCL 双重检测锁
public static SingleLazy getInstance(){
if(singleLazy == null){ //第一重检测
synchronized (SingleLazy.class){ //锁住当前对象
if(singleLazy == null){ //第一重检测
singleLazy = new SingleLazy(); //这不是一个原子性操作
}
}
}
return singleLazy;
}
/* ------3.破坏------- 通过反射,破解 DCL 单例 --------------
如上面所示,构造方法中锁住当前对象。
但是如果不调用getInstance,全部通过反射创建对象,依然可以破坏单例
*/
public static void main(String[] args) throws Exception {
//不调用.getInstance();
//SingleLazy singleLazy = SingleLazy.getInstance();
//通过类路径获取Class对象(不能通过对象获取,那时候已经初始化)
Class lazyClass = SingleLazy.class;
Constructor<SingleLazy> constructor = lazyClass.getDeclaredConstructor(null);
constructor.setAccessible(true);
//全部用反射创建对象
SingleLazy singleLazy = constructor.newInstance();
// -------- 5. 如果找到隐藏的标志,也可用反射破坏 -------
Field flagField = lazyClass.getDeclaredField("flag");
flagField.set(singleLazy,false);
// -------- 5.成功创建第二个对象 ---------
SingleLazy singleLazy2 = constructor.newInstance();
System.out.println(singleLazy);
System.out.println(singleLazy2);
}
}
任何方法,包括DCL,理论上都能破坏单例。只有枚举能真正实现单例
下面是newInstance方法的源码:
/**
* 通过枚举实现单例:
* enum本身也是一个Class类,枚举默认就是单例
*/
public enum SingleEnum {
SINGLE_ENUM;
public SingleEnum getInstance(){
return SINGLE_ENUM;
}
public static void main(String[] args) {
SingleEnum singleEnum1 = SingleEnum.SINGLE_ENUM;
SingleEnum singleEnum2 = SingleEnum.SINGLE_ENUM;
System.out.println(singleEnum1.hashCode());
System.out.println(singleEnum2.hashCode());
}
}
通过javap -p反编译class文件,得到源码:
通过javap命令,发现里面有个私有无参构造方法,但并不是真的。
使用javap反编译,得到的信息不正确。经验证,enum没有无参构造:
枚举类型的私有构造方法,形参有两个:分别为 String int
工厂模式(factory)
作用 | 实现创建者与调用者的分离 |
---|---|
详细分类 | 简单工厂模式 、工厂方法模式 、抽象工厂模式 |
涉及原则 | 开闭原则 、 依赖倒转原则 、迪米特法则 |
核心本质
- 实例化对象不适用new,用工厂方法代替
- 将选择实现类、创建对象统一管理和控制。从而将调用者与实现类解耦
三种模式:
- 简单工厂模式:用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
- 工厂方法模式:用来生产同一等级结构中的固定产品(支持增加任意产品)
- 抽象工厂模式:围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
分类 | 内容 |
---|---|
简单工厂模式 (静态工厂模式) | 虽然某种程度上不符合设计原则,但实际使用最多 |
工厂方法模式 | 满足开闭原则,不修改已有类的前提下,通过增加新的工厂实现扩展 |
抽象工厂模式 | 不可以增加新产品,可以增加产品族 |
应用场景 | 1.JDK的Calendar中getInstance方法 2.JDBC中Connection对象的获取 3.Spring中IOC容器创建管理bean对象 4.反射中Class对象的newInstance方法 |
简单工厂模式
// ---------- 接口 ----------
public interface Car {
public void name();
}
// ---------- 两个实现类: ----------
public class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
}
public class Wuling implements Car {
@Override
public void name() {
System.out.println("五菱宏光");
}
}
/* ---------- 简单工厂模式,又称静态工厂模式 ----------
缺点:不满足开闭原则。如果新增一个车型(大众),则车工厂需要改代码
*/
public class CarFactory {
//简单工厂方法一:
public static Car getCar(String carName){
if(carName.equals("五菱")){
return new Wuling();
}else if(carName.equals("特斯拉")){
return new Tesla();
}else{
return null;
}
}
//简单工厂方法二:
public static Car getWuling(){
return new Wuling();
}
public static Car getTesla(){
return new Tesla();
}
}
// ---------- 消费者 ----------
public class Consumer {
public static void main(String[] args) {
Car wuling = CarFactory.getCar("五菱");
Car tesla = CarFactory.getCar("特斯拉");
wuling.name(); //输出:五菱宏光
tesla.name(); //输出:特斯拉
}
}
工厂方法模式
// ---------- 接口 ----------
public interface Car {
public void name();
}
// ---------- 两个实现类: ----------
public class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
}
public class Wuling implements Car {
@Override
public void name() {
System.out.println("五菱宏光");
}
}
/* ---------- 方法工厂模式:每个实现类,分别创建一个工厂 ----------
优点:满足开闭原则,新增一个车型,不用修改原代码
缺点:代码量明显增加
*/
public interface CarFactory {
public Car getCar();
}
//特斯拉工厂,只生产特斯拉
public class TeslaFactory implements CarFactory {
@Override
public Car getCar() {
return new Tesla();
}
}
//五菱工厂,只生产五菱
public class WulingFactory implements CarFactory {
@Override
public Car getCar() {
return new Wuling();
}
}
// ---------- 消费者:通过特定的工厂,生产特定的车 ----------
public class Consumer {
public static void main(String[] args) {
Car wuling = new WulingFactory().getCar();
Car tesla = new TeslaFactory().getCar();
wuling.name(); //输出:五菱宏光
tesla.name(); //输出:特斯拉
}
}
对比项 | 结果 |
---|---|
结构复杂度 | 简单工厂模式,更简单 |
代码复杂度 | 简单工厂模式,更简洁 |
编程复杂度 | 简单工厂模式,更容易 |
管理复杂度 | 简单工厂模式,更易管理 |
开闭原则 | 简单工厂模式,不满足 方法工厂模式,满足 |
根据设计原则,应采用工厂方法模式
根据实际业务,简单工厂模式,应用更广泛
抽象工厂模式
定义 | 抽象工厂模式,提供了一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类 |
---|---|
应用场景 | 1.客户端不依赖于实例如何被创建、实现等细节 2.强调一系列相关的产品对象(属于同一产品族)一起使用,创建对象需要大量的重复代码 3.提供一个产品类的库,所有的产品以同样的接口出现,使客户端不依赖于具体实现 |
优点 | 1.具体产品在应用层的代码隔离,无需关心创建细节 2.将一个系列的产品,统一到一起创建 |
缺点 | 1.规定了所有可能被创建的产品集合,产品族中扩展新的产品困难 2.增加了系统的抽象性和理解难度 |
/**
* 接口中修饰符:
* Java 7 方法只能为抽象方法,默认为 public abstract
* Java 8 增加:默认方法default;静态方法static(默认方法不强制重写,静态方法不会被实现类继承)
* Java 9 增加:私有方法
*
* 接口中不可以定义变量即只能定义常量,属性默认是public static final,且必须赋值
*/
// 1.------------ 超级工厂,定义产品族 ------------
public interface IProductFactory {
//生产手机
IPhoneProduct phoneProduct();
//生产路由器
IRouterProduct routerProduct();
}
// 2.------------ 产品接口,定义产品功能 ------------
//手机产品接口
public interface IPhoneProduct {
void open(); //开机
void callup(); //打电话
}
//路由器产品接口
public interface IRouterProduct {
void open(); //开机
void openWifi(); //打开WiFi
}
// 3.------------ 具体产品,实现产品接口 ------------
//小米手机
public class XiaomiPhone implements IPhoneProduct {
@Override
public void open() {
System.out.println("小米手机:开机");
}
@Override
public void callup() {
System.out.println("小米手机:打电话");
}
}
//小米路由器
public class XiaomiRouter implements IRouterProduct {
@Override
public void open() {
System.out.println("小米路由器:开机");
}
@Override
public void openWifi() {
System.out.println("小米路由器:打开WiFi");
}
}
//华为手机
public class HuaweiPhone implements IPhoneProduct {
@Override
public void open() {
System.out.println("华为手机:开机");
}
@Override
public void callup() {
System.out.println("华为手机:打电话");
}
}
//华为路由器
public class HuaweiRouter implements IRouterProduct {
@Override
public void open() {
System.out.println("华为路由器:开机");
}
@Override
public void openWifi() {
System.out.println("华为路由器:打开WiFi");
}
}
// 4.------------ 超级工厂实现类,生产具体产品 ------------
//小米工厂,生产小米手机、路由器
public class XiaomiFactory implements IProductFactory{
@Override
public IPhoneProduct phoneProduct() {
return new XiaomiPhone();
}
@Override
public IRouterProduct routerProduct() {
return new XiaomiRouter();
}
}
//华为工厂,生产华为手机、路由器
public class HuaweiFactory implements IProductFactory{
@Override
public IPhoneProduct phoneProduct() {
return new HuaweiPhone();
}
@Override
public IRouterProduct routerProduct() {
return new HuaweiRouter();
}
}
// 5.------------ 消费者,实现具体消费 ------------
public class Consumer {
public static void main(String[] args) {
System.out.println("------------小米产品---------------");
XiaomiFactory xiaomiFactory = new XiaomiFactory(); //小米工厂
IPhoneProduct xiaomiPhone = xiaomiFactory.phoneProduct(); //生产小米手机
IRouterProduct xiaomiRouter = xiaomiFactory.routerProduct();//生产小米路由器
System.out.println("------------华为产品---------------");
HuaweiFactory huaweiFactory = new HuaweiFactory(); //华为工厂
IPhoneProduct huaweiPhone = huaweiFactory.phoneProduct(); //华为手机
IRouterProduct huaweiRouter = huaweiFactory.routerProduct();//华为路由器
}
}
总结:
1. 超级工厂接口,只起到一个限制作用,限制产品族
2. 具体工厂,实现超级工厂接口,生产不同规格的产品
建造者模式(builder)
建造者模式 | 也属于创建者模式,它提供了一种创建对象的最佳方式 |
---|---|
定义 | 讲一个复杂对象的构建与表示分离,使得同样的构建过程,可以创建不同的表示 |
主要作用 | 用户不需要知道,对象的的建造过程和细节,就能创建复杂的对象 |
例子 | 1.工厂(建造者模式):负责造汽车(组装过程和细节在工厂内) 2.购买者(用户):只需要说出型号(对象的类型和内容),就能直接购买(不用知道汽车怎么组装的) |
用户只需要给出复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(隐藏内部的过程和细节)
//1.-----------抽象的建造者:定义产品的建造方----------法
public abstract class Builder {
abstract void buildA(); //步骤一
abstract void buildB(); //步骤二
//建造完成,得到产品:
abstract Product getProduct();
}
//2.------- 定义产品,产品与建造者无必然联系 ---------
public class Product {
private String buildA; //步骤一搞定
private String buildB; //步骤二搞定
//get set 方法
public String getBuildA() { return buildA; }
public void setBuildA(String buildA) { this.buildA = buildA; }
public String getBuildB() { return buildB; }
public void setBuildB(String buildB) { this.buildB = buildB; }
//toString方法
@Override
public String toString() {
return "Product{" + "buildA='" + buildA + '\'' + ", buildB='" + buildB + '\'' + '}';
}
}
/*
3.----------工人继承Builder类,实现方法-------
不同的工人,可以按照构建者模式,生产不同规格的产品
*/
public class Worker extends Builder {
private Product product; //产品
/*--- 这一步很重要,工人负责创建产品,而不是外部传进来 --*/
public Worker() {
this.product = new Product();
}
//执行第一步
@Override
void buildA() {
product.setBuildA("步骤A");
System.out.println("步骤A");
}
//执行第二步
@Override
void buildB() {
product.setBuildB("步骤B");
System.out.println("步骤B");
}
//返回完整产品
@Override
Product getProduct() {
return product;
}
}
//4.----------指挥者,指挥工人生产产品
public class Director {
//****指挥工人,按照顺序构建产品****
public Product build(Builder builder){
//*****可以控制顺序*******
builder.buildB();
builder.buildA();
return builder.getProduct();
}
}
//5.--------测试:按照指挥者指定的顺序,得到产品--------
public static void main(String[] args) {
Director director = new Director();
Product product = director.build(new Worker());
System.out.println(product);
}
- 上面是Builder模式的常规用法,指挥类Director在构造者模式中作用很大:指挥具体构建者(Worker)如何构建产品,控制先后次序,并返回完整的产品类(Product)
- 通过静态内部类方式,实现零件无序装配构造,这种方式更灵活、更符合定义。内部有复杂对象的默认实现,使用时也可自定义更改内容,并且无需改变构造方式。
- 比如:饭店的套餐,服务员(具体构造者),可以默认几种菜品(零件)组成套餐(产品),客户(使用者)也可要求更换其中的菜品(零件)
- 比第一种少了指挥者,客户就是指挥者
//1.------------ 产品,属性有默认值 ------------
public class Product {
private String foodA = "小龙虾";
private String foodB = "大白菜";
//get set 方法
public String getFoodA() { return foodA; }
public void setFoodA(String foodA) { this.foodA = foodA; }
public String getFoodB() { return foodB; }
public void setFoodB(String foodB) { this.foodB = foodB; }
@Override
public String toString() {
return "Product{" + "foodA='" + foodA + '\'' + ", foodB='" + foodB + '\'' + '}';
}
}
//2.---------- 抽象构造者,定义生产产品有哪些步骤----------
public abstract class Builder {
//构造方法
abstract Builder buildA(String msg);
abstract Builder buildB(String msg);
//返回产品
abstract Product getProduct();
}
//3.---------- 具体构造者(服务员)----------
public class Worker extends Builder {
private Product product;
//产品在构造器中创建,而不是外部传进来
public Worker() {
product = new Product();
}
@Override
Builder buildA(String msg) {
product.setFoodA(msg);
return this; //返回当前对象
}
@Override
Builder buildB(String msg) {
product.setFoodA(msg);
return this; //返回当前对象
}
@Override
Product getProduct() {
return product;
}
}
//4.---------- 测试:----------
public static void main(String[] args) {
//服务员
Builder worker = new Worker();
//输出默认产品
System.out.println(worker.getProduct());
//链式编程(执行方法之后,依然返回当前对象:worker)
Product product = worker.buildA("大豆角").buildB("冰淇淋").getProduct();
System.out.println(product);
}
输出结果:
Product{foodA='小龙虾', foodB='大白菜'}
Product{foodA='大豆角', foodB='冰淇淋'}
优点
- 产品的建造和表示分离,实现了解耦。客户端不用知道具体细节
- 将负责的产品创建步骤、分节在不同的方法中,使创建过程更清晰
- 符合“开闭原则”,不同的建造者之间相互独立,有利于系统的扩展
缺点
- 适用范围受限:创建的产品具有较多共同点,组成部分相似;如果产品之间差异性很大,则不适用建造者模式。
- 如果产品的内部变化复杂,可能导致需要定义很多具体建造者(实现类)来实现这种变化,导致系统很庞大
应用场景
- 产品对象 内部结构复杂、具备共性
- 隔离复杂对象的创建和使用,使相同的创建过程可以创建不同的产品
- 适合创建有较多属性的对象
与抽象工厂比较
建造者模式 | 抽象工厂 |
---|---|
返回一个组装好的完整产品 | 返回一系列相关产品,具有不同的产品等级结构,组成产品族 |
不直接调用建造者的方法,通过指挥者指导如何生成对象, 包括对象的组装过程和建造步骤。侧重于分步骤构造一个复杂对象 | 客户端实例化工厂类,调用工厂方法,获取所需产品对象 |
类似于汽车组装工厂,通过对部件的组装,返回一辆整车 | 类似于汽车配件生产工厂,生产一个产品的产品族 |
原型模式(prototype)
原型模式,是指将一个对象,完整的克隆出一份
实现Cloneable接口,调用clone()方法
应用场景:
- spring bean:单例模式+原型模式
- 工厂模式: new 对象 <–替换成–> 原型模式
/**
* 浅克隆:将对象的值完全拷贝一份,
* 如果属性为引用类型变量,则拷贝属性指向的内存地址
* 实现Cloneable接口
* 重写 clone 方法(不做改造)
*/
public class Student implements Cloneable{
public int id;
public Integer age;
public String name;
public Date createTime;
public Student(int id, Integer age, String name,Date createTime) {
this.id = id;
this.age = age;
this.name = name;
this.createTime = createTime;
}
@Override
public String toString() {
return "Student{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + ", createTime=" + createTime + '}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws Exception {
Student stu_1 = new Student(1,18,"小红",new Date());
Student stu_2 = (Student) stu_1.clone();
System.out.println(stu_1);
System.out.println(stu_2);
System.out.println("-------修改stu_2的值------");
stu_2.id = 2; //不影响原对象
stu_2.age = 20; //不影响原对象
stu_2.name = "小兰";//不影响原对象
// ------- 影响原对象 -------
stu_2.createTime.setTime(22123156);
System.out.println(stu_1);
System.out.println(stu_2);
}
}
输出结果为:
Student{id=1, age=18, name='小红', createTime=Sun Apr 24 12:39:28 CST 2022}
Student{id=1, age=18, name='小红', createTime=Sun Apr 24 12:39:28 CST 2022}
-------修改stu_2的值------
Student{id=1, age=18, name='小红', createTime=Thu Jan 01 14:08:43 CST 1970}
Student{id=2, age=20, name='小兰', createTime=Thu Jan 01 14:08:43 CST 1970}
/**
* 实现深克隆的方式:
* 1.序列化与反序列化
* 2.改造clone方法
*/
@Override
protected Object clone() throws CloneNotSupportedException {
Student stu = (Student) super.clone();
//将引用类型的变量,也克隆一份
stu.createTime = (Date) this.createTime.clone();
return stu;
}
输出结果为:
Student{id=1, age=18, name='小红', createTime=Sun Apr 24 12:39:28 CST 2022}
Student{id=1, age=18, name='小红', createTime=Sun Apr 24 12:39:28 CST 2022}
-------修改stu_2的值------
Student{id=1, age=18, name='小红', createTime=Sun Apr 24 12:39:28 CST 2022}
Student{id=2, age=20, name='小兰', createTime=Thu Jan 01 14:08:43 CST 1970}
适配器模式(adapter)
结构型模式的作用:
从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题
适配器模式(也称包装样式or包装):将一个类的接口适配成用户所期待的。一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。
//1. -------- 被适配的类:网线 --------
public class NetCable {
public void request(){
System.out.println("连上网线,可以上网");
}
}
//2. -------- 客户端类:想上网,但接口插不上网线 --------
public class Computer {
//上网的具体实现,插不上传统网线,需要一个转接头
public void net(NetToUsb adapter){
//上网的具体实现
adapter.handlerRequest();
}
}
//3. -------- 接口转换器的抽象实现 --------
public interface NetToUsb {
//作用:处理请求(将 网线插口 ---》 USB插口)
public void handlerRequest();
}
/**
* 4. -------- 真正的适配器:需要连接USB 和 网线 --------
* 继承网线类,实现网线转USB接口
*/
public class Adapter extends NetCable implements NetToUsb {
@Override
public void handlerRequest() {
super.request(); //调用网线的上网方法
}
}
//5. -------- 测试:电脑通过适配器,调用网线的上网方法 --------
public static void main(String[] args) {
//创建电脑、适配器;适配器实现了转接口的功能,可以调用
Computer computer = new Computer();
Adapter adapter = new Adapter();
computer.net(adapter);
}
适配器有两种方式:
1.继承:类适配器(java是单继承) 上面代码就是 类适配器
2.组合:对象适配器(常用)
// ------ 对象适配器:通过组合的方式,将网线的功能集成进来 ------
public class Adapter2 implements NetToUsb {
private NetCable netCable; //通过组合,集成网线类
//构造方法中,传入网线对象
public Adapter2(NetCable netCable) {
this.netCable = netCable;
}
@Override
public void handlerRequest() {
netCable.request();
}
}
public static void main(String[] args) {
//1.创建电脑
Computer computer = new Computer();
//2.创建网线对象
NetCable netCable = new NetCable();
//3.将网线对象,放入适配器中
Adapter2 adapter2 = new Adapter2(netCable);
//4.电脑调用适配器接口,可以上网
computer.net(adapter2);
}
适配器模式:将一个类的接口,转换成客户希望的另一个接口。Adapter模式使得原来由于接口不兼容而不能一起工作的类,可以在一起工作。
角色分析:
- 目标接口:客户期待的接口。目标接口可以是具体或者抽象类,也可以是接口(电脑需要的USB接口)
- 需要适配的类:被适配的类或者适配者类(网线)
- 适配器:通过包装一个需要适配的对象,把原接口转换成目标对象(SUB、网线 接口转换器)
分类 | 描述 |
---|---|
对象适配器优点 | 1.一个对象适配器,可以将多个不同的适配者,适配到同一个目标 2.满足里氏替换原则:由于适配器和适配者是关联关系,可以适配一个适配者或者它的子类 |
类适配器缺点 | 1.对于java、C#这种不支持多继承的语言,一个适配器只能适配一个适配者 2.在java、C#等语言中,类适配器模式中的目标抽象类,只能为接口不能为类,有局限性 |
适用场景 | 1.系统要使用一些现有的类,但这些类的接口(如方法名)不符合系统需要,甚至没有这些类的源代码 2.想创建一个可以重复使用的类,用于与一些彼此之间没有关联、甚至将来可能引进的类,一起工作 |
桥接模式(bridge)
上图为多重继承模式,类的个数比较多,扩展比较麻烦;
下图为抽象出两个纬度,减少类的构建:
//1.品牌纬度 --------------- 接口:电脑品牌 ---------------
public interface Brand {
void brandName();
}
//联想品牌
public class Lenovo implements Brand {
@Override
public void brandName() {
System.out.print("联想品牌");
}
}
//苹果品牌
public class Apple implements Brand {
@Override
public void brandName() {
System.out.print("苹果品牌");
}
}
//2.种类纬度 --------------- 抽象类:电脑种类 ---------------
public abstract class Computer {
//通过组合的方式,给电脑增加上品牌属性
protected Brand brand;
//构造器中,引入品牌(出厂自带品牌)
public Computer(Brand brand) {
this.brand = brand;
}
//输出电脑信息:品牌 + 种类
abstract void computerInfo();
}
//笔记本
public class NoteBook extends Computer {
public NoteBook(Brand brand) { super(brand); }
@Override
void computerInfo() {
brand.brandName();
System.out.println("笔记本");
}
}
//平板
public class Flat extends Computer {
public Flat(Brand brand) { super(brand); }
@Override
void computerInfo() {
brand.brandName();
System.out.println("平板");
}
}
//3. ----------- 测试 ------------
public static void main(String[] args) {
//苹果笔记本
Computer computer = new NoteBook(new Apple());
computer.computerInfo();
//联想平板
Computer computer2 = new Flat(new Lenovo());
computer2.computerInfo();
}
输出结果:
苹果品牌笔记本
联想品牌平板
优点:
- 桥接模式偶尔类似于多重继承方案。但多重继承违背了单一职责原则,类的个数非常多。桥接模式抽象出不同纬度,可以减少子类的个数,降低管理和维护成本
- 桥接模式符合开闭原则,提高系统的可扩充性。如上面例子,两个纬度任意变化,都不需要修改原系统。就像一座桥,(通过组合)把两个纬度连接起来
缺点
- 桥接模式增加系统的理解与设计难度。聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程
- 桥接模式要求正确识别出系统中两个独立变化的纬度,使用范围有局限性
最佳实践
- 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
- 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
场景
- Java语言通过Java虚拟机实现了平台的无关性。
- AWT中的Peer架构
- JDBC驱动程序也是桥接模式的应用之一
代理模式
代理模式,是SpringAOP的低层实现
静态代理模式
角色分析:
- 抽象角色: 一般会使用接口或抽象类来解决(租房接口)
- 真实角色: 被代理的角色(房东)
- 代理角色: 代理真实角色,代理真实角色后,我们一般会做一些附属操作(中介)
- 客户: 访问代理对象的人(租户)
// ------- 租房接口。代理对象与被代理对象,都实现这个接口 -------
public interface Rent {
void rent();
}
// ------- 房东,实现租房接口 -------
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
/**
* 代理对象
* 使用组合将被代理的对象,集合进来(尽量不适用继承)
*/
public class Proxy implements Rent {
Host host; //房东:被代理的对象
public Proxy(Host host) { //构造方法中,传入房东
this.host = host;
}
//中介代理房东出租房子,执行一些额外的操作
@Override
public void rent() {
watchHouse(); //额外操作1.租房子
host.rent();
charge(); //额外操作2.收中介费
}
private void watchHouse(){
System.out.println("中介带你看房");
}
private void charge(){
System.out.println("中介带你看房");
}
}
// ------------ 测试 -----------------
public static void main(String[] args) {
Host host = new Host(); //房东要出租房子
Proxy proxy = new Proxy(host); //中介代理房东,与租户打交道
proxy.rent(); //租户找中介,租房子
}
输出结果:
中介带你看房
房东出租房子
中介带你看房
代理模式的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共业务交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
缺点
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低
静态代理加深理解:
//1. ---------- 业务层的接口,增删改查方法 ----------
public interface IUserService {
public void add();
public void delete();
public void modify();
public void search();
}
//2. ------- 被代理的类:业务层具体实现 --------
public class UserService implements IUserService {
@Override
public void add() { System.out.println("新增方法"); }
@Override
public void delete() { System.out.println("删除方法"); }
@Override
public void modify() { System.out.println("修改方法"); }
@Override
public void search() { System.out.println("查询方法"); }
}
//3.-----代理类(与被代理类,实现同一个接口),实现日志功能
public class UserServiceProxy implements IUserService{
//被代理的类私有化,使用set方法注入
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
/**
* 在执行原业务逻辑之前,给每个方法添加上日志
*/
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void modify() {
log("modify");
userService.modify();
}
@Override
public void search() {
log("search");
userService.search();
}
private void log(String msg){
System.out.println("log日志:调用了"+msg+"方法");
}
}
//4. ------------ 测试 -----------
public static void main(String[] args) {
//具体业务的实现类
UserService userService = new UserService();
//将业务实现类,注入到代理类
UserServiceProxy userServiceProxy = new UserServiceProxy();
userServiceProxy.setUserService(userService);
/*
通过代理类,调用业务处理方法:
在不修改业务处理类的情况下,添加日志
*/
userServiceProxy.add();
}
动态代理模式
- 动态代理与静态代理,角色组成一样(抽象角色、真实角色、代理角色)
- 动态代理的代理类是动态生成的,不是我们写好的
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口: JDK动态代理
- 基于类: cglib
- java字节码实现:javasist
动态代理两个重要的类:
- Proxy:代理(生成动态代理实例)
- InvocationHandler:调用处理程序(调用处理程序,并返回结果)
jdk18官方文档地址:JDK18API
InvocationHandler
java.lang.reflect
Interface InvocationHandler:是由代理实例的 调用处理程序 实现的接口。每个代理实例,都有一个关联的调用处理程序。在代理实例上调用方法时,方法调用将被 编码并分派 到其调用处理程序的invoke方法
只有一个方法: Object invoke(Object proxy, Method method , Object[] args )
作用:处理代理实例上的方法调用,并返回结果
Proxy
java.lang.reflect
Class Proxy:提供了静态方法,可以创建动态代理类和实例。它也是由这些方法创建的 所有动态代理类的 超类。
Proxy类的方法,只有以下四个:
动态代理用到的方法:
//1.------------- 租房接口。代理对象与被代理对象,都实现这个接口 ------------
public interface Rent {
void rent();
}
//2.------------- 房东,实现租房接口 -------------
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
/**
* MyInvocationHandler:代理实例的调用处理程序,继承了InvocationHandler接口
* 用这个类,自动生成代理类(上面的UserServiceProxy 也是代理类)
*/
public class MyInvocationHandler implements InvocationHandler {
//1.被代理的接口:
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//2.生成代理类
public Object getProxy(){
/**
* ClassLoader loader //定义 代理类 的类加载器
* Class<?>[] interfaces //代理类 实现的接口列表
* InvocationHandler h //将方法调用分派到的 调用处理程序
*/
return Proxy.newProxyInstance(rent.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
//3.处理代理程序,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//动态代理的本质,就是利用反射机制实现
Object result = method.invoke(rent, args);
charge();
return result;
}
private void seeHouse(){
System.out.println("中介带你看房子");
}
private void charge(){
System.out.println("收取中介费");
}
}
// ---------------------- 测试 ---------------------------
public static void main(String[] args) {
//1.真实角色
Host host = new Host(); //房东
//2.调用处理程序
MyInvocationHandler mih = new MyInvocationHandler();
//3.通过调用处理程序,处理我们要调用的【接口实现类对象】
mih.setRent(host);
//4.动态得到代理类,并强转为 Rent 接口类型:
// 这里的rentProxy就是动态生成的,并没有定义这个类(不像之前的 UserServiceProxy )
Rent rentProxy = (Rent) mih.getProxy();
//5.1 调用代理实例的 rent() 方法,
//5.2 被编码并转发到 调用处理程序(mih)的invoke方法上
rentProxy.rent();
}
输出结果:
中介带你看房子
房东出租房子
收取中介费
下面是动态代理的工具类,将被代理的接口,换成Object
/**
* 将被代理的接口(上面的Rent),换成Object。可以代理所有的Object子类
* 用这个类,自动生成代理类(上面的UserServiceProxy 也是代理类)
*/
public class DynamicInvocationHandler implements InvocationHandler {
//1.被代理的接口:
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//2.生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
//3.处理代理程序,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质,就是利用反射机制实现
printLog(method.getName());
Object result = method.invoke(target, args);
return result;
}
//日志方法
private void printLog(String msg){
System.out.println("执行了 "+msg+" 方法");
}
}
// ---------------------- 测试 ---------------------------
public static void main(String[] args) {
//万能代理类:
//1.真实对象
UserService userService = new UserService();
//2.调用处理程序
DynamicInvocationHandler dih = new DynamicInvocationHandler();
//3.通过调用处理程序,处理我们要调用的【接口实现类对象】
dih.setTarget(userService);
//4.动态得到代理类,并强转类型
// 只能转换成接口,不能强转成 实现类
IUserService proxy = (IUserService) dih.getProxy();
//5.通过代理类,执行方法
proxy.add();
proxy.delete();
}
输出结果:
执行了 add 方法
新增方法
执行了 delete 方法
删除方法
动态代理的好处:
- 具有静态代理的所有好处
- 一个动态代理类,代理的是一个接口。一般就是对应一类业务
- 一个动态代理类,可以代理多个类,只要实现了同一个接口