自己的网站源代码一片空白,聊城做网站比较不错的公司,wordpress怎么设置自己的模板,原则网站设计版式一、代理模式 在代理模式#xff08;Proxy Pattern#xff09;中#xff0c;一个类代表另一个类的功能#xff0c;这种类型的设计模式属于结构型模式。 代理模式通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介#xff0c;负责将客户端… 一、代理模式 在代理模式Proxy Pattern中一个类代表另一个类的功能这种类型的设计模式属于结构型模式。 代理模式通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介负责将客户端的请求转发给目标对象同时可以在转发请求前后进行额外的处理。 在代理模式中我们创建具有现有对象的对象以便向外界提供功能接口。 介绍 意图 为其他对象提供一种代理以控制对这个对象的访问。 主要解决的问题 代理模式解决的是在直接访问某些对象时可能遇到的问题例如对象创建成本高、需要安全控制或远程访问等。 使用场景 当需要在访问一个对象时进行一些控制或额外处理时。 实现方式 增加中间层创建一个代理类作为真实对象的中间层。代理与真实对象组合代理类持有真实对象的引用并在访问时进行控制。 关键代码 代理类实现与真实对象相同的接口并添加额外的控制逻辑。真实对象实际执行任务的对象。 应用实例 快捷方式Windows系统中的快捷方式作为文件或程序的代理。角色扮演孙悟空作为高翠兰的代理猪八戒无法区分。代售点购买火车票时代售点作为火车站的代理。支票作为银行账户资金的代理控制资金的访问。Spring AOP使用代理模式来实现面向切面编程。 优点 职责分离代理模式将访问控制与业务逻辑分离。扩展性可以灵活地添加额外的功能或控制。智能化可以智能地处理访问请求如延迟加载、缓存等。 缺点 性能开销增加了代理层可能会影响请求的处理速度。实现复杂性某些类型的代理模式实现起来可能较为复杂。 使用建议 根据具体需求选择合适的代理类型如远程代理、虚拟代理、保护代理等。确保代理类与真实对象接口一致以便客户端透明地使用代理。 注意事项 与适配器模式的区别适配器模式改变接口而代理模式不改变接口。与装饰器模式的区别装饰器模式用于增强功能代理模式用于控制访问。 结构 主要涉及到以下几个核心角色 抽象主题Subject: 定义了真实主题和代理主题的共同接口这样在任何使用真实主题的地方都可以使用代理主题。 真实主题Real Subject: 实现了抽象主题接口是代理对象所代表的真实对象。客户端直接访问真实主题但在某些情况下可以通过代理主题来间接访问。 代理Proxy: 实现了抽象主题接口并持有对真实主题的引用。代理主题通常在真实主题的基础上提供一些额外的功能例如延迟加载、权限控制、日志记录等。 客户端Client: 使用抽象主题接口来操作真实主题或代理主题不需要知道具体是哪一个实现类。 什么是代理模式 代理模式给某一个对象提供一个代理对象并由代理对象控制对原对象的引用。 通俗的来讲代理模式就是我们生活中常见的中介。举个例子来说明假如说我现在想买一辆二手车虽然我可以自己去找车源做质量检测等一系列的车辆过户流程但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢于是我就通过中介公司来买车他们来给我找车源帮我办理车辆过户流程我只是负责选择自己喜欢的车然后付钱就可以了 为什么要用代理模式 中介隔离作用在某些情况下一个客户类不想或者不能直接引用一个委托对象而代理类对象可以在客户类和委托对象之间起到中介的作用其特征是代理类和委托类实现相同的接口。开闭原则增加功能代理类除了是客户类和委托类的中介之外我们还可以通过给代理 类增加额外的功能来扩展委托类的功能这样做我们只需要修改代理类而不需要再修改委托类符合代码设计的开闭原则。 有哪几种代理模式 我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话 可以分为两种静态代理: 静态代理是由程序员创建或特定工具自动生成源代码在对其编译。在程序员运行之前代理类.class文件就已经被创建了。动态代理:动态代理是在程序运行时通过反射机制动态创建的。 动态代理分为: 基于接口的动态代理jdk自带基于子类的动态代理第三方 1. 静态代理 实现 创建一个接口。 public interface IWomen {public void makeEyesWithMan();//抛媚眼public void happyWithMan();//开心
} 创建实现接口的实体类 public class PanJinLianImp implements IWomen{public void makeEyesWithMan() {System.out.println(回眸一笑抛个媚眼);}public void happyWithMan() {System.out.println(嘿嘿嘿老娘来了~~~);}
} 代理 //代理 中间商
public class WangPoImp implements IWomen{//被代理IWomen iWomen;public WangPoImp() {}public WangPoImp(IWomen iWomen) {this.iWomen iWomen;}public void makeEyesWithMan() {System.out.println(斟一壶酒搞搞气氛);iWomen.makeEyesWithMan();}public void happyWithMan() {iWomen.happyWithMan();}
} 请求 public class XiMenQing {public static void main(String[] args) {//1.目标(被代理对象)PanJinLianImp panJinLianImp new PanJinLianImp();//2.代理WangPoImp wangPoImp new WangPoImp(panJinLianImp);wangPoImp.makeEyesWithMan();wangPoImp.happyWithMan();}
}2. 基于接口的动态代理jdk自带 基于接口的动态代理 特点字节码随用随创建随用随加载作用不修改源码的基础上对方法增强 涉及的类Proxy 提供者JDK官方如何创建代理对象 使用Proxy类中的newProxyInstance方法创建代理对象的要求 被代理类最少实现一个接口如果没有则不能使用newProxyInstance方法的参数 ClassLoader类加载器 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。 Class[]字节码数组 它是用于让代理对象和被代理对象有相同方法。固定写法。InvocationHandler用于提供增强的代码 实现 接口 public interface ISinger {public void sing();public void rap();
} 实现类 public class CaiXuKunImp implements ISinger{public void sing() {System.out.println(鸡你太美);}public void rap() {System.out.println(rap);}
} 测试 public class Test01 {public static void main(String[] args) {//1.被代理对象final CaiXuKunImp caiXuKunImp new CaiXuKunImp();/*** 基于接口的动态代理* 特点字节码随用随创建随用随加载* 作用不修改源码的基础上对方法增强* 涉及的类Proxy* 提供者JDK官方* 如何创建代理对象* 使用Proxy类中的newProxyInstance方法* 创建代理对象的要求* 被代理类最少实现一个接口如果没有则不能使用* newProxyInstance方法的参数* ClassLoader类加载器* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。* Class[]字节码数组* 它是用于让代理对象和被代理对象有相同方法。固定写法。* InvocationHandler用于提供增强的代码**/ISinger jinjinren (ISinger) Proxy.newProxyInstance(caiXuKunImp.getClass().getClassLoader(), caiXuKunImp.getClass().getInterfaces(), new InvocationHandler() {/**** Object proxy》被代理对象的引用(蔡徐坤对象)* Method method》执行的方法(蔡徐坤唱歌方法)* Object[] args》执行方法的参数(蔡徐坤唱歌方法的参数)* Object》执行方法的返回值(蔡徐坤唱歌方法的返回值)* *//*** 作用执行被代理对象的任何接口方法都会经过该方法* 方法参数的含义* proxy 代理对象的引用* method 当前执行的方法* args 当前执行方法所需的参数* Object 和被代理对象方法有相同的返回值*/Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(跳一段舞);Object obj method.invoke(caiXuKunImp, args);//代表调用被代理对象的核心方法System.out.println(打一个篮球);return obj;}});//3.唱歌jinjinren.sing();System.out.println(***********************);jinjinren.rap();}
}3. 基于子类的动态代理 基于子类的动态代理 涉及的类Enhancer提供者第三方cglib库开发环境:添加cglib依赖坐标 如何创建代理对象 使用Enhancer类中的create方法创建代理对象的要求 被代理类不能是最终类create方法的参数 Class字节码 它是用于指定被代理对象的字节码。 Callback用于提供增强的代码 它是让我们写如何代理。我们一般都是些一个该接口的实现类通常情况下都是匿名内部类但不是必须的。 此接口的实现类都是谁用谁写。我们一般写的都是该接口的子接口实现类MethodInterceptor 实现 注入 dependenciesdependencygroupIdcglib/groupIdartifactIdcglib/artifactIdversion2.1_3/version/dependency/dependencies 接口 public interface ISinger {public void sing();
} public class ZhouShenImp implements ISinger{Overridepublic void sing() {System.out.println(大鱼海棠);}
}public class Test01 {public static void main(String[] args) {1.被代理对象final ISinger zhouShenImp new ZhouShenImp();ISinger jingjiren (ISinger) Enhancer.create(zhouShenImp.getClass(), new InvocationHandler() {/*** 2.创建代理对象* 参数1被代理对象的字节码* 参数2InvocationHandler* *//*** 执行被代理对象的任何方法都会经过该方法* param proxy* param method* param args* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的* param methodProxy 当前执行方法的代理对象* return* throws Throwable*/Overridepublic Object invoke(Object o, Method method, Object[] objects) throws Throwable {System.out.println(找一下蔡徐坤);Object obj method.invoke(zhouShenImp,objects);System.out.println(和蔡徐坤一起基太美);return obj;}});//3.唱歌jingjiren.sing();}
} 二、Spring1案例补充
1. 常规实现 在Spring1中的基础上 实现 转钱功能 xml实现 项目状态成功提交失败不能做到一个业务方法一起回滚 分析原因项目存在事务自动管理且自动管理在dao层实现解决方案事务管理未来一定是在service层实现 方案思考 1.dao层不在进行事务管理自动事务提交关闭 2.业务类的每个业务方法中的多个dao操作公用同一个连接connection对象 3.ThreadLocal dao层不变 service 接口新增 public void transfer(String sourceName,String targetName,int money); 实现类 重写该方法 Overridepublic void transfer(String sourceName, String targetName, int money) {try {//开启事务transactionUtil.beginTx();Account sourceAccount dao.findByName(sourceName);Account targetAccount dao.findByName(targetName);sourceAccount.setAmoney(sourceAccount.getAmoney()-money);targetAccount.setAmoney(targetAccount.getAmoney()money);dao.updateById(sourceAccount);int a 10/0;//模拟异常dao.updateById(targetAccount);//提交事务transactionUtil.commitTx();} catch (Exception e) {//回滚事务transactionUtil.rollbackTx();e.printStackTrace();} finally {//关闭事务transactionUtil.closeTx();}} controller同理 public void transfer(String sourceName,String targetName,int money);Overridepublic void transfer(String sourceName, String targetName, int money) {service.transfer(sourceName,targetName,money);} util ConnectionUtil public class ConnectionUtil {//线程区域对象ThreadLocalConnection connectionThreadLocal new ThreadLocalConnection();//数据源DataSource dataSource;public void setDataSource(DataSource dataSource) {this.dataSource dataSource;}//获取连接public Connection createConn() {try {//在口袋里面找连接Connection connection connectionThreadLocal.get();//判断if (connectionnull){connectiondataSource.getConnection();connectionThreadLocal.set(connection);}return connection;} catch (SQLException throwables) {throwables.printStackTrace();}return null;}//关闭连接public void closeConn(){connectionThreadLocal.remove();//解绑}
}TransactionUtil public class TransactionUtil {//专配连接工具类ConnectionUtil connectionUtil;public void setConnectionUtil(ConnectionUtil connectionUtil) {this.connectionUtil connectionUtil;}//开启public void beginTx() {try {connectionUtil.createConn().setAutoCommit(false);} catch (SQLException throwables) {throwables.printStackTrace();}}//提交public void commitTx() {try {connectionUtil.createConn().commit();} catch (SQLException throwables) {throwables.printStackTrace();}}//回滚public void rollbackTx() {try {connectionUtil.createConn().rollback();} catch (SQLException throwables) {throwables.printStackTrace();}}//关闭public void closeTx() {try {connectionUtil.createConn().close();connectionUtil.closeConn();} catch (SQLException throwables) {throwables.printStackTrace();}}
} 同时需要在service 实现类装配事务工具类 //装配事务工具类TransactionUtil transactionUtil;public void setTransactionUtil(TransactionUtil transactionUtil) {this.transactionUtil transactionUtil;} dao层装配连接工具类 //装配连接工具类ConnectionUtil connectionUtil;public void setConnectionUtil(ConnectionUtil connectionUtil) {this.connectionUtil connectionUtil;} 在xml文件中注入 !-- 注入连接工具类 --bean idconnectionUtil classcom.zkt.util.ConnectionUtilproperty namedataSource refdataSource//bean!-- 注入事务工具类 --bean idtransactionUtil classcom.zkt.util.TransactionUtilproperty nameconnectionUtil refconnectionUtil//bean!-- 注入dao --bean iddao classcom.zkt.dao.AccountDaoImpproperty namequeryRunner refqueryRunner/property nameconnectionUtil refconnectionUtil//bean!-- 注入service --bean idservice classcom.zkt.service.AccountServiceImpproperty namedao refdao/property nametransactionUtil reftransactionUtil//bean 2. 代理实现 在util下创建代理 public class ProxyFactory {//被代理对象装配IAccountService toServiceProxy;public void setToServiceProxy(IAccountService toServiceProxy) {this.toServiceProxy toServiceProxy;}//装配事务TransactionUtil transactionUtil;public void setTransactionUtil(TransactionUtil transactionUtil) {this.transactionUtil transactionUtil;}//创建代理public IAccountService createProxy(){IAccountService service (IAccountService) Proxy.newProxyInstance(toServiceProxy.getClass().getClassLoader(), toServiceProxy.getClass().getInterfaces(), new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object obj null;try {transactionUtil.beginTx();obj method.invoke(toServiceProxy, args);transactionUtil.commitTx();} catch (Exception e) {e.printStackTrace();transactionUtil.rollbackTx();} finally {transactionUtil.closeTx();}return obj;}});return service;}
}事务工具类不变 连接工具类 public class ConnectionUtil {//线程区域对象ThreadLocalConnection connectionThreadLocal new ThreadLocalConnection();//数据源DataSource dataSource;public void setDataSource(DataSource dataSource) {this.dataSource dataSource;}//获取连接public Connection createConn() {try {//在口袋里面找连接Connection connection connectionThreadLocal.get();//判断if (connectionnull){connectiondataSource.getConnection();connectionThreadLocal.set(connection);}return connection;} catch (SQLException throwables) {throwables.printStackTrace();}return null;}//关闭连接public void closeConn(){connectionThreadLocal.remove();//解绑}
}故service的transfer 正常写就行 Overridepublic void transfer(String sourceName, String targetName, int money) {Account sourceAccount dao.findByName(sourceName);Account targetAccount dao.findByName(targetName);sourceAccount.setAmoney(sourceAccount.getAmoney() - money);targetAccount.setAmoney(targetAccount.getAmoney() money);dao.updateById(sourceAccount);
// int a 10 / 0;//模拟异常dao.updateById(targetAccount);} 最后 在xml文件中 注入就行 !-- 注入service(被代理对象) --bean idservice classcom.zkt.service.AccountServiceImpproperty namedao refdao//bean!-- 注入service(代理对象) --bean idproxyService classcom.zkt.service.AccountServiceImp factory-beanfactory factory-methodcreateProxy/bean idfactory classcom.zkt.util.ProxyFactoryproperty nametransactionUtil reftransactionUtil/property nametoServiceProxy refservice//bean!-- 注入controller(消费者) --bean idcontroller classcom.zkt.controller.AccountControllerImpproperty nameservice refproxyService//bean 其余不变 即可实现 三、AOP AOP 为 Aspect Oriented Programming 的缩写意思为面向切面编程是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术 AOP 是 OOP 的延续是软件开发中的一个热点也是Spring框架中的一个重要内容 是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离从而使得 业务逻辑各部分之间的耦合度降低提高程序的可重用性同时提高了开发的效率。 AOP 的作用及其优势 作用在程序运行期间在不修改源码的情况下对方法进行功能增强 优势减少重复代码提高开发效率并且便于维护 AOP 的应用 1.日志2.事务3.权限 Spring AOP 基于动态代理实现 如果被代理的对象已经实现某个接口则 Spring AOP 会使用 JDK Proxy反射基于接口的方式创建代理对象JDK动态代理的核心是InvocationHandler接口和Proxy类如果被代理的对象没有实现某个接口就无法使用 JDK Proxy 去进行代理了这时候Spring AOP 会使用 Cglib基于继承的方式生成一个被代理对象的子类来作为代理Cglib动态代理的核心是MethodInterceptor接口和Enhancer类; AOP术语 AOP通知类型 AOP将抽取出来的共性功能称为通知;通知类型:以通知在上下文中的具体位置作为划分 前置通知Before 后置通知After 返回通知After-returning 异常通知After-throwing 环绕通知Around AOP连接点(Join point) AOP将所有的方法都视为连接点不管是接口里面的抽象方法,还是实现类里面的重写方法都是连接点 AOP切点(Pointcut) AOP将可能被抽取共性功能的方法称为切入点。切入点是连接点的子集 AOP目标对象(Target): 就是挖掉功能的方法对应的类生的对象这种对象是无法直接完成最终工作的 AOP织入(Weaving): 就是将挖掉的功能回填的动态过程 AOP切面: 切点通知 实现步骤: 1.添加依赖aop与aspectj表达式的依赖2.创建spring的主配置文件bean内的命名空间要添加aop的3.创建业务代码并编写日志记录代码(事务管理代码)4.将业务层与日志记录层注入spring容器5.aop:config--aop配置 aop:aspect--aop切面 aop:before--通知内容与通知类型 切点表达式配置语法 execution(修饰符 返回值 包名称.类名称.方法名称(参数列表)) eg: execution(public void com.apesource.service.ServiceImp.findAll())1.修饰符可以省略代表任意 execution(返回值 包名称.类名称.方法名称(参数列表))2.返回值可以使用“*”代表任意 execution(* 包名称.类名称.方法名称(参数列表))3.包名可以使用“*”代表任意名称 execution(* *.*.*.类名称.方法名称(参数列表))4.包名可以使用“..”代表任意个数 execution(* *...类名称.方法名称(参数列表))5.类名与方法名可以使用“*”代表任意 execution(* *...*.*(参数列表))6.参数列表可以使用..代表任意个数任意类型 execution(* *...*.*(..)) 如果有参数 intint Stringjava.lang.String xml实现日志记录 poi.xml 导入坐标 AOP 不需要到导 因为context包含了 dependencygroupIdorg.aspectj/groupIdartifactIdaspectjweaver/artifactIdversion1.8.13/version/dependency service public interface IAccountService {public void save(int i);public void update();public int delete();
}public class AccountServiceImp implements IAccountService {public void save(int i) {System.out.println(业务层的新增方法: i);}public void update() {System.out.println(业务层的修改方法);
// int a 10/0;}public int delete() {System.out.println(业务层的删除方法);return 0;}
} util public class Logger {public void beforeMethod() {System.out.println(日志类logger前置通知);}public void returnMethod() {System.out.println(日志类logger返回通知);}public void throwMethod() {System.out.println(日志类logger异常通知);}public void afterMethod() {System.out.println(日志类logger后置通知);}
} applicationContext.xml !-- 注入业务层 --bean idaccountServiceImp classcom.zkt.service.AccountServiceImp/!-- 注入日志记录层通知 --bean idlogger classcom.zkt.util.Logger/!-- 配置AOP --aop:config!-- 配置切面 --aop:aspect idaopAspect reflogger!-- 切点 --aop:pointcut iddian expressionexecution(* com.zkt.service.AccountServiceImp.* (..))/!-- 通知 --aop:before methodbeforeMethod pointcut-refdian/aop:after-returning methodreturnMethod pointcut-refdian/aop:after-throwing methodthrowMethod pointcut-refdian/aop:after methodafterMethod pointcut-refdian//aop:aspect/aop:config 测试 RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(locations classpath:applicationContext.xml)
public class Test01 {Autowiredpublic IAccountService service;Testpublic void show1(){service.update();
// System.out.println();
// service.save(1);
// System.out.println();
// service.delete();}
} 注解实现日志记录 导入坐标同理 加入注解 util使用环绕通知实现 Component
Aspect//切面
public class Logger {Pointcut(execution(* com.zkt.service.AccountServiceImp.* (..)))public void dian() {}
// Before(dian())
// public void beforeMethod() {
// System.out.println(日志类logger前置通知);
// }
//
// AfterReturning(dian())
// public void returnMethod() {
// System.out.println(日志类logger返回通知);
// }
//
// AfterThrowing(dian())
// public void throwMethod() {
// System.out.println(日志类logger异常通知);
// }
//
// After(dian())
// public void afterMethod() {
// System.out.println(日志类logger后置通知);
// }//环绕通知Around(dian())public Object AroundLogger(ProceedingJoinPoint pjp) {Object returnobj null;//保存主业务方法的返回值try {//1.前置通知System.out.println(环绕通知》前置通知);Object[] objs pjp.getArgs();//主业务方法的参数returnobj pjp.proceed(objs);//调用主业务方法//3.后置通知System.out.println(环绕通知》返回通知);} catch (Throwable tw) {//4.异常通知System.out.println(环绕通知》异常通知);} finally {//5.最终通知System.out.println(环绕通知》后置通知);}return returnobj;}
}测试 异常情况 正常情况 四、面试题
16. Spring框架中的Bean生命周期 spring bean的生命周期总体分四个阶段实例化属性注入 初始化销毁step1 实例化bean根据配置文件中 bean 的定义利用 java reflection 反射技术创建 bean的实例step2 注入对象依赖的属性值或对象step3 处理各种Aware接口spring 会检测该 bean 是否实现了 xxxaware 接口通过Aware类型的接口可以让spring框架为当前 bean 注入相应的内容 如果 bean 实现 beannameaware 接口会调用它实现的 setbeanname(string beanid方 法注入 bean 的名字 如果 bean 实现 beanclassloaderaware 接口调用 setbeanclassloader方法注入classloader 对象的实例如果 bean 实现 beanfactoryaware 接口会调用它实现的 setbeanfactory方法注入的是 spring工厂。如果 bean 实现 applicationcontextaware 接口会调用 setapplicationcontext方法注入 spring 上下文step4 执行BeanPostProcessor前置处理如果想对 bean 进行一些自定义的前置处理那么可以让 bean 实现了 beanpostprocessor 接口将会在该阶段调用 postprocessbeforeinitialization(object objstring sStep5 执行initializingbean初始化方法如果 bean 实现了 InitializingBean接口执行afeterPropertiesSet() 方法step6 执行init-method自定义初始化方法如果 bean 在 spring 配置文件中配置了 init-method 属性则会自动调用其配置的初始化方法step7 执行beanpostprocessor后置处理如果这个 bean 实现了beanpostprocessor接口 用 postprocessAfterInitialization(object obstring s方法由于这个方法是在 bean初始化结束后调用 以上几个步骤完成后 bean 已经被正确创建可以正常使用这个bean了 step8 执行disposablebean销毁bean当 bean 不再需要时会经过清理阶段如果 bean 实现了 disposablebean 这个接口会调用其实现的 destroy(方法执行销毁step9 执行destroy-method自定义初始化方法如果 bean 在 spring 配置文件中配置了 destroy-method 属性则会自动调用其配置的初始化方法 17. spring 框架如何解决循环依赖? 循环依赖问题是指∶ 类与类之间的依赖关系形成了闭环就会导致循环依赖问题的产生。 Spring使用三级缓存来解决循环依赖 18. Spring 框架中有哪些注解? 用于声明 bean 的注解 Component定义通用bean的注解可标注任意类为 bean。如果一个 bean 不知道属于 哪个层可以使用 component 注解标注Repository定义数据访问层bean的注解Service定义业务层bean的注解Controller定义控制器bean的注解 用于注入的注解 Autowired按类型自动注入Qualifier按名称自动注入 声明配置、扫描、启用特性的注解 Configuration声明配置类ComponentScan组件扫描EnablesScheduling启用任务调度EnableAspectJAutoProxy启用自动代理工厂 19. Spring 框架中用到的设计模式