设计模式六大原则

开闭原则(Open Close Principle)

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

里氏代换原则(Liskov Substitution Principle)

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
封装继承多态 重写(模版方法设计模式中)接口、抽象类

依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。

接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

迪米特法则(最少知道原则)(Demeter Principle)

为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

设计模式的好处:使用设计模式可以重构架构代码、提高代码复用性、扩展性

1.单例模式

什么是单例模式?
单例模式确保一个类只有一个实例,并提供一个全局访问点,实现单例模式的方法是私有化构造函数,通过getInstance()方法实例化对象,并返回这个实例

保证在jvm中只有一个实例 幂等
JVM中如何保证实例的幂等问题 保证唯一性

单例模式的优缺点:
1.单例类只有一个实例
2.共享资源,全局使用
3.节省创建时间,提高性能
缺点:可能存在线程不安全的问题,解决线程安全问题
单例模式的七种写法:
1)饿汉模式
优点:先天性线程安全 为什么先天性 当类初始的时候,就会创建该对象
缺点:如果项目使用过多的饿汉式会发生问题,项目在启动的时候的时候会变的非常慢、存放在方法区占用内存比较大,如果用户不使用该对象的时候,也会被提前创建好

  1. /**
  2. * 单例模式:饿汉模式
  3. */
  4. public class SingletonV1 {
  5. private static SingletonV1 singletonV1 = new SingletonV1();
  6. private SingletonV1(){}
  7. public SingletonV1 getInstance() {
  8. return singletonV1;
  9. }
  10. }
  1. /**
  2. * 懒汉式模式
  3. */
  4. public class SingletonV2 {
  5. //懒汉式 当真正需要使用该对象的时候才会被初始化
  6. private static SingletonV2 singletonV2;
  7. private SingletonV2(){}
  8. /**
  9. * 线程安全问题 在多线程情况下 可能会被初始化多次
  10. *
  11. * @return
  12. */
  13. private static SingletonV2 getSingletonV2() {
  14. //当第一次singLetonV2等于null情况才会被初始化
  15. if (singletonV2 == null) {
  16. singletonV2 = new SingletonV2();
  17. }
  18. return new SingletonV2();
  19. }
  20. }
  1. /**
  2. * 双重检验锁
  3. */
  4. public class SingletonV2 {
  5. //懒汉式 当真正需要使用该对象的时候才会被初始化
  6. private static SingletonV2 singletonV2;
  7. private SingletonV2(){}
  8. /**
  9. * 线程安全问题 在多线程情况下 可能会被初始化多次
  10. *
  11. * @return
  12. */
  13. private static SingletonV2 getSingletonV2() {
  14. //当第一次singLetonV2等于null情况才会被初始化
  15. if (singletonV2 == null) {
  16. synchronized(SingletonV2.class){
  17. if (singletonV2 == null) {
  18. singletonV2 = new SingletonV2();
  19. }
  20. }
  21. }
  22. return new SingletonV2();
  23. }
  24. }

2.策略模式

策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理,最终可以实现解决多重if判断问题。
1.环境(Context)角色:持有一个Strategy的引用。
2.抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或者抽象类。此角色给所有的具体策略类所需的接口。
3.具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。
定义策略接口—->实现不同的策略类—->利用多态或其他方式调用策略
image.png

基于枚举+反射+工厂方式实现策略模式:
代码:

  1. public enum PayEnumStrategy {
  2. /**
  3. * 支付宝支付
  4. */
  5. ALI_PAY("com.mayikt.strategy.impl.AliPayStrategy"),
  6. /**
  7. * 银联支付
  8. */
  9. UNION_PAY("com.mayikt.strategy.impl.UnionPayStrategy");
  10. PayEnumStrategy(String className) {
  11. this.setClassName(className);
  12. }
  13. public String getClassName() {
  14. return className;
  15. }
  16. public void setClassName(String className) {
  17. this.className = className;
  18. }
  19. /**
  20. * class完整地址
  21. */
  22. private String className;
  23. }
  1. public interface PayStrategy {
  2. /**
  3. * 定义策略模式共同行为算法骨架
  4. * @return
  5. */
  6. public String toPayHtml();
  7. }
  1. @Component
  2. public class AliPayStrategy implements PayStrategy {
  3. @Override
  4. public String toPayHtml() {
  5. return "调用支付宝接口....";
  6. }
  7. }
  1. @Component
  2. public class PayContextStrategy {
  3. /**
  4. * 使用枚举+工厂方式实现策略模式
  5. *
  6. * @param payCode
  7. * @return
  8. */
  9. public String toPayEnumHtml(String payCode) {
  10. if (StringUtils.isEmpty(payCode)) {
  11. return "payCode不能为空!";
  12. }
  13. PayStrategy payStrategy = StrategyFactory.getPayStrategy(payCode);
  14. if (payStrategy == null) {
  15. return "没有查询到该渠道信息";
  16. }
  17. // 子类重写父类..
  18. return payStrategy.toPayHtml();
  19. }
  20. }
  1. @RestController
  2. public class StrategyController {
  3. @Autowired
  4. private PayContextStrategy payContextStrategy;
  5. @RequestMapping("/toPayHtml")
  6. public String toPayHtml(String payCode) {
  7. return payContextStrategy.toPayDbHtml(payCode);
  8. }
  9. }
  1. public class StrategyFactory {
  2. public static PayStrategy getPayStrategy(String strategyType) {
  3. try {
  4. // 1.获取枚举中className
  5. String className = PayEnumStrategy.valueOf(strategyType).getClassName();
  6. // 2.使用java反射技术初始化类
  7. return (PayStrategy) Class.forName(className).newInstance();
  8. } catch (Exception e) {
  9. return null;
  10. }
  11. }
  12. }

基于反射+数据库实现策略模式:直接通过数据库存入beanId的方式

  1. public interface PayStrategy {
  2. /**
  3. * 定义策略模式共同行为算法骨架
  4. * @return
  5. */
  6. public String toPayHtml();
  7. }
  1. @Component
  2. public class AliPayStrategy implements PayStrategy {
  3. @Override
  4. public String toPayHtml() {
  5. return "调用支付宝接口....";
  6. }
  7. }
  1. @Component
  2. public class PayContextStrategy {
  3. /**
  4. * 使用枚举+工厂方式实现策略模式
  5. *
  6. * @param payCode
  7. * @return
  8. */
  9. public String toPayEnumHtml(String payCode) {
  10. if (StringUtils.isEmpty(payCode)) {
  11. return "payCode不能为空!";
  12. }
  13. PaymentChannelEntity paymentChannel = paymentChannelMapper.getPaymentChannel(payCode);
  14. if(paymentChannel==null){
  15. return "没有查询到该渠道信息";
  16. }
  17. String strategyBeanId = paymentChannel.getStrategyBeanId();
  18. if(strategyBeanId==null){
  19. return "没有配置策略BeanId";
  20. }
  21. // 使用beanid从容器获取对象
  22. PayStrategy payStrategy= SpringUtils.getBean(strategyBeanId,PayStrategy.class);
  23. return payStrategy.toPayHtml();
  24. }
  25. }
  1. @RestController
  2. public class StrategyController {
  3. @Autowired
  4. private PayContextStrategy payContextStrategy;
  5. @RequestMapping("/toPayHtml")
  6. public String toPayHtml(String payCode) {
  7. return payContextStrategy.toPayDbHtml(payCode);
  8. }
  9. }
  1. public interface PaymentChannelMapper {
  2. @Select("\n" +
  3. "SELECT id as id ,CHANNEL_NAME as CHANNELNAME ,CHANNEL_ID as CHANNELID,strategy_bean_id AS strategybeanid\n" +
  4. "FROM payment_channel where CHANNEL_ID=#{payCode}")
  5. public PaymentChannelEntity getPaymentChannel(String payCode);
  6. }
  1. @Data
  2. public class PaymentChannelEntity {
  3. /** ID */
  4. private Integer id;
  5. /** 渠道名称 */
  6. private String channelName;
  7. /** 渠道ID */
  8. private String channelId;
  9. /**
  10. * 策略执行beanId
  11. */
  12. private String strategyBeanId;
  13. }


3.工厂模式

什么是工厂模式
实现了创建者和调用者分离,工厂模式分为简单工厂、工厂方法、抽象工厂模式

工厂模式好处
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。
利用工厂模式可以降低程序的耦合性,为后期的维护修改提供了很大的便利。
将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。

工厂模式分类
简单工厂模式:

简单工厂模式相当于是一个工厂中有各种产品,创建在一个类中,客户无需知道具体产品的名称,只需要知道产品类所对应的参数即可。但是工厂的职责过重,而且当类型过多时不利于系统的扩展维护。

image.png

  1. public interface Car {
  2. public void run();
  3. }
  4. public class AoDi implements Car {
  5. public void run() {
  6. System.out.println("我是奥迪汽车..");
  7. }
  8. }
  9. public class JiLi implements Car {
  10. public void run() {
  11. System.out.println("我是吉利汽车...");
  12. }
  13. }
  14. public class CarFactory {
  15. public static Car createCar(String name) {
  16. if (StringUtils.isEmpty(name)) {
  17. return null;
  18. }
  19. if(name.equals("奥迪")){
  20. return new AoDi();
  21. }
  22. if(name.equals("吉利")){
  23. return new JiLi();
  24. }
  25. return null;
  26. }
  27. }
  28. public class Client01 {
  29. public static void main(String[] args) {
  30. Car aodi =CarFactory.createCar("奥迪");
  31. Car jili =CarFactory.createCar("吉利");
  32. aodi.run();
  33. jili.run();
  34. }
  35. }

单工厂的优点/缺点

优点:简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。
缺点:很明显工厂类集中了所有实例的创建逻辑,容易违反GRASPR的高内聚的责任分配原则

工厂方法模式

image.png

工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

  1. public interface Car {
  2. public void run();
  3. }
  4. public class AoDi implements Car {
  5. public void run() {
  6. System.out.println("我是奥迪汽车..");
  7. }
  8. }
  9. public class JiLi implements Car {
  10. public void run() {
  11. System.out.println("我是吉利汽车...");
  12. }
  13. }
  14. public class JiLiFactory implements CarFactory {
  15. public Car createCar() {
  16. return new JiLi();
  17. }
  18. }
  19. public class AoDiFactory implements CarFactory {
  20. public Car createCar() {
  21. return new AoDi();
  22. }
  23. }
  24. public class Client {
  25. public static void main(String[] args) {
  26. Car aodi = new AoDiFactory().createCar();
  27. Car jili = new JiLiFactory().createCar();
  28. aodi.run();
  29. jili.run();
  30. }
  31. }

抽象工厂模式
抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品。

image.png

image.png

  1. //发动机
  2. public interface Engine {
  3. void run();
  4. void start();
  5. }
  6. class EngineA implements Engine {
  7. public void run() {
  8. System.out.println("转的快!");
  9. }
  10. public void start() {
  11. System.out.println("启动快,自动档");
  12. }
  13. }
  14. class EngineB implements Engine {
  15. public void run() {
  16. System.out.println("转的慢!");
  17. }
  18. public void start() {
  19. System.out.println("启动快,手动档");
  20. }
  21. }
  22. //座椅
  23. public interface Chair {
  24. void run();
  25. }
  26. class ChairA implements Chair{
  27. public void run() {
  28. System.out.println("可以自动加热!");
  29. }
  30. }
  31. class ChairB implements Chair{
  32. public void run() {
  33. System.out.println("不能加热!");
  34. }
  35. }
  36. public interface CarFactory {
  37. // 创建发动机
  38. Engine createEngine();
  39. // 创建座椅
  40. Chair createChair();
  41. }
  42. public class JiLiFactory implements CarFactory {
  43. public Engine createEngine() {
  44. return new EngineA();
  45. }
  46. public Chair createChair() {
  47. return new ChairA();
  48. }
  49. }
  50. public class Client002 {
  51. public static void main(String[] args) {
  52. CarFactory carFactory=new JiLiFactory();
  53. Engine engine=carFactory.createEngine();
  54. engine.run();
  55. engine.start();
  56. }
  57. }

简单工厂、工厂方法、抽象工厂之小结、区别
简单工厂 : 用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品) 抽象工厂 :用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)

4.代理模式

简介

通过代理控制对象的访问,可以详细访问某个对象的方法,在这个方法调用处理,或调用后处理。既(AOP微实现) ,AOP核心技术面向切面编程。

image.png

代理模式应用场景

SpringAOP、事物原理、日志打印、权限控制、远程调用、安全代理 可以隐蔽真实角色

代理的分类

静态代理(静态定义代理类)
动态代理(动态生成代理类)
Jdk自带动态代理
Cglib 、javaassist(字节码操作库)


静态代理

由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

静态代理代码:

  1. public interface IUserDao {
  2. void save();
  3. }
  4. public class UserDao implements IUserDao {
  5. public void save() {
  6. System.out.println("已经保存数据...");
  7. }
  8. }
  9. 代理类
  10. public class UserDaoProxy implements IUserDao {
  11. private IUserDao target;
  12. public UserDaoProxy(IUserDao iuserDao) {
  13. this.target = iuserDao;
  14. }
  15. public void save() {
  16. System.out.println("开启事物...");
  17. target.save();
  18. System.out.println("关闭事物...");
  19. }
  20. }

动态代理

简介:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理

JDK动态代理:

1)原理:是根据类加载器和接口创建代理类(此代理类是接口的实现类,所以必须使用接口 面向接口生成代理,位于java.lang.reflect包下)


2)实现方式:
1. 通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(…);
2. 通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
3. 通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4. 通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));

缺点:jdk动态代理,必须是面向接口,目标业务类必须实现接口

  1. // 每次生成动态代理类对象时,实现了InvocationHandler接口的调用处理器对象
  2. public class InvocationHandlerImpl implements InvocationHandler {
  3. private Object target;// 这其实业务实现类对象,用来调用具体的业务方法
  4. // 通过构造函数传入目标对象
  5. public InvocationHandlerImpl(Object target) {
  6. this.target = target;
  7. }
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  9. Object result = null;
  10. System.out.println("调用开始处理");
  11. result = method.invoke(target, args);
  12. System.out.println("调用结束处理");
  13. return result;
  14. }
  15. public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
  16. IllegalAccessException, IllegalArgumentException, InvocationTargetException {
  17. // 被代理对象
  18. IUserDao userDao = new UserDao();
  19. InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDao);
  20. ClassLoader loader = userDao.getClass().getClassLoader();
  21. Class<?>[] interfaces = userDao.getClass().getInterfaces();
  22. // 主要装载器、一组接口及调用处理动态代理实例
  23. IUserDao newProxyInstance = (IUserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl);
  24. newProxyInstance.save();
  25. }
  26. }

CGLIB动态代理:

原理:利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

使用cglib[Code Generation Library]实现动态代理,并不要求委托类必须实现接口,底层采用asm字节码生成框架生成代理类的字节码

相关代码:

  1. public class CglibProxy implements MethodInterceptor {
  2. private Object targetObject;
  3. // 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
  4. public Object getInstance(Object target) {
  5. // 设置需要创建子类的类
  6. this.targetObject = target;
  7. Enhancer enhancer = new Enhancer();
  8. enhancer.setSuperclass(target.getClass());
  9. enhancer.setCallback(this);
  10. return enhancer.create();
  11. }
  12. public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  13. System.out.println("开启事物");
  14. Object result = proxy.invoke(targetObject, args);
  15. System.out.println("关闭事物");
  16. // 返回代理对象
  17. return result;
  18. }
  19. public static void main(String[] args) {
  20. CglibProxy cglibProxy = new CglibProxy();
  21. UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDao());
  22. userDao.save();
  23. }
  24. }

CGLIB动态代理与JDK动态区别

java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
Spring中。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
JDK动态代理只能对实现了接口的类生成代理,而不能针对类 。 CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 。

性能问题:由于Cglib代理是利用ASM字节码生成框架在内存中生成一个需要被代理类的子类完成代理,而JDK动态代理是利用反射原理完成动态代理,所以Cglib创建的动态代理对象性能比JDk动态代理动态创建出来的代理对象新能要好的多,但是对象创建的速度比JDk动态代理要慢,所以,当Spring使用的是单例情况下可以选用Cglib代理,反之使用JDK动态代理更加合适。同时还有一个问题,被final修饰的类只能使用JDK动态代理,因为被final修饰的类不能被继承,而Cglib则是利用的继承原理实现代理的。
因为是继承,所以该类或方法最好不要声明成final ,final可以阻止继承和多态。