# lb-design **Repository Path**: lixiaobin2018/lb-design4645 ## Basic Information - **Project Name**: lb-design - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2019-03-07 - **Last Updated**: 2020-12-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 设计模式 ## 工厂模式 个人观点:抽象工厂模式和工厂方法模式,最大的区别点就在于前者有产品族概念,一个工厂可以生产多种类型的产品。在我写的例子中,电子厂既可以生产手机也可以生产笔记本电脑; 抽象工厂所承载的职责更多,是工厂方法的升级版 UML类结构图地址:https://www.processon.com/view/link/5c810c65e4b02b2ce490257a ## 单例模式 - **饿汉单例** - 线程安全,但是会无端的占用内存(占着茅坑不拉屎) - **懒汉单例** - 用的时候才会生成对象,但是有线程安全问题 - 可以采用加锁的方式解决线程安全问题,synchronized 关键字锁住方法或者双重锁,加锁会影响代码执行效率。 ```java public static LazyDoubleCheckSingleton getInstance(){ if (lazyDoubleCheckSingleton == null){ synchronized (LazyDoubleCheckSingleton.class){ if (lazyDoubleCheckSingleton ==null) { lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); } } } return lazyDoubleCheckSingleton; } ``` - **静态内部类实现单例** - 内部类默认是不加载的,既不存在线程安全问题,又避免了synchronized存在的影响性能的问题,是比较完美的单例。 即便内部类实现的单例集合了懒汉和恶汉的优点(懒汉:不浪费内存,恶汉:线程安全,锁,影响性能),但是还是会被反射破坏单例 ```java Class clazz = LazyInnerclassSingleton.class; Constructor c = clazz.getDeclaredConstructor(null); //强制访问私有变量 c.setAccessible(true); //暴利初始化 Object o1 = c.newInstance(); Object o2 = c.newInstance(); System.out.println(o1==o2);//false ``` 解决办法:构造方法抛出异常 ```java private LazyInnerclassSingleton(){ if (LazyHolder.lazyInnerclassSingieton !=null){ throw new RuntimeException("不允许创建多个实例"); } } ``` 序列化破坏单例 ```java SeriableHungrySingleton seriableHungrySingleton = null; SeriableHungrySingleton seriableHungrySingleton1 = SeriableHungrySingleton.getInstance(); try { FileOutputStream fos = new FileOutputStream("a.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(seriableHungrySingleton1); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("a.obj"); ObjectInputStream ois = new ObjectInputStream(fis); seriableHungrySingleton = (SeriableHungrySingleton)ois.readObject(); ois.close(); System.out.println(seriableHungrySingleton ==seriableHungrySingleton1);//false } catch (Exception e) { e.printStackTrace(); } ``` 解决方法:重写`readResolve()`方法 ```java private Object readResolve(){ return hungrySingleton; } ``` - **注册容器式单例(Spring使用)** - Spring中使用`ConcurrentHashMap`作为ioc容器`DefaultListableBeanFactory`,实现了Spring中的单例模式 - **枚举式单例** - Effective Java 推荐的用的单例,从JDK层面保证了单例不被破坏。 - **Threadlocal单例(伪单例,只能位置同一线程内的单例)** 内部类时序图地址:https://www.processon.com/view/link/5c879b64e4b01e76977bd77a **常见应用**:Spring中的应用:`ApplicationContext`、`DefaultListableBeanFactory`,数据库连接池,J2EE 标准中,`ServletContext`、`ServletContextConfig` 等。 ## 原型模式 原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这些 原型创建新的对象。 ##### 原型模式主要适用于以下场景: 1. 类初始化消耗资源较多; 2. new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等); 3. 构造函数比较复杂; 4. 循环体中生产大量对象时。 在 Spring 中,原型模式应用得非常广泛。例如 scope=“prototype”,在我们经常用 的 JSON.parseObject()也是一种原型模式。 - 浅克隆 浅克隆只会复制基本数据类型(值类型)的数据,对于引用类型,比如List,并不会改变引用地址,所有的引用对象仍然指向之前的对象。 >一、基本数据类型 > Java中一共分为8种基本数据类型:byte、short、int、long、float、double、char、boolean,其中byte、short、int、long是整型。float、double是浮点型,char是字符型,boolean是布尔型。 > 二、引用类型 >Java为每种基本类型都提供了对应的封装类型,分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean。引用类型是一种对象类型,它的值是指向内存空间的引用,就是地址。 - 深克隆 克隆会破坏单例,为了防止单例被破坏,只需要重写 `clone()`方法,禁止克隆。 ##### Cloneable 源码分析 常用的 ArrayList 就实现了 Cloneable 接口,重写clone()方法 ```java public Object clone() { try { ArrayList v = (ArrayList) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } } ``` ## 代理模式 - **JDK Proxy 动态代理** JDK Proxy 采用字节重组,重新生的对象来替代原始的对象以达到动态代理 的目的。JDK Proxy 生成对象的步骤如下: 1. 拿到被代理对象的引用,并且获取到它的所有的接口,反射获取。 2. JDK Proxy 类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接 口。 3. 动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体 现)。 4. 编译新生成的 Java 代码.class。 5. 再重新加载到 JVM 中运行。 - **CGLib 和 JDK 的动态代理对比** 1. **JDK 动态代理是实现了被代理对象的接口,CGLib 是继承了被代理对象。目的都是生成一个新的类,去实现增强代码逻辑的功能** 2. JDK 和 CGLib 都是在运行期生成字节码,JDK 是直接写 Class 字节码,CGLib 使用 ASM 框架写 Class 字节码,Cglib 代理实现更复杂,生成代理类的过程比 JDK 效率低。 3. JDK 调用代理方法,是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用方法, CGLib 执行效率更高 **常见应用**:三层架构就属于代理模式 ## 委派模式 委派模式不属于 GOF23 种设计模式中。委派模式(Delegate Pattern)的基本作用就是负责任务的调用和分配任务,跟代理模式很像,可以看做是一种特殊情况下的静态代理的全权代理,但是**代理模式注重过程,而委派模式注重结果**。委派模式在 Spring 中应用非常多,大家常用的`DispatcherServlet` 其实就是用到了委派模式。在 Spring 源码中,只要以 Delegate 结尾的都是实现了委派模式。例如:`BeanDefinitionParserDelegate` 根据不同类型委派不同的逻辑解析 `BeanDefinition`。 **Spring中的应用** :`DispatcherServlet`、`BeanDefinitionParserDelegate` ## 策略模式 #### 策略模式优缺点 ##### 优点 1. 策略模式符合开闭原则。 2. 避免使用多重条件转移语句,如 if...else...语句、switch 语句 3. 使用策略模式可以提高算法的保密性和安全性。 ##### 缺点: 1. 客户端必须知道所有的策略,并且自行决定使用哪一个策略类。 2. 代码中会产生非常多策略类,增加维护难度。 **常见应用**:Spring中的应用:`Resource`、`InstantiationStrategy` ## 模板模式(模板方法模式) 模板模式通常又叫模板方法模式(Template Method Pattern)是指定义一个算法的骨 架,并允许子类为一个或者多个步骤提供实现。模板方法使得子类可以在不改变算法结 构的情况下,重新定义算法的某些步骤,属于行为性设计模式。模板方法适用于以下应 用场景: 1. 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。 2. 各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。 #### 模板模式的优缺点 ##### 优点: 1. 利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性。 2. 将不同的代码不同的子类中,通过对子类的扩展增加新的行为,提高代码的扩展性。 3. 把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。 ##### 缺点: 1. 类数目的增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加。 2. 类数量的增加,间接地增加了系统实现的复杂度。 3. 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍。 **常见应用**:Mybatis中的应用:`BaseExecutor`、JDK中的应用:`AbstractList` ## 适配器模式 适配器模式(Adapter Pattern)是指将一个类的接口转换成客户期望的另一个接口,使原本的接口不兼容的类可以一起工作,属于结构型设计模式。 适配器适用于以下几种业务场景: 1. 已经存在的类,它的方法和需求不匹配(方法结果相同或相似)的情况。 2. 适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。有点亡羊补牢的感觉。 生活中也非常的应用场景,例如电源插转换头、手机充电转换头、显示器转接头。互联网中的登录方式,原本只有账号密码登陆,后来逐渐扩展了微信、qq、微博等登陆方式。 #### 适配器模式的优缺点 ##### 优点: 1. 能提高类的透明性和复用,现有的类复用但不需要改变。 2. 目标类和适配器类解耦,提高程序的扩展性。 3. 在很多业务场景中符合开闭原则。 ##### 缺点: 1. 适配器编写过程需要全面考虑,可能会增加系统的复杂性。 2. 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。 **常见应用**Spring 中适配器模式也应用得非常广泛,例如:SpringAOP 中的 `AdvisorAdapter` 类,它有三个实现类 `MethodBeforeAdviceAdapter`、`AfterReturningAdviceAdapter` 和`ThrowsAdviceAdapter` ## 装饰者模式 装饰者模式(Decorator Pattern)是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。 -|装饰者模式|适配器模式 --|:--:|:--: 形式|是一种非常特别的适配器模式|没有层级关系,装饰器模式有层级关系 定义|装饰者和被装饰者都实现同一个接口,主要目的是为了扩展之后依旧保留 OOP 关系|适配器和被适配者没有必然的联系,通常是采用继承或代理的形式进行包装 关系|满足 is-a 的关系|满足 has-a 的关系 功能|注重覆盖、扩展|注重兼容、转换 设计|前置考虑|后置考虑 #### 装饰者模式的优缺点 ##### 优点: 1. 装饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象 扩展功能,即插即用。 2. 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。 3. 装饰者完全遵守开闭原则。 ##### 缺点: 1. 会出现更多的代码,更多的类,增加程序复杂性。 2. 动态装饰时,多层装饰时会更复杂。 那么装饰者模式我们就讲解到这里,希望小伙伴们认真体会,加深理解。 **常见应用** JDK 中体现最明显的类就是 IO 相关的类,`BufferedReader`、`nputStream`、`OutputStream`,`Spring` 中的`TransactionAwareCacheDecorator` ## 观察者模式 观察者模式(Observer Pattern)定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新,属于行为型模式。观察者模式有时也叫做发布订阅模式。观察者模式主要用于在关联行为之间建立一套触发机制的场景。观察者模式在现实生活应用也非常广泛,比如:微信朋友圈动态通知、GPser 生态圈消息通知、邮件通知、广播通知、桌面程序。 #### 观察者模式的优缺点 ##### 优点: 1. 观察者和被观察者之间建立了一个抽象的耦合。 2. 观察者模式支持广播通信。 ##### 缺点: 1. 观察者之间有过多的细节依赖、提高时间消耗及程序的复杂度。 2. 使用要得当,要避免循环调用。 **常见应用**:Spring 中的 `ContextLoaderListener`, JDK 中 `EventListener` ## 设计模式总结 #### 一句话总结设计模式 |设计模式|一句话归纳|举例 :--|:--|:-- 工厂模式|只对结果负责,封装创建过程。我只要合格产品,不关心生产过程。|BeanFactory、Calender 单例模式|保证独一无二。始终只有一个实例。|ApplicationContext、Calender 原型模式|拔一根猴毛,吹出千万个。影分身,变出千千万万个我。|ArrayList、PrototypeBean 代理模式|找人办事,增强职责。我的事情叫别人帮我做,我只需要告诉他我的要求。|ProxyFactoryBean、JdkDynamicAopProxy、CglibAopProxy 委派模式|干活算你的(普通员工),功劳算我的(项目经理)。|DispatcherServlet、BeanDefinitionParserDelegate 策略模式|用户选择,结果统一。完成一个目标,我有好多种选择。|InstantiationStrategy 模板模式|流程标准化,自己实现定制。固定环节定下来,我只需要做不一样的那部分。|JdbcTemplate、HttpServlet 适配器模式|兼容转换头。转接头。|AdvisorAdapter、HandlerAdapter 装饰者模式|包装,同宗同源。无需继承,通过一次次包装完成豪华升级。|BufferedReader、InputStream、OutputStream、HttpHeadResponseDecorator 观察者模式|任务完成时通知。我一发布通知,你就会收到。|ContextLoaderListener #### IOC、AOP、DI 理解 **aop** 面向规则编程,将一系列同样业务规则的代码,放在一起组成一个面。主要应用场景权限认证、日志记录、自动缓存处理、统一错误处理等。 ```java @Component //加入IOC容器 @Aspect // 指定当前类为切面类 public class Aop { // 指定切入点表达式: 拦截哪些方法; 即为哪些类生成代理对象 // 解释@Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))") // @Pointcut("execution(* 切入点表达式固定写法, cn.itcast.e_aop_anno表示包.类名(可以用*表示包下所有的类).方法名(可以用*表示类下所有的方法)(..)表示参数可以用.. @Pointcut("execution(* com.lb.aop.*.*(..))") public void pointCut_(){ } //@Before("execution(* cn.itcast.e_aop_anno.*.*(..))")每个方法需要写相同的引用,所以将相同的部分抽取到一个空的方法中pointCut_(), // 前置通知 : 在执行目标方法之前执行 @Before("pointCut_()") public void begin(){ System.out.println("开始事务/异常"); } // 后置/最终通知:在执行目标方法之后执行 【无论是否出现异常最终都会执行】 @After("pointCut_()") public void after(){ System.out.println("提交事务/关闭"); } // 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】 @AfterReturning("pointCut_()") public void afterReturning() { System.out.println("afterReturning()"); } // 异常通知: 当目标方法执行异常时候执行此关注点代码 @AfterThrowing("pointCut_()") public void afterThrowing(){ System.out.println("afterThrowing()"); } // 环绕通知:环绕目标方式执行 @Around("pointCut_()") public void around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("环绕前...."); pjp.proceed(); // 执行目标方法 System.out.println("环绕后...."); } } ``` **IOC**:将原本自己new 对象的权利交给了Spring,Spring将创建的对象保存在一个IOC容器中,视只为JavaBean。 ```java @Component //加入IOC容器 public class Aop { ... } @Controller @Service @Respository ``` **DI**:又叫DL(Dependency Lookup)依赖注入,当某一个类需要某个对象的时候,Spring会从ICO容器中直接取出来。 ```java @Autowire @Qualifier("xxxDao") @Resource("xxxDao") ``` 一个运用Spring的Demo: