- 装饰者模式:已经有了一个现有类,不可使用继承,使用构造。侧重新类旧类同时使用
- 派发模式:delegate是关键字,如缓存与数据库的集合。有了现有类,但只想用新类方式使用旧类。于此同时还以偷懒的方式把任务给了旧类
- 策略模式:提供多个实现类供消费者多选一。要注意结合工厂模式,不要使用switch和if。策略模式是委派模式内部的一种实现形式。
- 动态代理:jdk动态代理接口InvocationHandler;cglib动态代理接口是MethodInceptor。一个是接口代理,几个是父类代理。
- 模板方法模式:抽象类,实现了一部分方法(不变部分),但已实现的方法里会调用未实现的内容(可变部分),所以利用不能子类的多态完成不同的功能。关键字眼是抽象类叫BaseXXX.java,不变方法是Query(),可变方法是doQuery()。如Servlet的
service()
和doGet()doPost()
(钩子思想) - 责任链模式:程序暂时无法直接决定由哪个对象负责处理时,就需要推卸责任。
- 适配器模式:转换电压。如登录方式多种多样,但最后都是账号密码的形式
delegate委派模式
委派模式不属于GOF23种设计模式, 主要角色有三种: 抽象任务角色, 委派者角色, 具体任务角色.
比如老板Boss给项目经理Leader下达任务,项目经理会根据实际情况给每个员工派发任务,待员工把任务完成后,再由项目经理向老板汇报结果。
实现层面上, 定义一个抽象接口, 它有若干实现类, 他们真正执行业务方法, 这些子类是具体任务角色; 定义委派者角色也实现该接口, 但它负责在各个具体角色实例之间做出决策, 由它判断并调用具体实现的方法.
委派模式对外隐藏了具体实现, 仅将委派者角色暴露给外部, 如Spring的DispatcherServlet.
代码
不管是leader还是普通员工,他们在boss眼里都是一个公司员工
公共接口
public interface IEmployee {
public void doing(String command);
}
boss
public class Boss {
public void command(String command,Leader leader){
leader.doing(command);
}
}
leader
public class Leader implements IEmployee {
private Map<String,IEmployee> targets = new HashMap<String,IEmployee>();
public Leader() {
targets.put("加密",new EmployeeA());
targets.put("登录",new EmployeeB());
}
//项目经理自己不干活
public void doing(String command){
targets.get(command).doing(command);
}
}
普通源
public class EmployeeA implements IEmployee {
@Override
public void doing(String command) {
System.out.println("我是员工A,我现在开始干" + command + "工作");
}
}
public class EmployeeB implements IEmployee {
@Override
public void doing(String command) {
System.out.println("我是员工B,我现在开始干" + command + "工作");
}
}
main
public class DelegateTest {
public static void main(String[] args) {
//客户请求(Boss)、委派者(Leader)、被被委派者(Target)
//委派者要持有被委派者的引用
//代理模式注重的是过程, 委派模式注重的是结果
//策略模式注重是可扩展(外部扩展),委派模式注重内部的灵活和复用
//委派的核心:就是分发、调度、派遣
//委派模式:就是静态代理和策略模式一种特殊的组合
new Boss().command("登录",new Leader());
}
}
在源码中的体现
Spring MVC的DispathcerServlet。
另外在以delegate结尾的地方都实现了委派模式
/**
* 相当于是项目经理的角色
*/
public class DispatcherServlet extends HttpServlet{
private List<Handler> handlerMapping = new ArrayList<Handler>();
public void init() throws ServletException {
try {
Class<?> memberControllerClass = MemberController.class;
handlerMapping.add(new Handler()
.setController(memberControllerClass.newInstance())
.setMethod(memberControllerClass.getMethod("getMemberById", new Class[]{
String.class}))
.setUrl("/web/getMemberById.json"));
}catch(Exception e){
}
}
// 下面是根据请求参数中的内容,判断应该执行哪个controller
// private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception{
//
// String uri = request.getRequestURI();
//
// String mid = request.getParameter("mid");
//
// if("getMemberById".equals(uri)){
// new MemberController().getMemberById(mid);
// }else if("getOrderById".equals(uri)){
// new OrderController().getOrderById(mid);
// }else if("logout".equals(uri)){
// new SystemController().logout();
// }else {
// response.getWriter().write("404 Not Found!!");
// }
//
// }
private void doDispatch(HttpServletRequest request, HttpServletResponse response){
//1、获取用户请求的url
// 如果按照J2EE的标准、每个url对对应一个Serlvet,url由浏览器输入
String uri = request.getRequestURI();
//2、Servlet拿到url以后,要做权衡(要做判断,要做选择)
// 根据用户请求的URL,去找到这个url对应的某一个java类的方法
//3、通过拿到的URL去handlerMapping(我们把它认为是策略常量)
Handler handle = null;
for (Handler h: handlerMapping) {
if(uri.equals(h.getUrl())){
handle = h;
break;
}
}
//4、将具体的任务分发给Method(通过反射去调用其对应的方法)
Object object = null;
try {
object = handle.getMethod().invoke(handle.getController(),request.getParameter("mid"));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//5、获取到Method执行的结果,通过Response返回出去
// response.getWriter().write();
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req,resp);
} catch (Exception e) {
e.printStackTrace();
}
}
class Handler{
private Object controller;
private Method method;
private String url;
// 省略setter getter
}
public class OrderController {
public void getOrderById(String mid){
}
}
public class SystemController {
public void logout(){
}
}
public class MemberController {
public void getMemberById(String mid){
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Gupao Web Application</display-name>
<!--自定义servlet-->
<servlet>
<servlet-name>delegateServlet</servlet-name>
<servlet-class>com.gupaoedu.vip.pattern.delegate.mvc.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>delegateServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
delegate总结
优点: 对内隐藏实现, 易于扩展; 简化调用;
委派模式大量使用在spring,mybatis等开源框架中, 理解委派模式的实现原理可以更好理解这些框架源码.
委派模式的核心是委派类的实现.
Strategy策略模式
整体地替换算法的实现部分。
定义了算法家族并分别封装起来,让他们之间可以互相替换,此模式使得算法的改变不贵影响使用算法的用户。
1 应用场景
- 系统中有很多类,他们的区别仅仅在于行为不同
- 一个系统需要动态地在几种算法中选择一种
要符合的原则
- 策略模式符合开闭原则
- 策略模式可编码使用多重条件判断语句,如if…else、switch…case
- 使用策略模式可以提高算法的保密性和安全性
如打折促销活动,有优惠券还有返现形式
2 代码
促销接口
/**
* 促销策略抽象
*/
public interface PromotionStrategy {
//这个方法是没有流程,只是个未实现的名字
void doPromotion();
}
促销实现类
优惠券方式/返现方式/拼团方式
/** * 优惠券 */
public class CouponStrategy implements PromotionStrategy {
public void doPromotion() {
System.out.println("领取优惠券,课程的价格直接减优惠券面值抵扣");
}
}
/*** 返现活动 */
public class CashbackStrategy implements PromotionStrategy {
public void doPromotion() {
System.out.println("返现促销,返回的金额转到支付宝账号");
}
}
/** * 拼团优惠 */
public class GroupbuyStrategy implements PromotionStrategy{
public void doPromotion() {
System.out.println("拼团,满20人成团,全团享受团购价");
}
}
/* 无优惠 */
public class EmptyStrategy implements PromotionStrategy {
public void doPromotion() {
System.out.println("无促销活动");
}
}
传入促销方案
/* * 优惠活动 */
public class PromotionActivity {
private PromotionStrategy promotionStrategy;
// 构造
public PromotionActivity(PromotionStrategy promotionStrategy) {
this.promotionStrategy = promotionStrategy;
}
public void execute(){
promotionStrategy.doPromotion();
}
}
客户端使用
我们首先想到的是new优惠方案之后传入构造函数,这样不方便对促销活动动态选择
第二种方案是switch 字符串的方式,
但是第二个问题是,
- 我们不应该让消费者方修改(要符合开闭原则),
- 比如还要在使用方main的里面修改switch(少用if…和switch…)。我们的想法是虽然消费方要使用新的优惠方式,只需要修改后台就可以了,不用修改消费方的逻辑。消费方应该少知道算法,有利于保密性和安全性
我们先说这两种方案,然后说继续改进方案
/**
* 促销活动
*/
public class PromotionActivityTest {
public static void main(String[] args) {
PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
PromotionActivity activity1111 = new PromotionActivity(new CashbackStrategy());
activity618.execute();
activity1111.execute();
}
// public static void main(String[] args) {
// PromotionActivity promotionActivity = null;
//
// String promotionKey = "COUPON";
//
// if(StringUtils.equals(promotionKey,"COUPON")){
// promotionActivity = new PromotionActivity(new CouponStrategy());
// }else if(StringUtils.equals(promotionKey,"CASHBACK")){
// promotionActivity = new PromotionActivity(new CashbackStrategy());
// }//......
// promotionActivity.execute();
// }
}
最终方案
最终选定方案为结合单例模式和工厂模式
/**
* 促销策略工厂
*/
public class PromotionStrategyFactory {
private static Map<String,PromotionStrategy> PROMOTION_STRATEGY_MAP = new HashMap<String, PromotionStrategy>();
static {
PROMOTION_STRATEGY_MAP.put(PromotionKey.COUPON,new CouponStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.CASHBACK,new CashbackStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.GROUPBUY,new GroupbuyStrategy());
}
private static final PromotionStrategy NON_PROMOTION = new EmptyStrategy();
private PromotionStrategyFactory(){
}
public static PromotionStrategy getPromotionStrategy(String promotionKey){
PromotionStrategy promotionStrategy = PROMOTION_STRATEGY_MAP.get(promotionKey);
return promotionStrategy == null ? NON_PROMOTION : promotionStrategy;
}
private interface PromotionKey{
String COUPON = "COUPON";
String CASHBACK = "CASHBACK";
String GROUPBUY = "GROUPBUY";
}
}
public static void main(String[] args) {
String promotionKey = "GROUPBUY";
// 虽然意思还是根据字符串返回对应的策略
PromotionActivity promotionActivity = new PromotionActivity(PromotionStrategyFactory.getPromotionStrategy(promotionKey));
promotionActivity.execute();
}
3 策略模式在源码中的体现
java-Comparator接口
spring-Resource接口
spring-InstantiationStrategy
实现类有SimpleInstantiationStrategy
和 CglibSubclassingInstantiationStrategy
4 委派模式和策略模式综合应用
在之前的例子中,我们说了我们可以自定义DispatcherServlet实现根据参数来选择controller。
但这样代码扩展性不好,不能为每个controller写个if…else,优化想法如下:
-
在DispatcherServlet的init()中获取所有HandlerMapping
-
在doDispatch()中for(Handler h:handlerMapping)即可
-
装饰者模式:已经有了一个现有类,不可使用继承,使用构造。侧重新类旧类同时使用
-
派发模式:delegate是关键字,如缓存与数据库的集合。有了现有类,但只想用新类方式使用旧类。于此同时还以偷懒的方式把任务给了旧类
-
策略模式:提供多个实现类供消费者选择。要注意结合工厂模式,不要使用switch和if
-
动态代理:jdk动态代理接口InvocationHandler;cglib动态代理接口是MethodInceptor。一个是接口代理,几个是父类代理。
-
模板方法模式:抽象类,实现了一部分方法(不变部分),但已实现的方法里会调用未实现的内容(可变部分),所以利用不能子类的多态完成不同的功能。关键字眼是抽象类叫BaseXXX.java,不变方法是Query(),可变方法是doQuery()。如Servlet的
service()
和doGet()doPost()
(钩子思想) -
适配器模式:转换电压。如登录方式多种多样,但最后都是账号密码的形式
装饰者模式
定义
装饰模式是在
- 不必改变原类和
- 不使用继承的情况下 (但可以使用实现),
动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象
使用场景
- 需要在运行时动态的给一个对象增加额外的职责时候
- 需要给一个现有的类增加职责,但是又不想通过继承的方式来实现的时候(应该优先使用组合而非继承),或者通过继承的方式不现实的时候(可能由于排列组合产生类爆炸的问题)。
比如,数据库查询的时候,我们有了缓存,可以先从缓存中拿,缓存中没有再从数据库中查。那么我们封装的时候查询类内部持有者数据库的属性,
比如mybatis中执行器:
![img](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2018a/83326eb3.jpg)
public class CachingExecutor implements Executor {
private BaseExecutor delegate;// 分发 ,这个是术语BaseExecutor那个支的实现类
CachingExecutor只负责缓存的逻辑,缓存中没有再拿delegate去查询数据库
具体业务场景
在上一篇 桥接模式 中提到林蛋大公司接了星巴克的咖啡系统项目。林蛋大在王二狗的指导下使用桥接模式实现了星巴克要求的各种下单功能:大杯原味、大杯加糖、大杯加奶;中杯原味、中杯加糖、中杯加奶;小杯原味、小杯加糖、小杯加奶。刚舒服了没两天,项目经理就来找蛋大了:蛋大啊,现在用户的口味太刁,好多都要同时加奶,加糖,有的还要加蜂蜜,咱们目前这个实现不支持,你去改一下。对了,有些用户要求先加糖再加奶,而一些用户要求先加奶然后再加糖,顺序不能乱!蛋大:我了个去。。。。。。。正当林蛋大一筹莫展的时候,王二狗兴高采烈的走了过来,看样子和牛翠花的进展不错。蛋大:狗哥,我这个有个棘手的问题请教你一下。。。
王二狗开始分析需求:假设我们有一个原味咖啡的类 OriginalCoffee
, 目前的需求就是要动态的给这个类的一些实例增加一些额外功能,此处就是动态的对某些咖啡制作过程增加新的流程,例如加奶,加糖,而有的咖啡却保持原味不变。
这种需求要是通过继承的方式就不太好实现,因为咖啡制作过程是动态变化的。例如有的需要原味咖啡,有的需要加奶咖啡,有的需要加糖咖啡,而有的需要先加奶再加糖咖啡,而有的需要先加糖再加奶的咖啡,。。。这是一个排列组合问题,如果使用类继承的方式实现,必须预先将所有可能组合都想清楚,然后生成相应的子类,随着咖啡口味的增多,以及添加顺序的改变,几乎是不可扩展和维护的。
经过需求分析,二狗锁定了装饰者模式来实现此需求。原味咖啡是本质,而加奶,加糖都是在装饰这个本质的东西,再怎么加东西咖啡还是咖啡。
下图是装饰者模式的UML图
首先我们有一个ICoffee
接口,里面有一个制作咖啡的接口方法makeCoffee()
。要进行装饰的类 OriginalCoffee
和装饰者基类CoffeeDecorator
(一般为抽象类)实现了此接口。
CoffeeDecorator
类里面持有一个ICoffee
引用,我们第一步会把要装饰那个原始对象赋值给这个引用,那样在装饰者类中才可以调用到那个被装饰的对象的方法。MilkDecorator
和SugarDecorator
都继承至CoffeeDecorator
, 都是具体的装饰者类。
具体实现
接口
第一步:先声明一个原始对象的接口
public interface ICoffee {
void makeCoffee();
}
现有类
第二步:构建我们的原始对象,此处为原味咖啡对象,它实现了ICoffee
接口。
public class OriginalCoffee implements ICoffee {
@Override
public void makeCoffee() {
System.out.print("原味咖啡 ");
}
}
利用构造扩展现有类
第三步:构建装饰者抽象基类,它要实现与原始对象相同的接口ICoffee
,其内部持有一个ICoffee
类型的引用,用来接收被装饰的对象
public abstract class CoffeeDecorator implements ICoffee {
private ICoffee coffee;// 原味咖啡
public CoffeeDecorator(ICoffee coffee){
this.coffee=coffee;
}
@Override
public void makeCoffee() {
coffee.makeCoffee();
}
}
第四步:构建各种装饰者类,他们都继承至装饰者基类 CoffeeDecorator
。此处生成了两个,一个是加奶的装饰者,另一个是加糖的装饰者。
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(ICoffee coffee) {
super(coffee);
}
@Override
public void makeCoffee() {
super.makeCoffee();
addMilk();
}
private void addMilk(){
System.out.print("加奶 ");
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(ICoffee coffee) {
super(coffee);
}
@Override
public void makeCoffee() {
super.makeCoffee();
addSugar();
}
private void addSugar(){
System.out.print("加糖");
}
}
第五步:客户端使用
public static void main(String[] args) {
//原味咖啡
ICoffee coffee=new OriginalCoffee();
coffee.makeCoffee();
System.out.println("");
//加奶的咖啡
coffee=new MilkDecorator(coffee);
coffee.makeCoffee();
System.out.println("");
//先加奶后加糖的咖啡
coffee=new SugarDecorator(coffee);
coffee.makeCoffee();
}
输出:
原味咖啡
原味咖啡 加奶
原味咖啡 加奶 加糖
可以从客户端调用代码看出,装饰者模式的精髓在于动态的给对象增减功能。
当你你需要原味咖啡时那就生成原味咖啡的对象,而当你需要加奶咖啡时,仅仅需要将原味咖啡对象传递到加奶装饰者中去装饰一下就好了。如果你加了奶还想加糖,那就把加了奶的咖啡对象丢到加糖装饰者类中去装饰一下,一个先加奶后加糖的咖啡对象就出来了。
优缺点
优点
可以提供比继承更加灵活的方式去扩展对象的功能,通过排列组合,可以对某个类的一些对象做动态的功能扩展,而不需要装饰的对象却可以保持原样。
缺点
仍然是设计模式的通用缺点:类的个数会增加,会产生很多装饰者类,相应的就增加了复杂度。
装饰者模式与代理模式的区别
一般认为代理模式侧重于使用代理类增强被代理对象的访问,而装饰者模式侧重于使用装饰者类来对被装饰对象的功能进行增减。 除了上面的区别,个人实践中还发现,装饰者模式主要是提供一组装饰者类,然后形成一个装饰者栈,来动态的对某一个对象不断加强,而代理一般不会使用多级代理,详情请见秒懂Java代理与动态代理模式
装饰者模式与派发模式
装饰者模式侧重于都实现同一接口,已经有了一个现有类
派发模式侧重于对现有类进行包装,如缓存,关键字眼是delegate
动态代理模式也是包装,也叫包装的方式很多,我觉得派发模式是静态代理,个人的想法,欢迎一起讨论
责任链模式
当不请求程序进行某个处理,但程序暂时无法直接决定由哪个对象负责处理时,就需要推卸责任。
这种情况下,我们可以考虑将多个对象组成一条责任链,然后按照它们在职责链上的顺序一个一个地找出到底应该谁来负责处理。
自己处理不了就交给下一个
问题
public class Trouble {
private int number; // 问题编号
public Trouble(int number) {
// 生成问题
this.number = number;
}
public int getNumber() {
// 获取问题编号
return number;
}
public String toString() {
// 代表问题的字符串
return "[Trouble " + number + "]";
}
}
抽象类Support
public abstract class Support {
private String name; // 解决问题的实例的名字
private Support next; // 要推卸给的对象
public Support(String name) {
// 生成解决问题的实例
this.name = name;
}
// 返回传入的
public Support setNext(Support next) {
// 设置要推卸给的对象
this.next = next;
return next;
}
public void support(Trouble trouble) {
// 解决问题的步骤
// 先自己尝试处理
if (resolve(trouble)) {
done(trouble);
// 自己处理不了,交给下一个
} else if (next != null) {
next.support(trouble);
} else {
// 谁也处理不了
fail(trouble);
}
}
public String toString() {
// 显示字符串
return "[" + name + "]";
}
protected abstract boolean resolve(Trouble trouble); // 解决问题的方法
protected void done(Trouble trouble) {
// 解决
System.out.println(trouble + " is resolved by " + this + ".");
}
protected void fail(Trouble trouble) {
// 未解决
System.out.println(trouble + " cannot be resolved.");
}
}
永远不处理问题
public class NoSupport extends Support {
public NoSupport(String name) {
super(name);
}
protected boolean resolve(Trouble trouble) {
// 解决问题的方法
return false; // 自己什么也不处理
}
}
处理指定范围
public class LimitSupport extends Support {
private int limit; // 可以解决编号小于limit的问题
public LimitSupport(String name, int limit) {
// 构造函数
super(name);
this.limit = limit;
}
protected boolean resolve(Trouble trouble) {
// 解决问题的方法
if (trouble.getNumber() < limit) {
return true;
} else {
return false;
}
}
}
处理奇数
public class OddSupport extends Support {
public OddSupport(String name) {
// 构造函数
super(name);
}
protected boolean resolve(Trouble trouble) {
// 解决问题的方法
if (trouble.getNumber() % 2 == 1) {
return true;
} else {
return false;
}
}
}
处理指定数字
public class SpecialSupport extends Support {
private int number; // 只能解决指定编号的问题
public SpecialSupport(String name, int number) {
// 构造函数
super(name);
this.number = number;
}
protected boolean resolve(Trouble trouble) {
// 解决问题的方法
if (trouble.getNumber() == number) {
return true;
} else {
return false;
}
}
}
main
public class Main {
public static void main(String[] args) {
Support alice = new NoSupport("Alice");
Support bob = new LimitSupport("Bob", 100);
Support charlie = new SpecialSupport("Charlie", 429);
Support diana = new LimitSupport("Diana", 200);
Support elmo = new OddSupport("Elmo");
Support fred = new LimitSupport("Fred", 300);
// 形成职责链
alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
// 制造各种问题
for (int i = 0; i < 500; i += 33) {
alice.support(new Trouble(i));
}
}
}