zoukankan      html  css  js  c++  java
  • 扫盲:策略模式,成事儿还需要策略

    什么是策略模式?

    生活中的策略

    策略模式在生活中体现很多。

    我们要去旅游,我们可以选择不同的出行方式:飞机,火车,大巴,自驾等,这是不同的策略。

    双十一当当网购买满减活动,满 100 减 50,满 200 减 100,满 400 减 250 等,这也是不同的策略。

    抑或是我们在追求女生时,针对不同性格的女孩子采用不同的方式,这还是不同的策略。

    程序中的策略

    策略模式在程序中的体现依然淋漓尽致。

    比如我们的图片加载,Android 上有 FrescoPicassoGlideUniversal-Image-Loader 等,iOS 上有 SDWebImageAFNetworkingFastImageCache 等。

    所以,假设让你来设计一个图片加载上层框架,要求可以底层可以使用 A B 两种加载策略,你会怎么做呢?

    // 加载类A 
    public class ImageLoadServiceA {
        public void loadImage() {
            System.out.println("使用 A 加载框架");
        }
    }
    // 加载类B
    public class ImageLoadServiceB {
        public void loadImage() {
            System.out.println("使用 B 加载框架");
        }
    }
    
    // 使用
    public void loadNetImage(boolean useA) {
    	if(useA){
    		new ImageLoadServiceA().loadImage();// 使用A加载方式	
    	} else {
    		new ImageLoadServiceB().loadImage();// 使用B加载方式
    	}
    }
    
    

    可以看到,上述通过一个 useA 参数判断是否使用 A 框架,为 true 使用 A,否则使用 B 框架进行加载。

    使用简单工厂模式应对

    但假设我们现在需要再支持一个 C 框架的使用,你可能想到了,那就再加一个 boolean 参数 useB 即可,或者直接使用一个 int 参数 loadType,宏定义 0 代表 A 框架,1 代表 B 框架,2 代表 C 框架,这样如果需要增加方式则更新取值即可。

    设计模式不过是我们写程序的招式,由于之前大家可能还学习过了简单工厂模式,我们不妨在这里进行实战。

    // 抽象图片加载类 
    public abstract class ImageLoadService {
        public abstract void loadImage();
    }		
    
    // 具体加载类A 
    public class ImageLoadServiceA extends ImageLoadService {
        @Override
        public void loadImage() {
            System.out.println("使用 A 加载框架");
        }
    }
    //具体加载类B
    public class ImageLoadServiceB extends ImageLoadService {
        @Override
        public void loadImage() {
            System.out.println("使用 B 加载框架");
        }
    }
    
    //具体加载类C
    public class ImageLoadServiceC extends ImageLoadService {
        @Override
        public void loadImage() {
            System.out.println("使用 C 加载框架");
        }
    }
    
    public class ImageLoadFactory {
    	public static ImageLoadService create(int loadType) {
    	    ImageLoadService loadService = null;
    	    switch (loadType) {
    	        case 0:
    	            loadService = new ImageLoadServiceA();
    	            break;
    	        case 1:
    	            loadService = new ImageLoadServiceB();
    	            break;
    	        case 2:
    	            loadService = new ImageLoadServiceC();
    	            break;
    	    }
    	    return loadService;
    	}
    }
    
    // 使用
    public void loadNetImage(int loadType) {
        ImageLoadFactory.create(loadType).loadImage();
    }
    

    可以看到,我们使用简单工厂模式后,在处理新增其他加载方式的问题的时候,不会再去影响原有的加载类代码,如果新增一种加载方式的话,我们只需要新增 ImageLoadXXX 类,实现 loadImage() 加载方法,再修改工厂类 ImageLoadFactory 即可。

    相信你也发现了,这个方式只能解决对象的创建问题,我们每次新增方式的时候都会新增一个类,而且需要对工厂类进行代码修改,显然是违反了开闭原则。

    策略模式

    人生处处有策略,上面的不同的加载方式其实就是不同的「策略」。

    策略模式是对 算法的封装,它将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以独立变换。

    策略模式的特点

    • 是一种行为模式,对算法封装,使得客户端独立于各个策略;
    • 扩展性强,添加策略无非就是添加一个具体的实现类而已,代价非常低;

    策略模式的结构


    类图

    策略模式做实现

    要学习一个设计模式,先要学会临摹,所以上面的需求,我们可以实现为:

    1. 定义抽象策略
    public interface ImageLoadStrategy {
        void loadImage() ;
    }
    
    1. 定义具体的策略
    // 具体加载类A 
    public class ImageLoadStrategyA implements ImageLoadStrategy {
        @Override
        public void loadImage() {
            System.out.println("使用 A 加载框架");
        }
    }
    //具体加载类B
    public class ImageLoadStrategyB implements ImageLoadStrategy {
        @Override
        public void loadImage() {
            System.out.println("使用 B 加载框架");
        }
    }
    
    //具体加载类C
    public class ImageLoadStrategyC implements ImageLoadStrategy {
        @Override
        public void loadImage() {
            System.out.println("使用 C 加载框架");
        }
    }
    
    1. 定义上下文,选择方式
    public class ContextImageLoadStrategy {
    
        private ImageLoadStrategy strategy ;
    
        public ContextImageLoadStrategy(ImageLoadStrategy strategy){
            this.strategy = strategy ;
        }
    
        public void loadImage(){
            strategy.loadImage();
        }
    }
    
    1. 使用
    public void loadImage(ImageLoadStrategy imageLoadStrategy){
        ContextImageLoadStrategy contextStrategy = new ContextImageLoadStrategy(imageLoadStrategy);
        contextStrategy.loadImage();
    }	
    

    注意: 策略的核心不是如何实现算法,而是如何更优雅的把这些算法组织起来,让客户端非常好调用「虽然策略非常多,可以自由切换,但是同一时间客户端只能调用一个策略,其实也很好理解,你不可能同时既坐飞机,又坐火车」。

    策略模式的优点

    • 策略类可以互相替换
      由于策略类都实现同一个接口,因此他们能够互相替换。
    • 耦合度低,方便扩展
      增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开闭原则。
    • 避免使用多重条件选择语句(if-else 或者 switch)。

    策略模式的缺点

    • 策略的增多会导致子类的也会变多。比如上方再增加加载方式必须增加类。
    • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。比如上方必须知道有哪些加载策略,这样我们才能调用到正确的加载方式。

    你有想到如何解决「客户端必须知道所有的策略类」这个缺点么?

    策略模式的应用场景

    • 同一个问题具有不同算法时,即仅仅是具体的实现细节不同时,如各种排序算法等等。
    • 对客户隐藏具体策略(算法)的实现细节,彼此完全独立;提高算法的保密性与安全性。
    • 一个类拥有很多行为,而又需要使用 if-else 或者 switch 语句来选择具体行为时。使用策略模式把这些行为独立到具体的策略类中,可以避免多重选择的结构。

    源码中的策略模式

    想必大家已经很清楚上面的策略模式了,下面源码中用到策略模式了吗?

    • Android 的动画插值器;
    • Android 中 ListViewArrayAdapterSimpleAdapter

    写在最后

    总的来说,策略模式还算我们项目开发中会使用非常频繁的模式,你学会了么?如有疑问,请在评论区留言。

  • 相关阅读:
    [其他]将Windows Terminal添加到右键菜单
    [VS Code]在自己的Ubuntu服务器上构建VSCode Online
    [Go]goFileView-基于Golang的在线Office全家桶预览
    [Go]基于Go语言的Web路由转发,多个网站共享一个端口(新版本,支持WebSocket)
    [WSL]在Windows10子系统里安装运行桌面(xUbuntu)
    [Go]使用Golang对鸢尾花数据集进行k-means聚类
    [Python+JavaScript]JS调用摄像头并拍照,上传至tornado后端并转换为PIL的Image
    [Python]Python基于OpenCV批量提取视频中的人脸并保存
    [WSL]Windows10 Ubuntu子系统编译安装线程安全版LAMP
    [Go]基于Go语言的Web路由转发,多个网站共享一个端口(存在问题,已经抛弃,新解决方案请看新博客)
  • 原文地址:https://www.cnblogs.com/liushilin/p/14268179.html
Copyright © 2011-2022 走看看