设计模式知识点汇总
什么是设计模式
- 设计模式是前辈们对开发经验的总结,是解决特定问题的一些列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案
学习设计模式的意义
- 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性、多态性以及类的关联关系和组合关系的充分理解
- 正确使用设计模式具有以下优点
- 可以提高程序员的思维能力、编程能力和设计能力
- 使程序设计更加标准化、代码编写更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期
- 使设计的代码可复用性高、可维护性强、可读性强、可靠性高、灵活性好
23种设计模式
- 创建型模式:1、单例模式;2、工厂模式;3、抽象工厂模式;4、创造者模式;5、原型模式
- 结构型模式:6、适配器模式;7、桥接模式;8、装饰模式;9、组合模式;10、外观模式;11、享元模式;12、代理模式
- 行为型模式:13、模板方法模式;14、命令模式;15、迭代器模式;16、观察者模式;17、中介者模式;18、备忘录模式;19、解释器模式;20、状态模式;21、策略模式;22、职责链模式;23、访问者模式
单例模式
核心作用
- 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
常见场景
- Windows的任务管理器
- Windows的回收站
- 项目中,读取配置文件的类,一般也只有一个对象,没必要每次都去new对象读取
- 网站的计数器一般也会采用单例模式,可以保证同步
- 数据库的连接池的设计一般也是单例模式
- 在Servlet编程中,每个Servlet也是单例的
- 在Spring中,每个Bean默认就是单例的
饿汉式单例
public class Hungry{
//未用之前就已经创建了对象,浪费内存空间
private byte[] data = new byte[1024*1024];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式单例
public class LazyMan{
private LazyMan(){
}
/*volatile:
1、保证了不同线程对这个变量进行操作时的可见性,
即一个线程修改了某个变量的值,这新值对其他线程是立即可见的;
2、禁止进行指令重排
*/
private volatile static LazyMan lazyMan;
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
//第一个判断条件是为了提高效率,先判断有没有竞争锁的必要
if(lazyMan==null){
synchronized(LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan(); //不是一个原子性操作
/*操作步骤
1、分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
期望执行顺序是123,实际执行顺序可能是132,此时发生了指令重排;
如果A线程执行了13过程,此时lazyMan还没有完成构造,
恰好此时B线程进来,判断lazyMan不为空
*/
}
}
}
return lazyMan;
}
}
反射会破坏单例模式
public class LazyMan{
private static boolean flag = false;
private LazyMan(){
synchronized(LazyMan.class){
if(flag == false){
flag = true;
}else{
throw new RuntimeException("不要试图使用反射破坏单例模式");
}
}
}
private volatile static LazyMan lazyMan;
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized(LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan(); //不是一个原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws NoSuchMethodException{
// 第一种创建实例方式
// LazyMan instance = LazyMan.getInstance();
Field flag = LazyMan.class.getDeclaredField("flag");
flag.setAccessible(true);
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
// 利用反射创建实例方式
LazyMan instance = constructor.newInstance();
flag.set(instance,false);
LazyMan instance2 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
反射破坏单例模式的解决方法
//enum(枚举):本身也是一个class类
public enum EnumSingle{
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args){
EnumSingle instance1 = EnumSingle.INSTANCE;
//EnumSingle instance2 = EnumSingle.INSTANCE;
Constructor<EnumSingle> constructor=
EnumSingle .class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
// 利用反射创建实例方式
EnumSingle instance2 = constructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
工厂模式
- 作用:实现了创建者和调用者的分离
- 详细分类:
- 简单工厂模式:用来生产同一等级结构中的任意产品(对于增加新的产品,需要修改已有代码)
- 工厂方法模式:用来生产同一等级结构中的固定产品(支持增加任意产品)
- 抽象工厂模式:围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工厂的工厂,可以增加产品族(比如:小米手机和小米路由器等小米产品就属于同一个产品族)
- 核心本质:
- 实例化对象不使用new,用工厂方法代替
- 将选择实现类,创建对象统一管理和控制,从而将调用者和我们的实现类解耦
- 简单工厂模式和工厂方法模式对比:
- 结构复杂度:简单工厂模式占优
- 代码复杂度:简单工厂模式占优
- 编程复杂度:简单工厂模式占优
- 管理上的复杂度:简单工厂模式占优
结论:根据设计原则,选择工厂方法模式;根据实际业务,选择简单工厂模式
- 小结:
- 简单工厂模式:虽然某种程度上不符合设计原则,但实际使用最多
- 工厂方法模式:不修改已有工厂类的前提下,通过增加新的工厂类实现扩展
- 抽象工厂模式:不可以增加产品,可以增加产品族
- 应用场景:
- JDK中Calendar的getInstance方法
- JDBC中的Connection对象的获取
- Spring中IOC容器创建管理bean对象
- 反射中Class对象的newInstance方法
简单工厂模式
//也叫静态工厂模式
//增加一个新的产品,需要修改原工厂类的代码
public interface Car{
public void name();
}
public class WuLing implements Car{
@Override
public void name(){
System.out.println("五菱宏光!");
}
}
public class Tesla implements Car{
@Override
public void name(){
System.out.println("特斯拉!");
}
}
public class CarFactory{
public static Car getCar(String car){
if("五菱".equals(car)){
return new WuLing();
}else if("特斯拉".equals(car)){
return new Tesla();
}else {
return null;
}
}
}
public class Consumer{
public static void main(String[] args){
Car car = CarFactory.getCar("五菱");
car.name();
}
}
工厂方法模式
public interface Car{
public void name();
}
public class WuLing implements Car{
@Override
public void name(){
System.out.println("五菱宏光!");
}
}
public class Tesla implements Car{
@Override
public void name(){
System.out.println("特斯拉!");
}
}
public interface CarFactory{
public Car getCar();
}
public class WuLingFactory implements CarFactory{
@Override
public Car getCar(){
return new WuLing();
}
}
public class TeslaFactory implements CarFactory{
@Override
public Car getCar(){
return new Tesla();
}
}
public class Consumer{
public static void main(String[] args){
Car car = new WuLingFactory().getCar();
car.name();
}
}
抽象工厂模式
定义:
- 抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类
适用场景:
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
- 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
- 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现
优点:
- 具体产品在应用层的代码隔离,无需关心创建的细节
- 将一个系列的产品统一到一起创建
缺点:
- 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难
- 增加了系统的抽象性和理解难度
代码:
//汽车产品接口
public interface Car{
public void name();
}
public class WuLing implements Car{
@Override
public void name(){
System.out.println("五菱宏光汽车!");
}
}
public class Tesla implements Car{
@Override
public void name(){
System.out.println("特斯拉汽车!");
}
}
//电动车产品接口
public interface Electric{
public void name();
}
public class WuLingElectric implements Electric{
@Override
public void name(){
System.out.println("五菱宏光电动车!");
}
}
public class TeslaElectric implements Electric{
@Override
public void name(){
System.out.println("特斯拉电动车!");
}
}
//产品工厂
public interface ProductFactory{
//生产汽车
public Car getCar();
//生产电动车
public Electric getElectric();
}
public class WuLingFactory implements ProductFactory{
@Override
public Car getCar(){
return new WuLing();
}
@Override
public Electric getElectric(){
return new WuLingElectric();
}
}
public class TeslaFactory implements ProductFactory{
@Override
public Car getCar(){
return new Tesla();
}
@Override
public Electric getElectric(){
return new TeslaElectric();
}
}
public class Consumer{
public static void main(String[] args){
Car car = new WuLingFactory().getCar();
car.name();
Electric electric = new WuLingFactory().getElectric();
electric.name();
}
}
建造者模式
定义:
- 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
- 用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
主要作用:
- 在用户不知道对象建造过程和细节的情况下就可以直接创建复杂的对象
例子
- 工厂(建造者模式):负责组装汽车(组装过程和细节在工厂内)
- 汽车购买者(用户):你只要说出你需要的型号(对象的类型和内容),然后直接购买就可以使用了
与工厂模式的区别:
- 工厂模式负责生产汽车零件,而建造者模式负责组装汽车零件为汽车
代码:
//产品
public class Product{
private String buildA;
private String buildB;
private String buildC;
private String buildD;
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;
}
public String getBuildC(){
return buildC;
}
public void setBuildC(String buildC){
this.buildC = buildC;
}
public String getBuildD(){
return buildD;
}
public void setBuildD(String buildD){
this.buildD = buildD;
}
public String toString(){
return "Product{buildA="+buildA+", buildB="+buildB+", buildC="+buildC+", buildD="+buildD+"}";
}
}
//抽象的建造者
public abstract class Builder{
abstract void buildA();
abstract void buildB();
abstract void buildC();
abstract void buildD();
//得到产品
abstract Product getProduct();
}
//具体的建造者
public class Worker extends Builder{
private Product product;
public Worker(){
//至关重要的一步
product = new Product();
}
@Overrid
void buildA(){
product.setBuildA("地基");
System.out.println("地基");
}
@Overrid
void buildB(){
product.setBuildB("钢筋工程");
System.out.println("钢筋工程");
}
@Overrid
void buildC(){
product.setBuildC("铺电线");
System.out.println("铺电线");
}
@Overrid
void buildD(){
product.setBuildD("粉刷");
System.out.println("粉刷");
}
//得到产品
@Override
Product getProduct(){
return produce;
}
}
//指挥:核心,负责指挥购建一个工程,工程如何构建,由它决定
public class Director{
//指挥工人按照顺序建房子
public Product build(Builder builder){
builder.buildA();
builder.buildB();
builder.buildC();
builder.buildD();
return builder.getProduct();
}
}
public class Test{
public static void main(String[] args){
Director director = new Director();
Product product = director.build(new Worker());
System.out.println(product.toString());
}
}
原型模式
应用场景:
- Prototype(JavaScript中的原型链)
- Cloneable接口
- clone()方法
- Spring Bean
代码:
/*
1、实现一个接口 Cloneable
2、重写一个方法
*/
public class Video implements Cloneable{
private String name;
private Date createTime;
/*浅拷贝
@Override
protected Object clone() throws CloneNotSupporedException{
return super.clone();
}*/
//实现深拷贝
@Override
protected Object clone() throws CloneNotSupporedException{
Object obj = super.clone();
Video v = (Video) obj;
v.createTime = (Date) this.createTime.clone();
return obj;
}
public Video(){
}
public Video(String name,Date createTime){
this.name = name;
this.createTime = createTime;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public Date getCreateTime(){
return createTime;
}
public void setCreateTime(Date createTime){
this.createTime = createTime;
}
@Override
public String toString(){
return "Video{name="+name+", createTime="+createTime+"}";
}
}
public class Test{
public static void main(String[] args){
//原型对象
Date date = new Date();
Video v1 = new Video("原型模式讲解",date);
System.out.println(v1);
//v1克隆成v2
Video v2 = (Video)v1.clone();
System.out.println(v2);
}
}
适配器模式
- 属于结构型模式,结构型模式从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题
- 将一个类的接口转换成客户希望的另外一个接口,使得原本接口不兼容而不能一起工作的那些类可以在一起工作
对象适配器优点:
- 一个对象适配器可以把多个不同的适配者适配到同一个目标
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏替换原则”,适配者的子类也可以通过该适配器进行适配
类适配器缺点:
- 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者
- 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性
适用场景:
- IO流中
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码
- 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作
代码:
//要被适配的类:网线
public class Adaptee{
public void request(){
System.out.println("连接网线上网");
}
}
public interface NetToUsb{
//作用:处理请求
public void handleRequest();
}
//真正的适配器,需要连接网线和usb
public class Adapter implements NetToUsb{
//此时为对象适配器,如果是继承Adaptee类,则是类适配器
private Adaptee adaptee;
public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
@Override
public void handleRequest(){
adaptee.request();
}
}
//要上网,插不上网线:电脑
public class Computer{
public void net(NetToUsb adapter){
//上网的具体实例,找一个转接头
adapter.handleRequest;
}
}
public class Test{
public static void main(String[] args){
Computer computer = new Computer(); //电脑
Adaptee adaptee = new Adaptee(); //网线
Adapter adapter = new Adapter(adaptee); //适配器
computer.net(adapter);
}
}
桥接模式
- 桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立的变化。它是一种对象结构型模式,又称为柄体模式或接口模式
与适配器模式比较:
- 适配器模式解决兼容问题
- 桥接模式解决功能扩展问题
优点:
- 桥接模式偶尔类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是多继承方案更好的解决方法。极大的减少了子类的个数,从而降低管理和维护的成本
- 桥接模式提高了系统的可扩充性,在两个变化的维度中任意扩展一个维度,都不需要修改原有的系统。符合开闭原则,就像一座桥,可以把两个变化的维度连接起来
缺点:
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性
场景:
- Java语言通过Java虚拟机实现了平台的无关性
- AWT中的Peer架构
- JDBC驱动程序也是桥接模式的应用之一
代码:
//品牌
public interface Brand{
public void info();
}
//联想品牌
public class Lenovo implements Brand{
@Override
public void info(){
System.out.println("联想");
}
}
//苹果品牌
public class Apple implements Brand{
@Override
public void info(){
System.out.println("苹果");
}
}
//类型
public abstract class Computer{
//组合,品牌
protected Brand brand;
public Computer(){
}
public Computer(Brand brand){
this.brand = brand;
}
public void info(){
brand.info();
}
}
//台式机
public class Desktop extends Computer{
public Desktop(){
}
public Desktop(Brand brand){
super(brand);
}
@Override
public void info(){
super.info();
System.out.println("台式机");
}
}
//笔记本
public class Laptop extends Computer{
public Laptop(){
}
public Laptop(Brand brand){
super(brand);
}
@Override
public void info(){
super.info();
System.out.println("笔记本");
}
}
public class Test{
public static void main(String[] args){
//苹果笔记本
Computer computer = new Laptop(new Apple());
//联想台式机
Computer computer2 = new Desktop(new Lenovo());
}
}
代理模式
为什么要学习代理模式?因为这就是SpringAOP(面向切面编程)的底层
代理模式的分类
静态代理
- 角色分析:
- 抽象角色:一般会使用接口或抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象的人
- 代码:
//租房
public interface Rent{
public void rent();
}
//房东
public class Host implements Rent{
@Override
public void rent(){
System.out.println("房东要出租房子!");
}
}
//代理:中介
public class Proxy implements Rent{
private Host host;
public Proxy(){
}
//方法一:构造方法注入
public Proxy(Host host){
this.host = host;
}
//方法二:set方法注入
/*public void setHost(Host host){
this.host = host;
}*/
@Override
public void rent(){
host.rent();
seeHouse();
heTong();
}
//看房
public void seeHouse(){
System.out.println("中介带租客看房");
}
//签合同
public void heTong(){
System.out.println("中介与租客签合同");
}
}
//租客
public class Tenant{
public static void main(String[] args){
Proxy proxy = new Proxy(new Host());
proxy.rent();
}
}
动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口:JDK动态代理【我们在这里使用】
- 基于类:cglib
- 优点:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共业务交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候方便集中处理
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
- 代码:
//需要了解两个类:Proxy:代理,InvocationHandler:调用处理程序
//租房
public interface Rent{
public void rent();
}
//房东
public class Host implements Rent{
@Override
public void rent(){
System.out.println("房东要出租房子!");
}
}
//用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler{
//被代理的接口
private Object target;
public void setTarget(Target target){
this.target = target;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader, target.getClass().getInterface(), this);
}
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
//动态代理的本质,就是使用反射机制实现
Object result = method.invoke(target, args);
return result;
}
//看房
public void seeHouse(){
System.out.println("中介带租客看房");
}
//签合同
public void heTong(){
System.out.println("中介与租客签合同");
}
}
//租客
public class Tenant{
public static void main(String[] args){
//真实角色
Host host = new Host();
//代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//设置要代理的对象
pih.setTarget(host);
//动态生成代理类
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
代理模式的好处
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共业务交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候方便集中处理
缺点
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低
面向对象程序设计(OOP)七大原则
- 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性(一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中)
- 开闭原则:对扩展开放,对修改关闭(在不修改原有功能的基础上能扩展其他功能)
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立(子类可以扩展父类的功能,但是尽量不要改变父类原有的功能)
- 依赖倒置原则:要面向接口编程,不要面向实现编程(抽象不依赖细节,细节应该依赖抽象)
- 接口隔离原则:要为各个类建立它们需要的专用接口
- 迪米特法则:只与你的直接朋友交谈,不跟陌生人说话(controller——>service——>dao)
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现