zoukankan      html  css  js  c++  java
  • 设计模式-委派/策略模式

    1. 委派模式

    1.1 委派模式的简介

    • 委派模式不属于 GOF23 种设计模式中。
    • 委派模式( Delegate Pattern )的基本作用就是负责任务的调用和分配任务,跟代理模式很像,可以看做是一种特殊情况下的静态代理 的全权代理,但是代理模式注重过程,而委派模式注重结果。

    1.2 委派模式的使用场景

    • 委派模式在 Spring 中应用非常多,大家常用的 DispatcherServlet 其实就是用到了委派模式。

    • 现实生活中也常有委 派的场景发生,例如:老板(Boss)给项目经理(Leader)下达任务,项目经理会根据 实际情况给每个员工派发工作任务,待员工把工作任务完成之后,再由项目经理汇报工 作进度和结果给老板。

    1.3 场景实现

    • 上述工作中的场景是大家熟悉的,当 BossLeader 下发任务后, Leader 会根据实际情况来分配给响应的组员,我们将这一实际场景进行抽象化处理,用代码来进行实现

    • 首先我们要明确其中的关系,客户请求(Boss)、委派者(Leader)、被委派者(Target) 在这个构建中 委派者与被委派者都服务与客户请求,只是真实的操作时让被委派者执行的,有点像静态代理

    • 总体模型视图如下:

    • 编写 LeaderTarget 的共有父接口
    
    public interface IEmployee {
    
        void doWork(String commd);
    }
    
    
    • 编写相应的实现类

    编写普通员工类:

    public class EmployeeA implements IEmployee {
        @Override
        public void doWork(String commd) {
            System.out.println("EmployeeA 正在处理 "+commd +"任务");
        }
    }
    
    public class EmployeeB implements IEmployee {
        @Override
        public void doWork(String commd) {
            System.out.println("EmployeeB 正在处理 "+commd +"任务");
    
        }
    }
    
    

    编写 Leader 实现:

    public class Leader implements IEmployee {
    
        private static Map<String,IEmployee> handlerMapping = new HashMap<>();
        public Leader(){
            //初始化规则
            handlerMapping.put("Login",new EmployeeA());
            handlerMapping.put("Pay",new EmployeeB());
        }
        @Override
        public void doWork(String commd) {
            handlerMapping.get(commd).doWork(commd);
        }
    }
    

    在初始化 Leader 时我们首先将对应的规则记录,也就是委派的规则,那些任务需要派给 A , 那么任务需要派给 B ,后期的其他需求也是在这里进行扩展

    编写 Boss 类:

    /**
     * @author: anonystar
     * @time: 2020/5/27 16:48
     */
    public class Boss {
    
        private Leader leader;
        
        public Boss(Leader leader){
            this.leader = leader;
        }
    
        public void command(String cmd) {
            //委派分发
            leader.doWork(cmd);
        }
    }
    

    测试代码:

    /**
     * @author: anonystar
     * @time: 2020/5/28 9:40
     */
    public class SimpleDelegateTest {
    
        public static void main(String[] args) {
            //客户请求(Boss)、委派者(Leader)、被被委派者(Target)
            // 委派者要持有被委派者的引用
            // 代理模式注重的是过程, 委派模式注重的是结果
            // 策略模式注重是可扩展(外部扩展),委派模式注重内部的灵活和复用
            // 委派的核心:就是分发、调度、派遣
            // 
            Boss boss = new Boss(new Leader());
            boss.command("Pay");
        }
    }
    

    1.4 小结

    • 我们通过上面代码可以发现委派模式就是静态代理和策略模式一种特殊的组合

    • 代理模式注重的是过程, 委派模式更注重的是结果

    • 委派者要持有被委派者的引用

    • 委派的核心:就是分发、调度、派遣


    2. 策略模式

    2.1 策略模式简介

    • 策略模式是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。

    • 此模式让算法的变化不会影响到使用算法的用户

    2.2 场景适用

    • 1、假如系统中有很多类,而他们的区别仅仅在于他们的行为不同。

    • 2、一个系统需要动态地在几种算法中选择一种。

    2.3 场景模拟

    2.3.1 场景问题提出

    前提:

    • 假设你为旅游者们设计了一款导游程序。 该程序的核心功能是提供美观的地图, 以帮助用户在任何城市中快速定位。

    • 用户期待的程序新功能是自动路线规划: 他们希望输入地址后就能在地图上看到前往目的地的最快路线。

    • 程序的首个版本只能规划公路路线,这满足了驾车旅行的人们的需求,但是也很明显的会忽略其他选择,所以你需要在一次次的迭代中增加新的规划线路方案,如增加步行线路、公共交通线路等等。

    • 你以为这样就够了?这只是个开始,没多久时间你又要为骑行者规划路线。 又过了一段时间, 你又要为游览城市中的所有景点规划路线。此时相信面对不断臃肿的代码已经苦不堪言了,每次都的改动大量的代码

    实际问题:

    • 每次线路的增加都让整个开发团队非常头痛,因为每次增加新的线路规划后整个代码中的主体类都会增加一倍,慢慢的整个团都都无法继续维护这大量凌乱的代码

    • 当在使用过程中暴露出缺陷和某些功能的微调时,那么对当前的修改都会影响到整个线路规划,同时增加了程序运行中的其他风险

    • 越到后期团队合作将变得越低效。 尤其在后期招募了新的团队成员,他们需要大量的时间来熟悉和适应这些内容,同时在各种版本合并中挣扎。在实现新功能的过程中, 你的团队需要修改同一个巨大的类, 这样他们所编写的代码相互之间就可能会出现冲突。

    2.3.2 解决方案

    • 策略模式建议找出负责用许多不同方式完成特定任务的类, 然后将其中的算法抽取到一组被称为策略的独立类中。

    名为上下文的原始类必须包含一个成员变量来存储对于每种策略的引用。 上下文并不执行任务, 而是将工作委派给已连接的策略对象。

    上下文不负责选择符合任务需要的算法——客户端会将所需策略传递给上下文。 实际上, 上下文并不十分了解策略, 它会通过同样的通用接口与所有策略进行交互, 而该接口只需暴露一个方法来触发所选策略中封装的算法即可。

    因此, 上下文可独立于具体策略。 这样你就可在不修改上下文代码或其他策略的情况下添加新算法或修改已有算法了。

    2.4 代码实现

    • 构建路线顶级接口
    /**
     * 路线接口
     * @author: anonystar
     * @url: i-code.online
     * @time: 2020/6/8 16:51
     */
    public interface Route {
    
         String ROUTE_WALK = "walk";
         String ROUTE_CAR = "car";
         String ROUTE_CYCLING = "cycling";
    
        public void doRoute();
    }
    
    • 实现具体线路方式 如步行线路、驾车线路、骑行线路等,均实现 Route 接口
    /**
     * 驾车线路
     * @author: anonystar
     * @url: i-code.online
     * @time: 2020/6/8 16:58
     */
    public class CarRoute implements Route {
        @Override
        public void doRoute() {
            System.out.println("======== 驾车线路 start =========");
        }
    }
    
    /**
     * 骑行线路
     * @author: anonystar
     * @url: i-code.online
     * @time: 2020/6/8 17:01
     */
    public class CyclingRoute implements Route {
        @Override
        public void doRoute() {
            System.out.println("======== 骑行线路 start =========");
        }
    }
    
    /**
     * 步行线路
     * @author: anonystar
     * @url: i-code.online
     * @time: 2020/6/8 17:02
     */
    public class WalkRoute implements Route {
        @Override
        public void doRoute() {
            System.out.println("======== 步行线路 start =========");
    
        }
    }
    
    • 构建路线的上下文,作为对外使用的唯一入口,调用所有的策略均从这里使用
    package org.strategy.travel;
    
    /**
     * 构建路线 上下文
     * @author: anonystar
     * @url: i-code.online
     * @time: 2020/6/9 14:45
     */
    public class RouteContext {
    
        // 上下文会维护指向某个策略对象的引用。上下文不知晓策略的具体类。
        // 上下文必须通过策略接口来与所有策略进行交互。
        private Route route;
    
        // 上下文通常会通过构造函数来接收策略对象,
        // 同时还提供设置器以便在运行时切换策略。
        public RouteContext(Route route){
            this.route = route;
        }
        public void setRoute(Route route) {
            this.route = route;
        }
    
        // 上下文会将一些工作委派给策略对象,而不是自行实现不同版本的算法。
        public void execute(){
            route.doRoute();
        }
    }
    
    
    • 测试代码
        public void travle3(){
            String cmd = "walk";
    
            RouteContext route = null;
            if (cmd.equals(Route.ROUTE_WALK)){
                route = new RouteContext(new WalkRoute());
            }else if (cmd.equals(Route.ROUTE_CAR)){
                route = new RouteContext( new CarRoute());
            }
            route.execute();
        }
    

    上面代码我们会发现如果有很多策略时,那么会造成大量的if语句,这里我们可以使用工厂模式来进行简化,可以看我们之前的文章在i-code.online

    • 我们构建一个工厂来简化创建
    package org.strategy.travel;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 获取上下文工厂
     * @author: anonystar
     * @url: i-code.online
     * @time: 2020/6/8 17:22
     */
    public class RouteContextFactory {
    
        private static Map<String,Route> routeMap = new HashMap<>();
    
        private RouteContextFactory(){
    
        }
    
        static {
            routeMap.put(Route.ROUTE_CAR,new CarRoute());
            routeMap.put(Route.ROUTE_WALK,new WalkRoute());
            routeMap.put(Route.ROUTE_CYCLING,new CyclingRoute());
        }
    
        public static RouteContext getRoute(String cmd){
            Route route = routeMap.get(cmd);
            if ( null == route){
                route = routeMap.get(Route.ROUTE_CAR);
            }
            return new RouteContext(route);
        }
    }
    
    
    • 测试代码
     /**
         * 通过工厂方法来简化
         */
        public static void travle4(){
            String cmd = "car";
            RouteContext route = RouteContextFactory.getRoute(cmd);
            route.execute();
        }
    

    2.5 使用场景

    • 当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式。

    策略模式让你能够将对象关联至可以不同方式执行特定子任务的不同子对象, 从而以间接方式在运行时更改对象行为。

    • 当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。

    策略模式让你能将不同行为抽取到一个独立类层次结构中, 并将原始类组合成同一个, 从而减少重复代码。

    • 如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。

    策略模式让你能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来。 不同客户端可通过一个简单接口执行算法, 并能在运行时进行切换。

    • 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。

    策略模式将所有继承自同样接口的算法抽取到独立类中, 因此不再需要条件语句。 原始对象并不实现所有算法的变体, 而是将执行工作委派给其中的一个独立算法对象。

    2.6 策略模式的优缺点

    2.6.1 优点:

    • 1、策略模式符合开闭原则。
    • 2、避免使用多重条件转移语句,如 if...else... 语句、switch 语句
    • 3、使用策略模式可以提高算法的保密性和安全性。

    2.6.2 缺点:

    • 1、客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
    • 2、代码中会产生非常多策略类,增加维护难度

    本文由AnonyStar 发布,可转载但需声明原文出处。
    仰慕「优雅编码的艺术」 坚信熟能生巧,努力改变人生
    欢迎关注微信公账号 :云栖简码 获取更多优质文章
    更多文章关注笔者博客 :云栖简码

  • 相关阅读:
    OI数学知识清单
    线段树入门教程
    扩展欧几里得定理基础讲解 代码及证明
    名字竞技场 V3.0
    可持久化线段树(主席树)新手向教程
    矩阵乘法浅析
    [Luogu] P1233 木棍加工
    高斯消元 模板
    位运算技巧
    [ZJOJ] 5794 2018.08.10【2018提高组】模拟A组&省选 旅行
  • 原文地址:https://www.cnblogs.com/i-code/p/13073148.html
Copyright © 2011-2022 走看看