1.前言在平时的开发中涉及到设计模式的有两块内容第一个是我们平时使用的框架比如spring、mybatis等第二个是我们自己开发业务使用的设计模式。面试官一般比较关心的是我们在开发过程中有没有使用过设计模式或者在简历上写了关于设计模式的描述那么这样我们就需要重点关心自己开发中用过的设计模式。在平时的业务开发中其实真正使用设计模式的场景并不多虽然设计号称有23种之多不同的纬度可能会更多但是在项目最常使用的也就几种而已在面试的过程中我们主要介绍一种或两种就可以重点要说的是在什么业务场景下使用了设计模式什么设计模式这次面试部分我们主要介绍三种设计模式工厂方法模式简单工厂、工厂方法、抽象工厂策略模式责任链模式2.工厂方法模式2.1 概述需求设计一个咖啡店点餐系统。设计一个咖啡类Coffee并定义其两个子类美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】再设计一个咖啡店类CoffeeStore咖啡店具有点咖啡的功能。具体类的设计如下1.类图中的符号表示public-表示private#表示protected2.泛化关系(继承)用带空心三角箭头的实线来表示3.依赖关系使用带箭头的虚线来表示package com.itheima.factory.simple; public class CoffeeStore { public static void main(String[] args) { Coffee coffee orderCoffee(latte); System.out.println(coffee.getName()); } public static Coffee orderCoffee(String type){ Coffee coffee null; if(american.equals(type)){ coffee new AmericanCoffee(); }else if (latte.equals(type)){ coffee new LatteCoffee(); } //添加配料 coffee.addMilk(); coffee.addSuqar(); return coffee; } }在java中万物皆对象这些对象都需要创建如果创建的时候直接new该对象就会对该对象耦合严重假如我们要更换对象所有new对象的地方都需要修改一遍这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象我们就只和工厂打交道就可以了彻底和对象解耦如果要更换对象直接在工厂里更换该对象即可达到了与对象解耦的目的所以说工厂模式最大的优点就是解耦。开闭原则对扩展开放对修改关闭。在程序需要进行拓展的时候不能去修改原有的代码实现一个热插拔的效果。简言之是为了使程序的扩展性好易于维护和升级。三种工厂简单工厂模式工厂方法模式抽象工厂模式2.2 简单工厂模式简单工厂不是一种设计模式反而比较像是一种编程习惯。2.2.1 结构简单工厂包含如下角色抽象产品 定义了产品的规范描述了产品的主要特性和功能。具体产品 实现或者继承抽象产品的子类具体工厂 提供了创建产品的方法调用者通过该方法来获取产品。2.2.2 实现现在使用简单工厂对上面案例进行改进类图如下工厂类代码如下public class SimpleCoffeeFactory { public Coffee createCoffee(String type) { Coffee coffee null; if(americano.equals(type)) { coffee new AmericanoCoffee(); } else if(latte.equals(type)) { coffee new LatteCoffee(); } return coffee; } }咖啡店package com.itheima.factory.simple; public class CoffeeStore { public Coffee orderCoffee(String type){ //通过工厂获得对象不需要知道对象实现的细节 SimpleCoffeeFactory factory new SimpleCoffeeFactory(); Coffee coffee factory.createCoffee(type); //添加配料 coffee.addMilk(); coffee.addSuqar(); return coffee; } }工厂factory处理创建对象的细节一旦有了SimpleCoffeeFactoryCoffeeStore类中的orderCoffee()就变成此对象的客户后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合同时又产生了新的耦合CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合工厂对象和商品对象的耦合。后期如果再加新品种的咖啡我们势必要需求修改SimpleCoffeeFactory的代码违反了开闭原则。工厂类的客户端可能有很多比如创建美团外卖等这样只需要修改工厂类的代码省去其他的修改操作。2.2.3 优缺点优点封装了创建对象的过程可以通过参数直接获取对象。把对象的创建和业务逻辑层分开这样以后就避免了修改客户代码如果要实现新产品直接修改工厂类而不需要在原代码中修改这样就降低了客户代码修改的可能性更加容易扩展。缺点增加新产品时还是需要修改工厂类的代码违背了“开闭原则”。2.2.4 扩展静态工厂在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的这个就是静态工厂模式它也不是23种设计模式中的。代码如下public class SimpleCoffeeFactory { public static Coffee createCoffee(String type) { Coffee coffee null; if(americano.equals(type)) { coffee new AmericanoCoffee(); } else if(latte.equals(type)) { coffee new LatteCoffee(); } return coffe; } }2.3 工厂方法模式针对上例中的缺点使用工厂方法模式就可以完美解决完全遵循开闭原则。2.3.1 概念定义一个用于创建对象的接口让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。2.3.2 结构工厂方法模式的主要角色抽象工厂Abstract Factory提供了创建产品的接口调用者通过它访问具体工厂的工厂方法来创建产品。具体工厂ConcreteFactory主要是实现抽象工厂中的抽象方法完成具体产品的创建。抽象产品Product定义了产品的规范描述了产品的主要特性和功能。具体产品ConcreteProduct实现了抽象产品角色所定义的接口由具体工厂来创建它同具体工厂之间一一对应。2.3.3 实现使用工厂方法模式对上例进行改进类图如下流程代码如下抽象工厂public interface CoffeeFactory { Coffee createCoffee(); }具体工厂public class LatteCoffeeFactory implements CoffeeFactory { public Coffee createCoffee() { return new LatteCoffee(); } } public class AmericanCoffeeFactory implements CoffeeFactory { public Coffee createCoffee() { return new AmericanCoffee(); } }咖啡店类public class CoffeeStore { private CoffeeFactory factory; public CoffeeStore(CoffeeFactory factory) { this.factory factory; } public Coffee orderCoffee(String type) { Coffee coffee factory.createCoffee(); coffee.addMilk(); coffee.addsugar(); return coffee; } }从以上的编写的代码可以看到要增加产品类时也要相应地增加工厂类不需要修改工厂类的代码了这样就解决了简单工厂模式的缺点。工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性工厂方法模式保持了简单工厂模式的优点而且克服了它的缺点。2.3.4 优缺点优点用户只需要知道具体工厂的名称就可得到所要的产品无须知道产品的具体创建过程在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类无须对原工厂进行任何修改满足开闭原则缺点每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类这增加了系统的复杂度。2.4 抽象工程模式前面介绍的工厂方法模式中考虑的是一类产品的生产如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。这些工厂只生产同种类产品同种类产品称为同等级产品也就是说工厂方法模式只考虑生产同等级的产品但是在现实生活中许多工厂是综合型的工厂能生产多等级种类 的产品如电器厂既生产电视机又生产洗衣机或空调大学既有软件专业又有生物专业等。本节要介绍的抽象工厂模式将考虑多等级产品的生产将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族下图所示产品族一个品牌下面的所有产品例如华为下面的电脑、手机称为华为的产品族产品等级多个品牌下面的同种产品例如华为和小米都有手机电脑为一个产品等级2.4.1 概念是一种为访问类提供一个创建一组相关或相互依赖对象的接口且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。抽象工厂模式是工厂方法模式的升级版本工厂方法模式只生产一个等级的产品而抽象工厂模式可生产多个等级的产品。一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂2.4.2 结构抽象工厂模式的主要角色如下抽象工厂Abstract Factory提供了创建产品的接口它包含多个创建产品的方法可以创建多个不同等级的产品。具体工厂Concrete Factory主要是实现抽象工厂中的多个抽象方法完成具体产品的创建。抽象产品Product定义了产品的规范描述了产品的主要特性和功能抽象工厂模式有多个抽象产品。具体产品ConcreteProduct实现了抽象产品角色所定义的接口由具体工厂来创建它 同具体工厂之间是多对一的关系。2.4.3 实现现咖啡店业务发生改变不仅要生产咖啡还要生产甜点同一个产品等级产品分类咖啡拿铁咖啡、美式咖啡甜点提拉米苏、抹茶慕斯同一个风味就是同一个产品族相当于同一个品牌美式风味美式咖啡、抹茶慕斯意大利风味拿铁咖啡、提拉米苏要是按照工厂方法模式需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类很容易发生类爆炸情况。所以这个案例可以使用抽象工厂模式实现。类图如下实现关系使用带空心三角箭头的虚线来表示整体调用思路2.4.4 优缺点优点当一个产品族中的多个对象被设计成一起工作时它能保证客户端始终只使用同一个产品族中的对象。缺点当产品族中需要增加一个新的产品时所有的工厂类都需要进行修改。2.4.5 使用场景当需要创建的对象是一系列相互关联或相互依赖的产品族时如电器工厂中的电视机、洗衣机、空调等。系统中有多个产品族但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。系统中提供了产品的类库且所有产品的接口相同客户端不依赖产品实例的创建细节和内部结构。如输入法换皮肤一整套一起换。生成不同操作系统的程序。3.策略模式3.1 概述先看下面的图片我们去旅游选择出行模式有很多种可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。作为一个程序猿开发需要选择一款开发工具当然可以进行代码开发的工具有很多可以选择Idea进行开发也可以使用eclipse进行开发也可以使用其他的一些开发工具。定义该模式定义了一系列算法并将每个算法封装起来使它们可以相互替换且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式它通过对算法进行封装把使用算法的责任和算法的实现分割开来并委派给不同的对象对这些算法进行管理。3.2 结构策略模式的主要角色如下抽象策略Strategy类这是一个抽象角色通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。具体策略Concrete Strategy类实现了抽象策略定义的接口提供具体的算法实现或行为。环境Context类持有一个策略类的引用最终给客户端调用。3.3 案例实现【例】促销活动一家百货公司在定年度的促销活动。针对不同的节日春节、中秋节、圣诞节推出不同的促销活动由促销员将促销活动展示给客户。类图如下聚合关系可以用带空心菱形的实线来表示代码如下定义百货公司所有促销活动的共同接口public interface Strategy { void show(); }定义具体策略角色Concrete Strategy每个节日具体的促销活动//为春节准备的促销活动A public class StrategyA implements Strategy { public void show() { System.out.println(买一送一); } } //为中秋准备的促销活动B public class StrategyB implements Strategy { public void show() { System.out.println(满200元减50元); } } //为圣诞准备的促销活动C public class StrategyC implements Strategy { public void show() { System.out.println(满1000元加一元换购任意200元以下商品); } }定义环境角色Context用于连接上下文即把促销活动推销给客户这里可以理解为销售员public class SalesMan { //持有抽象策略角色的引用 private Strategy strategy; public SalesMan(Strategy strategy) { this.strategy strategy; } //向客户展示促销活动 public void salesManShow(){ strategy.show(); } }3.3.1 综合案例下图是gitee的登录的入口其中有多种方式可以进行登录用户名密码登录短信验证码登录微信登录QQ登录....3.3.2 目前已实现的代码(1)登录接口说明请求方式POST路径/api/user/login参数LoginReq返回值LoginResp请求参数LoginReqData public class LoginReq { private String name; private String password; private String phone; private String validateCode;//手机验证码 private String wxCode;//用于微信登录 /** * account : 用户名密码登录 * sms : 手机验证码登录 * we_chat : 微信登录 */ private String type; }响应参数LoginRespData public class LoginResp{ private Integer userId; private String userName; private String roleCode; private String token; //jwt令牌 private boolean success; }控制层LoginControllerRestController RequestMapping(/api/user) public class LoginController { Autowired private UserService userService; PostMapping(/login) public LoginResp login(RequestBody LoginReq loginReq){ return userService.login(loginReq); } }业务层UserServiceService public class UserService { public LoginResp login(LoginReq loginReq){ if(loginReq.getType().equals(account)){ System.out.println(用户名密码登录); //执行用户密码登录逻辑 return new LoginResp(); }else if(loginReq.getType().equals(sms)){ System.out.println(手机号验证码登录); //执行手机号验证码登录逻辑 return new LoginResp(); }else if (loginReq.getType().equals(we_chat)){ System.out.println(微信登录); //执行用户微信登录逻辑 return new LoginResp(); } LoginResp loginResp new LoginResp(); loginResp.setSuccess(false); System.out.println(登录失败); return loginResp; } }注意我们重点讲的是设计模式并不是登录的逻辑所以以上代码并没有真正的实现登录功能(2)问题分析业务层代码大量使用到了if...else在后期阅读代码的时候会非常不友好大量使用if...else性能也不高如果业务发生变更比如现在新增了QQ登录方式这个时候需要修改业务层代码违反了开闭原则解决使用工厂方法设计模式策略模式解决3.3.3 代码改造工厂策略1整体思路改造之后不在service中写业务逻辑让service调用工厂然后通过service传递不同的参数来获取不同的登录策略登录方式2具体实现抽象策略类UserGranter/** * 抽象策略类 */ public interface UserGranter{ /** * 获取数据 * param loginReq 传入的参数 * return map值 */ LoginResp login(LoginReq loginReq); }具体的策略AccountGranter、SmsGranter、WeChatGranter/** * 策略账号登录 **/ Component public class AccountGranter implements UserGranter{ Override public LoginResp login(LoginReq loginReq) { System.out.println(登录方式为账号登录 loginReq); // TODO // 执行业务操作 return new LoginResp(); } } /** * 策略:短信登录 */ Component public class SmsGranter implements UserGranter{ Override public LoginResp login(LoginReq loginReq) { System.out.println(登录方式为短信登录 loginReq); // TODO // 执行业务操作 return new LoginResp(); } } /** * 策略:微信登录 */ Component public class WeChatGranter implements UserGranter{ Override public LoginResp login(LoginReq loginReq) { System.out.println(登录方式为微信登录 loginReq); // TODO // 执行业务操作 return new LoginResp(); } }工程类UserLoginFactory/** * 操作策略的上下文环境类 工具类 * 将策略整合起来 方便管理 */ Component public class UserLoginFactory implements ApplicationContextAware { private static MapString, UserGranter granterPool new ConcurrentHashMap(); Autowired private LoginTypeConfig loginTypeConfig; /** * 从配置文件中读取策略信息存储到map中 * { * account:accountGranter, * sms:smsGranter, * we_chat:weChatGranter * } * * param applicationContext * throws BeansException */ Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { loginTypeConfig.getTypes().forEach((k, y) - { granterPool.put(k, (UserGranter) applicationContext.getBean(y)); }); } /** * 对外提供获取具体策略 * * param grantType 用户的登录方式需要跟配置文件中匹配 * return 具体策略 */ public UserGranter getGranter(String grantType) { UserGranter tokenGranter granterPool.get(grantType); return tokenGranter; } }在application.yml文件中新增自定义配置login: types: account: accountGranter sms: smsGranter we_chat: weChatGranter新增读取数据配置类Getter Setter Configuration ConfigurationProperties(prefix login) public class LoginTypeConfig { private MapString,String types; }改造service代码Service public class UserService { Autowired private UserLoginFactory factory; public LoginResp login(LoginReq loginReq){ UserGranter granter factory.getGranter(loginReq.getType()); if(granter null){ LoginResp loginResp new LoginResp(); loginResp.setSuccess(false); return loginResp; } LoginResp loginResp granter.login(loginReq); return loginResp; } }大家可以看到我们使用了设计模式之后业务层的代码就清爽多了如果后期有新的需求改动比如加入了QQ登录我们只需要添加对应的策略就可以无需再改动业务层代码。3.3.4 举一反三其实像这样的需求在日常开发中非常常见场景有很多以下的情景都可以使用工厂模式策略模式解决比如订单的支付策略支付宝支付微信支付银行卡支付现金支付解析不同类型excelxls格式xlsx格式打折促销满300元9折满500元8折满1000元7折物流运费阶梯计算5kg以下5kg-10kg10kg-20kg20kg以上一句话总结只要代码中有冗长的 if-else 或 switch 分支判断都可以采用策略模式优化4.责任链设计模式4.1 概述在现实生活中常常会出现这样的事例一个请求有多个对象可以处理但每个对象的处理条件或权限不同。例如公司员工请假可批假的领导有部门负责人、副总经理、总经理等但每个领导能批准的天数不同员工必须根据自己要请假的天数去找不同的领导签名也就是说员工必须记住每个领导的姓名、电话和地址等信息这增加了难度。这样的例子还有很多如找领导出差报销、生活中的“击鼓传花”游戏等。定义又名职责链模式为了避免请求发送者与多个请求处理者耦合在一起将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链当有请求发生时可将请求沿着这条链传递直到有对象处理它为止。比较常见的springmvc中的拦截器web开发中的filter过滤器4.2 结构职责链模式主要包含以下角色:抽象处理者Handler角色定义一个处理请求的接口包含抽象处理方法和一个后继连接。具体处理者Concrete Handler角色实现抽象处理者的处理方法判断能否处理本次请求如果可以处理请求则处理否则将该请求转给它的后继者。客户类Client角色创建处理链并向链头的具体处理者对象提交请求它不关心处理细节和请求的传递过程。4.3 案例实现处理订单的操作类图代码抽象处理者package com.itheima.designpattern.chain; /** * 抽象处理者 */ public abstract class Handler { protected Handler handler; public void setNext(Handler handler) { this.handler handler; } /** * 处理过程 * 需要子类进行实现 */ public abstract void process(OrderInfo order); }订单信息类package com.itheima.designpattern.chain; import java.math.BigDecimal; public class OrderInfo { private String productId; private String userId; private BigDecimal amount; public String getProductId() { return productId; } public void setProductId(String productId) { this.productId productId; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId userId; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount amount; } }具体处理者/** * 订单校验 */ public class OrderValidition extends Handler { Override public void process(OrderInfo order) { System.out.println(校验订单基本信息); //校验 handler.process(order); } } /** * 补充订单信息 */ public class OrderFill extends Handler { Override public void process(OrderInfo order) { System.out.println(补充订单信息); handler.process(order); } } /** * 计算金额 */ public class OrderAmountCalcuate extends Handler { Override public void process(OrderInfo order) { System.out.println(计算金额-优惠券、VIP、活动打折); handler.process(order); } } /** * 订单入库 */ public class OrderCreate extends Handler { Override public void process(OrderInfo order) { System.out.println(订单入库); } }客户类public class Application { public static void main(String[] args) { //检验订单 Handler orderValidition new OrderValidition(); //补充订单信息 Handler orderFill new OrderFill(); //订单算价 Handler orderAmountCalcuate new OrderAmountCalcuate(); //订单落库 Handler orderCreate new OrderCreate(); //设置责任链路 orderValidition.setNext(orderFill); orderFill.setNext(orderAmountCalcuate); orderAmountCalcuate.setNext(orderCreate); //开始执行 orderValidition.process(new OrderInfo()); } }4.4 优缺点优点降低了对象之间的耦合度该模式降低了请求发送者和接收者的耦合度。增强了系统的可扩展性可以根据需要增加新的请求处理类满足开闭原则。增强了给对象指派职责的灵活性当工作流程发生变化可以动态地改变链内的成员或者修改它们的次序也可动态地新增或者删除责任。责任链简化了对象之间的连接一个对象只需保持一个指向其后继者的引用不需保持其他所有处理者的引用这避免了使用众多的 if 或者 if···else 语句。责任分担每个类只需要处理自己该处理的工作不能处理的传递给下一个对象完成明确各类的责任范围符合类的单一职责原则。缺点不能保证每个请求一定被处理。由于一个请求没有明确的接收者所以不能保证它一定会被处理该请求可能一直传到链的末端都得不到处理。对比较长的职责链请求的处理可能涉及多个处理对象系统性能将受到一定影响。职责链建立的合理性要靠客户端来保证增加了客户端的复杂性可能会由于职责链的错误设置而导致系统出错如可能会造成循环调用。4.5 举一反三内容审核视频、文章、课程….订单创建简易流程审批结语这个篇章个人认为还是很有用的可以学习一下然后用到自己项目之中然后写在简历里面另外如果面试官问到哪些难点或者棘手的问题也可以把设计模式相关内容加进去
设计模式面试高频题:单例/工厂/代理/观察者模式在框架中的实际应用
发布时间:2026/7/13 21:04:17
1.前言在平时的开发中涉及到设计模式的有两块内容第一个是我们平时使用的框架比如spring、mybatis等第二个是我们自己开发业务使用的设计模式。面试官一般比较关心的是我们在开发过程中有没有使用过设计模式或者在简历上写了关于设计模式的描述那么这样我们就需要重点关心自己开发中用过的设计模式。在平时的业务开发中其实真正使用设计模式的场景并不多虽然设计号称有23种之多不同的纬度可能会更多但是在项目最常使用的也就几种而已在面试的过程中我们主要介绍一种或两种就可以重点要说的是在什么业务场景下使用了设计模式什么设计模式这次面试部分我们主要介绍三种设计模式工厂方法模式简单工厂、工厂方法、抽象工厂策略模式责任链模式2.工厂方法模式2.1 概述需求设计一个咖啡店点餐系统。设计一个咖啡类Coffee并定义其两个子类美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】再设计一个咖啡店类CoffeeStore咖啡店具有点咖啡的功能。具体类的设计如下1.类图中的符号表示public-表示private#表示protected2.泛化关系(继承)用带空心三角箭头的实线来表示3.依赖关系使用带箭头的虚线来表示package com.itheima.factory.simple; public class CoffeeStore { public static void main(String[] args) { Coffee coffee orderCoffee(latte); System.out.println(coffee.getName()); } public static Coffee orderCoffee(String type){ Coffee coffee null; if(american.equals(type)){ coffee new AmericanCoffee(); }else if (latte.equals(type)){ coffee new LatteCoffee(); } //添加配料 coffee.addMilk(); coffee.addSuqar(); return coffee; } }在java中万物皆对象这些对象都需要创建如果创建的时候直接new该对象就会对该对象耦合严重假如我们要更换对象所有new对象的地方都需要修改一遍这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象我们就只和工厂打交道就可以了彻底和对象解耦如果要更换对象直接在工厂里更换该对象即可达到了与对象解耦的目的所以说工厂模式最大的优点就是解耦。开闭原则对扩展开放对修改关闭。在程序需要进行拓展的时候不能去修改原有的代码实现一个热插拔的效果。简言之是为了使程序的扩展性好易于维护和升级。三种工厂简单工厂模式工厂方法模式抽象工厂模式2.2 简单工厂模式简单工厂不是一种设计模式反而比较像是一种编程习惯。2.2.1 结构简单工厂包含如下角色抽象产品 定义了产品的规范描述了产品的主要特性和功能。具体产品 实现或者继承抽象产品的子类具体工厂 提供了创建产品的方法调用者通过该方法来获取产品。2.2.2 实现现在使用简单工厂对上面案例进行改进类图如下工厂类代码如下public class SimpleCoffeeFactory { public Coffee createCoffee(String type) { Coffee coffee null; if(americano.equals(type)) { coffee new AmericanoCoffee(); } else if(latte.equals(type)) { coffee new LatteCoffee(); } return coffee; } }咖啡店package com.itheima.factory.simple; public class CoffeeStore { public Coffee orderCoffee(String type){ //通过工厂获得对象不需要知道对象实现的细节 SimpleCoffeeFactory factory new SimpleCoffeeFactory(); Coffee coffee factory.createCoffee(type); //添加配料 coffee.addMilk(); coffee.addSuqar(); return coffee; } }工厂factory处理创建对象的细节一旦有了SimpleCoffeeFactoryCoffeeStore类中的orderCoffee()就变成此对象的客户后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合同时又产生了新的耦合CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合工厂对象和商品对象的耦合。后期如果再加新品种的咖啡我们势必要需求修改SimpleCoffeeFactory的代码违反了开闭原则。工厂类的客户端可能有很多比如创建美团外卖等这样只需要修改工厂类的代码省去其他的修改操作。2.2.3 优缺点优点封装了创建对象的过程可以通过参数直接获取对象。把对象的创建和业务逻辑层分开这样以后就避免了修改客户代码如果要实现新产品直接修改工厂类而不需要在原代码中修改这样就降低了客户代码修改的可能性更加容易扩展。缺点增加新产品时还是需要修改工厂类的代码违背了“开闭原则”。2.2.4 扩展静态工厂在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的这个就是静态工厂模式它也不是23种设计模式中的。代码如下public class SimpleCoffeeFactory { public static Coffee createCoffee(String type) { Coffee coffee null; if(americano.equals(type)) { coffee new AmericanoCoffee(); } else if(latte.equals(type)) { coffee new LatteCoffee(); } return coffe; } }2.3 工厂方法模式针对上例中的缺点使用工厂方法模式就可以完美解决完全遵循开闭原则。2.3.1 概念定义一个用于创建对象的接口让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。2.3.2 结构工厂方法模式的主要角色抽象工厂Abstract Factory提供了创建产品的接口调用者通过它访问具体工厂的工厂方法来创建产品。具体工厂ConcreteFactory主要是实现抽象工厂中的抽象方法完成具体产品的创建。抽象产品Product定义了产品的规范描述了产品的主要特性和功能。具体产品ConcreteProduct实现了抽象产品角色所定义的接口由具体工厂来创建它同具体工厂之间一一对应。2.3.3 实现使用工厂方法模式对上例进行改进类图如下流程代码如下抽象工厂public interface CoffeeFactory { Coffee createCoffee(); }具体工厂public class LatteCoffeeFactory implements CoffeeFactory { public Coffee createCoffee() { return new LatteCoffee(); } } public class AmericanCoffeeFactory implements CoffeeFactory { public Coffee createCoffee() { return new AmericanCoffee(); } }咖啡店类public class CoffeeStore { private CoffeeFactory factory; public CoffeeStore(CoffeeFactory factory) { this.factory factory; } public Coffee orderCoffee(String type) { Coffee coffee factory.createCoffee(); coffee.addMilk(); coffee.addsugar(); return coffee; } }从以上的编写的代码可以看到要增加产品类时也要相应地增加工厂类不需要修改工厂类的代码了这样就解决了简单工厂模式的缺点。工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性工厂方法模式保持了简单工厂模式的优点而且克服了它的缺点。2.3.4 优缺点优点用户只需要知道具体工厂的名称就可得到所要的产品无须知道产品的具体创建过程在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类无须对原工厂进行任何修改满足开闭原则缺点每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类这增加了系统的复杂度。2.4 抽象工程模式前面介绍的工厂方法模式中考虑的是一类产品的生产如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。这些工厂只生产同种类产品同种类产品称为同等级产品也就是说工厂方法模式只考虑生产同等级的产品但是在现实生活中许多工厂是综合型的工厂能生产多等级种类 的产品如电器厂既生产电视机又生产洗衣机或空调大学既有软件专业又有生物专业等。本节要介绍的抽象工厂模式将考虑多等级产品的生产将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族下图所示产品族一个品牌下面的所有产品例如华为下面的电脑、手机称为华为的产品族产品等级多个品牌下面的同种产品例如华为和小米都有手机电脑为一个产品等级2.4.1 概念是一种为访问类提供一个创建一组相关或相互依赖对象的接口且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。抽象工厂模式是工厂方法模式的升级版本工厂方法模式只生产一个等级的产品而抽象工厂模式可生产多个等级的产品。一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂2.4.2 结构抽象工厂模式的主要角色如下抽象工厂Abstract Factory提供了创建产品的接口它包含多个创建产品的方法可以创建多个不同等级的产品。具体工厂Concrete Factory主要是实现抽象工厂中的多个抽象方法完成具体产品的创建。抽象产品Product定义了产品的规范描述了产品的主要特性和功能抽象工厂模式有多个抽象产品。具体产品ConcreteProduct实现了抽象产品角色所定义的接口由具体工厂来创建它 同具体工厂之间是多对一的关系。2.4.3 实现现咖啡店业务发生改变不仅要生产咖啡还要生产甜点同一个产品等级产品分类咖啡拿铁咖啡、美式咖啡甜点提拉米苏、抹茶慕斯同一个风味就是同一个产品族相当于同一个品牌美式风味美式咖啡、抹茶慕斯意大利风味拿铁咖啡、提拉米苏要是按照工厂方法模式需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类很容易发生类爆炸情况。所以这个案例可以使用抽象工厂模式实现。类图如下实现关系使用带空心三角箭头的虚线来表示整体调用思路2.4.4 优缺点优点当一个产品族中的多个对象被设计成一起工作时它能保证客户端始终只使用同一个产品族中的对象。缺点当产品族中需要增加一个新的产品时所有的工厂类都需要进行修改。2.4.5 使用场景当需要创建的对象是一系列相互关联或相互依赖的产品族时如电器工厂中的电视机、洗衣机、空调等。系统中有多个产品族但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。系统中提供了产品的类库且所有产品的接口相同客户端不依赖产品实例的创建细节和内部结构。如输入法换皮肤一整套一起换。生成不同操作系统的程序。3.策略模式3.1 概述先看下面的图片我们去旅游选择出行模式有很多种可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。作为一个程序猿开发需要选择一款开发工具当然可以进行代码开发的工具有很多可以选择Idea进行开发也可以使用eclipse进行开发也可以使用其他的一些开发工具。定义该模式定义了一系列算法并将每个算法封装起来使它们可以相互替换且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式它通过对算法进行封装把使用算法的责任和算法的实现分割开来并委派给不同的对象对这些算法进行管理。3.2 结构策略模式的主要角色如下抽象策略Strategy类这是一个抽象角色通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。具体策略Concrete Strategy类实现了抽象策略定义的接口提供具体的算法实现或行为。环境Context类持有一个策略类的引用最终给客户端调用。3.3 案例实现【例】促销活动一家百货公司在定年度的促销活动。针对不同的节日春节、中秋节、圣诞节推出不同的促销活动由促销员将促销活动展示给客户。类图如下聚合关系可以用带空心菱形的实线来表示代码如下定义百货公司所有促销活动的共同接口public interface Strategy { void show(); }定义具体策略角色Concrete Strategy每个节日具体的促销活动//为春节准备的促销活动A public class StrategyA implements Strategy { public void show() { System.out.println(买一送一); } } //为中秋准备的促销活动B public class StrategyB implements Strategy { public void show() { System.out.println(满200元减50元); } } //为圣诞准备的促销活动C public class StrategyC implements Strategy { public void show() { System.out.println(满1000元加一元换购任意200元以下商品); } }定义环境角色Context用于连接上下文即把促销活动推销给客户这里可以理解为销售员public class SalesMan { //持有抽象策略角色的引用 private Strategy strategy; public SalesMan(Strategy strategy) { this.strategy strategy; } //向客户展示促销活动 public void salesManShow(){ strategy.show(); } }3.3.1 综合案例下图是gitee的登录的入口其中有多种方式可以进行登录用户名密码登录短信验证码登录微信登录QQ登录....3.3.2 目前已实现的代码(1)登录接口说明请求方式POST路径/api/user/login参数LoginReq返回值LoginResp请求参数LoginReqData public class LoginReq { private String name; private String password; private String phone; private String validateCode;//手机验证码 private String wxCode;//用于微信登录 /** * account : 用户名密码登录 * sms : 手机验证码登录 * we_chat : 微信登录 */ private String type; }响应参数LoginRespData public class LoginResp{ private Integer userId; private String userName; private String roleCode; private String token; //jwt令牌 private boolean success; }控制层LoginControllerRestController RequestMapping(/api/user) public class LoginController { Autowired private UserService userService; PostMapping(/login) public LoginResp login(RequestBody LoginReq loginReq){ return userService.login(loginReq); } }业务层UserServiceService public class UserService { public LoginResp login(LoginReq loginReq){ if(loginReq.getType().equals(account)){ System.out.println(用户名密码登录); //执行用户密码登录逻辑 return new LoginResp(); }else if(loginReq.getType().equals(sms)){ System.out.println(手机号验证码登录); //执行手机号验证码登录逻辑 return new LoginResp(); }else if (loginReq.getType().equals(we_chat)){ System.out.println(微信登录); //执行用户微信登录逻辑 return new LoginResp(); } LoginResp loginResp new LoginResp(); loginResp.setSuccess(false); System.out.println(登录失败); return loginResp; } }注意我们重点讲的是设计模式并不是登录的逻辑所以以上代码并没有真正的实现登录功能(2)问题分析业务层代码大量使用到了if...else在后期阅读代码的时候会非常不友好大量使用if...else性能也不高如果业务发生变更比如现在新增了QQ登录方式这个时候需要修改业务层代码违反了开闭原则解决使用工厂方法设计模式策略模式解决3.3.3 代码改造工厂策略1整体思路改造之后不在service中写业务逻辑让service调用工厂然后通过service传递不同的参数来获取不同的登录策略登录方式2具体实现抽象策略类UserGranter/** * 抽象策略类 */ public interface UserGranter{ /** * 获取数据 * param loginReq 传入的参数 * return map值 */ LoginResp login(LoginReq loginReq); }具体的策略AccountGranter、SmsGranter、WeChatGranter/** * 策略账号登录 **/ Component public class AccountGranter implements UserGranter{ Override public LoginResp login(LoginReq loginReq) { System.out.println(登录方式为账号登录 loginReq); // TODO // 执行业务操作 return new LoginResp(); } } /** * 策略:短信登录 */ Component public class SmsGranter implements UserGranter{ Override public LoginResp login(LoginReq loginReq) { System.out.println(登录方式为短信登录 loginReq); // TODO // 执行业务操作 return new LoginResp(); } } /** * 策略:微信登录 */ Component public class WeChatGranter implements UserGranter{ Override public LoginResp login(LoginReq loginReq) { System.out.println(登录方式为微信登录 loginReq); // TODO // 执行业务操作 return new LoginResp(); } }工程类UserLoginFactory/** * 操作策略的上下文环境类 工具类 * 将策略整合起来 方便管理 */ Component public class UserLoginFactory implements ApplicationContextAware { private static MapString, UserGranter granterPool new ConcurrentHashMap(); Autowired private LoginTypeConfig loginTypeConfig; /** * 从配置文件中读取策略信息存储到map中 * { * account:accountGranter, * sms:smsGranter, * we_chat:weChatGranter * } * * param applicationContext * throws BeansException */ Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { loginTypeConfig.getTypes().forEach((k, y) - { granterPool.put(k, (UserGranter) applicationContext.getBean(y)); }); } /** * 对外提供获取具体策略 * * param grantType 用户的登录方式需要跟配置文件中匹配 * return 具体策略 */ public UserGranter getGranter(String grantType) { UserGranter tokenGranter granterPool.get(grantType); return tokenGranter; } }在application.yml文件中新增自定义配置login: types: account: accountGranter sms: smsGranter we_chat: weChatGranter新增读取数据配置类Getter Setter Configuration ConfigurationProperties(prefix login) public class LoginTypeConfig { private MapString,String types; }改造service代码Service public class UserService { Autowired private UserLoginFactory factory; public LoginResp login(LoginReq loginReq){ UserGranter granter factory.getGranter(loginReq.getType()); if(granter null){ LoginResp loginResp new LoginResp(); loginResp.setSuccess(false); return loginResp; } LoginResp loginResp granter.login(loginReq); return loginResp; } }大家可以看到我们使用了设计模式之后业务层的代码就清爽多了如果后期有新的需求改动比如加入了QQ登录我们只需要添加对应的策略就可以无需再改动业务层代码。3.3.4 举一反三其实像这样的需求在日常开发中非常常见场景有很多以下的情景都可以使用工厂模式策略模式解决比如订单的支付策略支付宝支付微信支付银行卡支付现金支付解析不同类型excelxls格式xlsx格式打折促销满300元9折满500元8折满1000元7折物流运费阶梯计算5kg以下5kg-10kg10kg-20kg20kg以上一句话总结只要代码中有冗长的 if-else 或 switch 分支判断都可以采用策略模式优化4.责任链设计模式4.1 概述在现实生活中常常会出现这样的事例一个请求有多个对象可以处理但每个对象的处理条件或权限不同。例如公司员工请假可批假的领导有部门负责人、副总经理、总经理等但每个领导能批准的天数不同员工必须根据自己要请假的天数去找不同的领导签名也就是说员工必须记住每个领导的姓名、电话和地址等信息这增加了难度。这样的例子还有很多如找领导出差报销、生活中的“击鼓传花”游戏等。定义又名职责链模式为了避免请求发送者与多个请求处理者耦合在一起将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链当有请求发生时可将请求沿着这条链传递直到有对象处理它为止。比较常见的springmvc中的拦截器web开发中的filter过滤器4.2 结构职责链模式主要包含以下角色:抽象处理者Handler角色定义一个处理请求的接口包含抽象处理方法和一个后继连接。具体处理者Concrete Handler角色实现抽象处理者的处理方法判断能否处理本次请求如果可以处理请求则处理否则将该请求转给它的后继者。客户类Client角色创建处理链并向链头的具体处理者对象提交请求它不关心处理细节和请求的传递过程。4.3 案例实现处理订单的操作类图代码抽象处理者package com.itheima.designpattern.chain; /** * 抽象处理者 */ public abstract class Handler { protected Handler handler; public void setNext(Handler handler) { this.handler handler; } /** * 处理过程 * 需要子类进行实现 */ public abstract void process(OrderInfo order); }订单信息类package com.itheima.designpattern.chain; import java.math.BigDecimal; public class OrderInfo { private String productId; private String userId; private BigDecimal amount; public String getProductId() { return productId; } public void setProductId(String productId) { this.productId productId; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId userId; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount amount; } }具体处理者/** * 订单校验 */ public class OrderValidition extends Handler { Override public void process(OrderInfo order) { System.out.println(校验订单基本信息); //校验 handler.process(order); } } /** * 补充订单信息 */ public class OrderFill extends Handler { Override public void process(OrderInfo order) { System.out.println(补充订单信息); handler.process(order); } } /** * 计算金额 */ public class OrderAmountCalcuate extends Handler { Override public void process(OrderInfo order) { System.out.println(计算金额-优惠券、VIP、活动打折); handler.process(order); } } /** * 订单入库 */ public class OrderCreate extends Handler { Override public void process(OrderInfo order) { System.out.println(订单入库); } }客户类public class Application { public static void main(String[] args) { //检验订单 Handler orderValidition new OrderValidition(); //补充订单信息 Handler orderFill new OrderFill(); //订单算价 Handler orderAmountCalcuate new OrderAmountCalcuate(); //订单落库 Handler orderCreate new OrderCreate(); //设置责任链路 orderValidition.setNext(orderFill); orderFill.setNext(orderAmountCalcuate); orderAmountCalcuate.setNext(orderCreate); //开始执行 orderValidition.process(new OrderInfo()); } }4.4 优缺点优点降低了对象之间的耦合度该模式降低了请求发送者和接收者的耦合度。增强了系统的可扩展性可以根据需要增加新的请求处理类满足开闭原则。增强了给对象指派职责的灵活性当工作流程发生变化可以动态地改变链内的成员或者修改它们的次序也可动态地新增或者删除责任。责任链简化了对象之间的连接一个对象只需保持一个指向其后继者的引用不需保持其他所有处理者的引用这避免了使用众多的 if 或者 if···else 语句。责任分担每个类只需要处理自己该处理的工作不能处理的传递给下一个对象完成明确各类的责任范围符合类的单一职责原则。缺点不能保证每个请求一定被处理。由于一个请求没有明确的接收者所以不能保证它一定会被处理该请求可能一直传到链的末端都得不到处理。对比较长的职责链请求的处理可能涉及多个处理对象系统性能将受到一定影响。职责链建立的合理性要靠客户端来保证增加了客户端的复杂性可能会由于职责链的错误设置而导致系统出错如可能会造成循环调用。4.5 举一反三内容审核视频、文章、课程….订单创建简易流程审批结语这个篇章个人认为还是很有用的可以学习一下然后用到自己项目之中然后写在简历里面另外如果面试官问到哪些难点或者棘手的问题也可以把设计模式相关内容加进去