开始时间:2022-09-21
课程链接:【狂神说Java】通俗易懂的23种设计模式教学
设计模式的概念
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
创建型:单例、工厂方法、抽象工厂、建造者、原型
结构型:适配器、桥接、装饰、组合、外观、享元、代理
行为型:模板方法、命令、迭代器、观察者、中介者、备忘录、解释器、状态、策略、职责链、访问者
OOP七大原则
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立(扩展父类功能,不改变父类原有功能)
- 依赖倒置原则:要面向接口编程,不要面向实现编程。(细节依赖抽象)
- 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性。
- 接口隔离原则:要为各个类建立它们需要的专用接口
- 迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话。 第三方转发来通信而不直接通信,降低耦合性,有点像权限框架那块设计
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
高内聚低耦合
工厂模式
简单工厂模式:用来生产同一等级结构中的任意产品(对于增加新的产品,需要已有代码进行更新),一个工厂管所有生产
工厂方法模式:用来生产同一等级结构中的固定产品(支持增加任意产品)
抽象工厂模式:围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
实现了创建者和调用者的分离
核心本质:
实例化对象不使用new,用工厂方法代替
看个例子
car接口
public interface Car {
public void name();
}
特斯拉类
public class Tesla implements Car{
@Override
public void name() {
System.out.println("我是特斯拉!!!");
}
}
Car工厂
public class CarFactory {
public static Car getCar(String car) {
if ("特斯拉".equals(car)) {
return new Tesla();
} else {
return null;
}
}
}
消费者
public class Consumer {
public static void main(String[] args) {
Car mycar = new Tesla();
mycar.name();
Car car = CarFactory.getCar("特斯拉");
car.name();
System.out.println();
}
}
我们普通的方式就是自己new,也就是调用者和创建者是一体的
用了工厂后,拿对象就是拿的人管那,创建的交给工厂
这种属于静态工厂,简单工厂模式
如果有了新产品,那就得重新改代码
不满足开闭原则
工厂方法模式
每要增加一辆车,就直接造一个生产车的工厂,这样原有的工厂代码我们就不用动了
抽象工厂模式
生成工厂的工厂
- 定义︰抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类
- 适用场景:
客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现 - 优点:
具体产品在应用层的代码隔离,无需关心创建的细节将一个系列的产品统一到一起创建 - 缺点:
规定了所有可能被创建的产品集合,产品簇中扩展新的产品困难;增加了系统的抽象性和理解难度
不要纠结狂神画的这个坐标
总之就是区分开一个公司下的不同产品
和不同公司下的同类产品
定义路由器和手机两类产品
华为和小米都有这两个产品,对产品进行了具体的实现
这点还好理解,那我们继续进一步抽象!
定义了一个抽象工厂,里面有生成路由器和生产手机两个方法
然后小米和华为分别建一个实现这个接口的类
看看使用
建造者模式
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
主要作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。
建造者模式和工厂模式的区别
参考CSDN博主「茄子_土豆」的博客
客户参与度和关心点:
建造者让我们调用者参与程度很高,每个参数的值都是我们参与调用的。所以我们很关心每个数值
工厂模式,参数值相对比较固定(举个例子,一个数据源对象他有很多配置,最大连接数,最小连接数,空闲连接数…等等,可是你发现很多参数基本调好后都是固定的),所以我们对每个数值并不太关心,更关心的是这个对象整体,工厂你实例化一个对象,屏蔽复杂的细节,给我对象就完了
我们来看看代码
产品
package com.bupt.designPattern.factoryPattern.builderFactory;
import lombok.Data;
//产品 套餐
@Data
public class Product {
private String buildA = "汉堡";
private String buildB = "可乐";
private String buildC = "薯条";
@Override
public String toString() {
return "Product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
'}';
}
}
worker
package com.bupt.designPattern.factoryPattern.builderFactory;
//具体的建造者:工人
public class Worker extends Builder {
private Product product;
// public Worker(Product product) {
// this.product = product;
// }
//注意区别
public Worker() {
product = new Product();
}
@Override
Builder buildA(String msg) {
product.setBuildA(msg);
return this;
}
@Override
Builder buildB(String msg) {
product.setBuildB(msg);
return this;
}
@Override
Builder buildC(String msg) {
product.setBuildB(msg);
return this;
}
@Override
Product getProduct() {
return product;
}
}
抽象类Builder
package com.bupt.designPattern.factoryPattern.builderFactory;
//抽象建造者
public abstract class Builder {
abstract Builder buildA(String msg);//汉堡
abstract Builder buildB(String msg);//饮料
abstract Builder buildC(String msg);//薯条
abstract Product getProduct();
}
链式调用,创建对象
package com.bupt.designPattern.factoryPattern.builderFactory;
public class Test {
public static void main(String[] args) {
Worker worker = new Worker();
//链式编程,更加自由的创建对象
Product product = worker.buildA("全家桶").getProduct();
System.out.println(product.toString());
}
}
原型模式
拷贝+修改
像极了我写代码的样子
搞一个副本自己玩
这里就联系上了之前的深拷贝浅拷贝,虽然我背得住概念,但是没有手动拷贝过
public class ProtoTypeDemo1 {
public static void main(String[] args) throws CloneNotSupportedException {
//原型对象
Date date=new Date();
Video v1 = new Video("JDH", date);
System.out.println("v1=>" + v1);
System.out.println("v1.hashCode()=>" + v1.hashCode());
//根据v1创建v2
Video v2 = (Video) v1.clone();
System.out.println("v2=>" + v2);
System.out.println("v2.hashCode()=>" + v2.hashCode());
}
}
import java.util.Date;
@Data
public class Video implements Cloneable{
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Video() {
}
public Video(String name, Date createTime) {
this.name = name;
this.createTime = createTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", createTime=" + createTime +
'}';
}
}
这个地方有点奇怪,按理说浅拷贝应该是一个新的对象,只是如果对象里面的属性有引用的话,那么拷贝出来两个对象里的这个引用依然是同一个。
但是我这边看hashcode就一样了,很奇怪,我的代码和老师的的应该差不多
v1=>Video{name='JDH', createTime=Thu Sep 22 20:41:42 CST 2022}
v1.hashCode()=>1702487108
v2=>Video{name='JDH', createTime=Thu Sep 22 20:41:42 CST 2022}
v2.hashCode()=>1702487108
破案了,我这里是用@Data来代替手写get set方法
我手写get set方法后,他的hashcode就不同了
v1=>Video{name='JDH', createTime=Thu Sep 22 20:42:18 CST 2022}
v1.hashCode()=>325040804
v2=>Video{name='JDH', createTime=Thu Sep 22 20:42:18 CST 2022}
v2.hashCode()=>1173230247
要实现深拷贝,就要把每一个引用的地方也手动拷贝
重新改造刚刚的代码,就ok了
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
Video v = (Video) obj;
v.createTime = (Date) this.createTime.clone();
return obj;
}
单例模式
饿汉式
package com.bupt.designPattern.singlePattern;
//饿汉式,一上来就给对象了
public class Hungry {
//这样给对象时会加载这些属性,浪费空间
private byte[] data1=new byte[1024*1024];
private byte[] data2=new byte[1024*1024];
private byte[] data3=new byte[1024*1024];
private Hungry() {
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance() {
return hungry;
}
}
懒汉式
package com.bupt.designPattern.singlePattern;
//双重校验锁懒汉式
//先判断为空,然后使用synchronize防止多线程出错
//再判断是否为空,防止重复new对象
//使用volatile是为了保证有序性,防止指令重排
public class LazyMan {
private LazyMan() {
}
private static volatile LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
结构型模式
从程序的结构上实现松耦剑,从而可以扩大整体的类结构,用来解决更大的问题。
- 适配器模式
- 代理模式
- 桥接模式
- 装饰模式
- 组合模式
- 外观模式
- 享元模式
适配器模式
看个例子,上网网线转接
电脑
public class Computer {
public void net(NetToUsb adapter) {
//转接头
adapter.handleRequest();
}
public static void main(String[] args) {
Computer computer = new Computer();
Adaptee adaptee = new Adaptee();
Adapter adapter = new Adapter();
computer.net(adapter);
}
}
//接口转换器的抽象实现
public interface NetToUsb {
//作用:处理请求,网线->usb
public void handleRequest();
}
网线
package com.bupt.designPattern.adapter;
//类适配器
public class Adapter extends Adaptee implements NetToUsb{
@Override
public void handleRequest() {
super.request();
}
}
转接器
package com.bupt.designPattern.adapter;
public class Adaptee {
public void request() {
System.out.println("连接网线上网");
}
}
通过连接上适配器实现上网
上面的形式是单继承
下面改为组合式
public class Adapter2 implements NetToUsb {
private Adaptee adaptee;
public Adapter2(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void handleRequest() {
adaptee.request();
}
}
public static void main(String[] args) {
Computer computer = new Computer();
Adaptee adaptee = new Adaptee();
Adapter adapter = new Adapter();
computer.net(adapter);
//网线接转接器上
Adapter2 adapter2 = new Adapter2(adaptee);
//转接器再插入到电脑上
computer.net(adapter2);
}
Spring MVC里面dispatchHandler是有这块思想的
桥接模式
不使用桥接模式
使用桥接模式
我们来看个例子
如果我们要组合苹果笔记本,联想台式机,该怎么用桥接模式呢
package com.bupt.designPattern.bridge;
//抽象电脑类
public abstract class Computer {
//组合 品牌 桥
protected Brand brand;
public Computer(Brand brand) {
this.brand = brand;
}
public void info() {
//自带品牌
brand.info();
}
}
笔记本和台式机
class Desktop extends Computer {
public Desktop(Brand brand) {
super(brand);
}
@Override
public void info() {
super.info();
System.out.println("台式机");
}
}
class Laptop extends Computer {
public Laptop(Brand brand) {
super(brand);
}
@Override
public void info() {
super.info();
System.out.println("笔记本");
}
}
品牌接口。联想品牌和苹果品牌
package com.bupt.designPattern.bridge;
public interface Brand {
void info();
}
package com.bupt.designPattern.bridge;
//联想品牌
public class Lenovo implements Brand {
@Override
public void info() {
System.out.print("联想");
}
}
package com.bupt.designPattern.bridge;
//苹果品牌
public class Apple implements Brand {
@Override
public void info() {
System.out.print("苹果");
}
}
来看测试
public class Test {
public static void main(String[] args) {
Computer computer = new Laptop(new Apple());
computer.info();
Computer computer1 = new Desktop(new Lenovo());
computer1.info();
}
}
苹果笔记本
联想台式机
一个类存在两个独立变化的维度,且这两个维度都需要进行扩展、
Java语言通过Java虚拟机实现了平台的无关性。
AWT中的Peer架构
JDBC驱动程序也是桥接模式的应用之一。
代理模式
代理模式的好处:
·可以使真实角色的操作更加纯粹! 不用去关注一些公共的业务· 公共也就就交给代理角色!实现了业务的分工!
·公共业务发生扩展的时候,方便集中管理!
抽象角色:一般会使用接口或者抽象类来解决·
真实角色︰被代理的角色
代理角色∶代理真实角色,代理真实角色后,我们一般会做一些附属操作·
客户︰访问代理对象的人!
请中介来完成代理
和适配器不同的点在于,适配器没有中间转接头自己没办法完成这个动作
而租房的人没有中介也可以租房。
不使用代理
租房行为
package com.bupt.designPattern.proxyDesign;
//租房
public interface Rent {
public void rent();
}
房东实现租房
package com.bupt.designPattern.proxyDesign;
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
租客直接面对房东
package com.bupt.designPattern.proxyDesign;
public class Client {
public static void main(String[] args) {
Host host = new Host();
host.rent();
}
}
静态代理
一个真实角色就产生一个代理对象,代码量会翻倍
添加中介的角色,改改上述代码
中介类
package com.bupt.designPattern.proxyDesign;
public class Proxy implements Rent {
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
host.rent();
}
}
public class Client {
public static void main(String[] args) {
Host host = new Host();
Proxy proxy=new Proxy(host);
proxy.rent();
}
}
这样看也没什么嘛,还弯弯绕绕
但是中介可以实现其他的功能,是对房东角色的一个增强
例如
public class Proxy implements Rent {
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
this.seeHouse();
host.rent();
this.fare();
}
//看房
public void seeHouse() {
System.out.println("中介带你看房");
}
//收中介费
public void fare() {
System.out.println("中介收中介费");
}
}
中介多了看房和收中介费的功能
此时再执行原有main方法
中介带你看房
房东要出租房子
中介收中介费
代理是在原有上扩展,不修改原有业务代码
动态代理
动态代理和静态代理角色一样
动态代理的代理类是动态生成的,不是我们直接写好的!
基于接口的动态代理:JDK动态代理
基于类的动态代理:Cglib动态代理
都依赖了反射
动态代理这一块没太看懂,看文档吧
我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)
Spring中的设计模式
工厂模式
Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象。
参考博客讲解Spring
单例模式
Spring 中 bean 的默认作用域就是 singleton(单例)的。
使用单例模式的好处:
对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
代理模式
将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
我们的日志@Log 事务处理@Transactional以及权限控制@PreAuthorized
适配器模式
Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter
还有些有时间再补充,我也没学多少。。
结束时间:20220923