设计模式简介-第一节
分类
总体来说设计模式分为三大类:
-
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
-
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
-
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
一、创建型模式
1、单例模式
1.1、定义:
一个类只能有一个对象实例,并且自行实例化并向整个系统提供这个实例,有以下特点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
分为三种
- 饿汉模式
- 懒汉模式
- 双重检查加锁
1.2、饿汉模式
在装载类的时候就创建了对象,这是线程安全的,但是占用空间比较多
public class TestSingleton { private static TestSingleton testSingleton = new TestSingleton(); private TestSingleton() { } public static TestSingleton getInstance() { return testSingleton; } }
1.3、懒汉模式
访问的时候再去创建对象,典型的时间换空间,每次访问都进行判断,会降低访问速度,但是如果访问量比较低就会节约空间。而且非线程安全
public class TestSingleton { private static TestSingleton testSingleton = null; private TestSingleton() { } public static TestSingleton getInstance() { if (testSingleton == null) { return new TestSingleton(); } else { return testSingleton; } } }
1.4、双重检查加锁
通过双重检查加锁,就可以既实现线程安全,又能够使性能不受很大的影响
并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
“双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
public class Singleton { private volatile static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ //先检查实例是否存在,如果不存在才进入下面的同步块 if(instance == null){ //同步块,线程安全的创建实例 synchronized (Singleton.class) { //再次检查实例是否存在,如果不存在才真正的创建实例 if(instance == null){ instance = new Singleton(); } } } return instance; } }
2、工厂模式
工厂模式分为简单工厂和工厂方法和抽象工厂
2.1、简单工厂
一个工厂类,不同条件下实例化不同的对象
public class SimplePizzaFactory { public Pizza CreatePizza(String ordertype) { Pizza pizza = null; if (ordertype.equals("cheese")) { //不同的条件判断 pizza = new CheesePizza(); } else if (ordertype.equals("greek")) { pizza = new GreekPizza(); } else if (ordertype.equals("pepper")) { pizza = new PepperPizza(); } return pizza; } }
简单工厂带来的问题就是耦合性太强,修改条件就得修改工厂类,所以就有了工厂方法
2.2、工厂方法
定义一个抽象类,将实例化对象的任务交给抽象类的子类来完成,这样如果需要增加条件那么增加子类就好了,达到了低耦合的目标
//抽象类 public interface OrderPizza { Pizza CreatePizza(String ordertype) ; }
//子类 public class LDOrderPizza extends OrderPizza { Pizza createPizza(String ordertype) { Pizza pizza = null; if (ordertype.equals("cheese")) { pizza = new LDCheesePizza(); } else if (ordertype.equals("pepper")) { pizza = new LDPepperPizza(); } return pizza; } } public class NYOrderPizza extends OrderPizza { Pizza createPizza(String ordertype) { Pizza pizza = null; if (ordertype.equals("cheese")) { pizza = new NYCheesePizza(); } else if (ordertype.equals("pepper")) { pizza = new NYPepperPizza(); } return pizza; } }
工厂方法也有局限性,就是如果有多个系列产品,那就需要把工厂方法所有的类复制一份,修改成别的产品,为了解决这一问题,抽象工厂出现了
2.3、抽象工厂
披萨的抽象方法改成接口,同样也有例如蛋糕这样的接口,他们分别都有实现的工厂类。然后抽象工厂类里填写创建两个产品的接口方法,抽象工厂的实现子类去真正创建所有的产品
//披萨工厂接口 public interface PizzaFactory { Pizza CreatePizza(String orderType); } //水果披萨工厂 public class FruitPizzaFactory implements PizzaFactory { public Pizza CreatePizza(String orderType) { if (orderType.equals("Apple")) { return new Pizza(30,"Apple"); } else if (orderType.equals("Banana")) { return new Pizza(30,"Banana"); } else { return null; } } } #抽象工厂 public interface Order { Pizza CreatePizza(String orderType); //还可以写别的产品 } //实现抽象工厂类 public class Supermarket implements Order{ public Pizza CreatePizza(String orderType) { return new FruitPizzaFactory().CreatePizza(orderType); } } #主方法调用 public class Test { public static void main(String[] args) { Order order = new Supermarket(); order.CreatePizza("banana"); } }
三种方法的比较:
-
简单工厂 : 用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
-
工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品)
-
抽象工厂 :用来生产不同产品族的全部产品。(支持拓展增加产品;支持增加产品族)
二、结构型模式
1、代理模式
代理模式分为静态代理和动态代理
1.1、静态代理
角色分析:
- 抽象角色:一般用接口或者抽象类
- 真实角色(房东):被代理的角色
- 代理角色(中介):代理真实角色,代理真实角色后,我们一般会有一些附属操作
- 客户(租客):访问代理角色的人
#抽象角色 public interface BuyHouse { void RentHosue(); } #房东 public class BuyHouseImpl implements BuyHouse { @Override public void RentHosue() { System.out.println("我要租房"); } } #中介 public class RentHouseProxy implements RentHouse { private RentHouse rentHouse ; public RentHouseProxy(final RentHouse RentHouse ) { this.RentHouse = RentHouse ; } @Override public void RentHouse () { System.out.println("看房"); buyHouse.buyHosue(); System.out.println("收中介费"); } }
好处是:
- 使得真实角色的操作更加纯粹,不用管一些公共的事物
- 公共事物交给了代理,实现了分工
- 公共业务发生拓展的时候方便管理
缺点是:
- 因为是静态的,所以一个真实角色对应一个代理,耦合性太强
1.2、动态代理
- 动态代理和静态代理的角色是一样的,区别在于代理角色是动态生成的
- 动态代理分为两大类,基于接口的动态代理、基于类的动态代理
基于接口—JDK动态代理
基于类:cglib
java字节码实现:javasist
这里介绍一下基于接口的动态代理,需要了解两个类:Proxy(代理类),InvocationHandler:调用处理程序
Proxy:
InvocationHandler:
例子:
//抽象角色: package com.wcy.demo; public interface Rent { void rent(); }
//房东 public class Host implements Rent{ @Override public void rent() { System.out.println("房东要出租房子"); } }
//动态代理类 import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class testProxy implements InvocationHandler { private Object object; public void setRent(Rent rent) { this.object = rent; } //生成代理类 public Object getProxy() { return Proxy.newProxyInstance(this.getClass().getClassLoader(),object.getClass().getInterfaces(),this); //classloader用于知道类在哪,object.getClass().getInterfaces()用于知道代理的类,最后一个知道动态代理给谁 } //处理代理实例,并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { seeHouse(); System.out.println("执行了" + method.getName() + "方法"); Object result = method.invoke(object,args); fare(); return result; } //代理公共操作 public void seeHouse() { System.out.println("中介带看房子"); } public void fare() { System.out.println("收费"); } }
//测试代码 public class Client { public static void main(String[] args) { //生成真实角色 Host host = new Host(); Host2 host2 = new Host2(); //代理角色 testProxy tproxy = new testProxy(); tproxy.setRent(host); Rent actual_proxy = (Rent) tproxy.getProxy(); actual_proxy.rent(); //代理第二位房东 tproxy.setRent(host2); actual_proxy.rent(); } }
1.3 动态代理源码解析
动态代理关键在于获取代理类,如上所述,getProxy()获取的代理类的方法已经实现了代理,rent方法实际已经变成了invoke里的逻辑,那么是如何实现的呢?
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { //动态代理类非空判断 Objects.requireNonNull(h); //被代理类接口复制 final Class<?>[] intfs = interfaces.clone(); //安全检查 final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * **通过加载器和接口数组获取一个特殊的代理类 (重要)** */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } //获取特殊代理类有InvocationHandler参数的构造器 final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; //如果构造器权限不是public的,设置未public if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } //通过反射,将代理类作为参数,创建被代理对象,此时返回的被代理对象就实现了动态代理 return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
可以看到,除了一些必要的安全检查和权限check之外,主要步骤是
- 生产一个能够将 代理类 传入 被代理类 的 特殊代理类
- 通过反射,生产被代理类实例
生产这个特殊代理类的源码有些复杂,底层调用的是ProxyClassFactory.apply方法,核心代码如下
/* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { //生产对应的被代理类对象 return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); }
2、适配器模式
适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。
主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
#类适配器 /** 继承USBImpl,有了它的方法,然后本身又是VGA类,既可以把USBImpl适配到VGA上了 */ public class AdapterUSB2VGA extends USBImpl implements VGA { @Override public void projection() { super.showPPT(); } }
#对象适配器 /** 继承USBImpl,有了它的方法,然后本身实现VGA类,既可以把USBImpl适配到VGA上了 */ public class AdapterUSB2VGA extends USBImpl implements VGA { @Override public void projection() { super.showPPT(); } }
## 对象适配器 /** 通过依赖USBImpl对象,然后本身又是VGA类,既可以把USBImpl适配到VGA上了 */ public class AdapterUSB2VGA implements VGA { USB u = new USBImpl(); @Override public void projection() { u.showPPT(); } }
# 接口依赖器(当不想实现适配后的对象所有的方法时使用) /** 可以先创建一个抽象类,依赖USBImpl对象,然后实现这个抽象类的时候选中对应的方法就好了 */ public abstract class AdapterUSB2VGA implements VGA { USB u = new USBImpl(); @Override public void projection() { u.showPPT(); } @Override public void b() { }; @Override public void c() { }; } public class AdapterUSB2VGAImpl extends AdapterUSB2VGA { public void projection() { super.projection(); } }
三、行为型模式
1、策略型模式
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。
其实就是一个函数如果有太多if...else耦合性就太强,那么通过不同的类封装不同的函数,就可以避免,这就是策略模式
//策略接口 public interface Strategy { public int calc(int num1,int num2); }
//具体策略类 public class AddStrategy implements Strategy { @Override public int calc(int num1, int num2) { // TODO Auto-generated method stub return num1 + num2; } } public class SubstractStrategy implements Strategy { @Override public int calc(int num1, int num2) { // TODO Auto-generated method stub return num1 - num2; } }
//环境 public class Environment { private Strategy strategy; public Environment(Strategy strategy) { this.strategy = strategy; } public int calculate(int a, int b) { return strategy.calc(a, b); } }
//测试类 public class MainTest { public static void main(String[] args) { Environment environment=new Environment(new AddStrategy()); int result=environment.calculate(20, 5); System.out.println(result); Environment environment1=new Environment(new SubstractStrategy()); int result1=environment1.calculate(20, 5); System.out.println(result1); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具