【设计模式之美】策略模式实践:不同大小(采用不同的策略)文件进行排序

文章目录

  • [一. 问题与解决思路](#一. 问题与解决思路)
  • [二. 代码实现与分析](#二. 代码实现与分析)
    • [1. 业务代码逻辑的架子](#1. 业务代码逻辑的架子)
    • [2. 代码重构:使用策略模式来解耦代码逻辑](#2. 代码重构:使用策略模式来解耦代码逻辑)
  • [三. 进一步:满足开闭原则:使用注解或配置文件](#三. 进一步:满足开闭原则:使用注解或配置文件)

设计原则和思想其实比设计模式更加普适和重要,掌握了代码的设计原则和思想,我们甚至可以自己创造出来新的设计模式。---《设计模式之美》王争

一. 问题与解决思路

假设有这样一个需求,希望写一个小程序,实现对一个文件进行排序的功能。如果文件涉及到的文件有不同规模的,如下

  • 10GB 大小,因为内存有限(比如只有 8GB 大小),我们没办法一次性加载文件中的所有数据到内存中,这个时候,我们就要利用外部排序算法(ing)了。
  • 100GB 大小,我们为了利用 CPU 多核的优势,可以在外部排序的基础之上进行优化,加入多线程并发排序的功能,这就有点类似"单机版"的 MapReduce。
  • 1TB 大小,即便是单机多线程排序,这也算很慢了。这个时候,我们可以使用真正的 MapReduce 框架,利用多机的处理能力,提高排序的效率。

很明显不同大小的文件需要使用不同的算法逻辑去实现,而不同的算法逻辑就可以使用策略模式将算法逻辑解耦,具体算法实现不是本文的重点,我们来看下策略模式是如何实现这个代码架构的。

二. 代码实现与分析

1. 业务代码逻辑的架子

如下逻辑架子,简单实现了算法的一些流程步骤,具体算法暂时不实现。

java 复制代码
public class Sorter {
  private static final long GB = 1000 * 1000 * 1000;

  public void sortFile(String filePath) {
    // 省略校验逻辑
    File file = new File(filePath);
    long fileSize = file.length();
    if (fileSize < 6 * GB) { // [0, 6GB)
      quickSort(filePath);
    } else if (fileSize < 10 * GB) { // [6GB, 10GB)
      externalSort(filePath);
    } else if (fileSize < 100 * GB) { // [10GB, 100GB)
      concurrentExternalSort(filePath);
    } else { // [100GB, ~)
      mapreduceSort(filePath);
    }
  }

  private void quickSort(String filePath) {
    // 快速排序
  }

  private void externalSort(String filePath) {
    // 外部排序
  }

  private void concurrentExternalSort(String filePath) {
    // 多线程外部排序
  }

  private void mapreduceSort(String filePath) {
    // 利用MapReduce多机排序
  }
}

public class SortingTool {
  public static void main(String[] args) {
    Sorter sorter = new Sorter();
    sorter.sortFile(args[0]);
  }
}

2. 代码重构:使用策略模式来解耦代码逻辑

分析下需求和代码结构:

  • 策略定义与创建: 每种排序类都是无状态的,我们没必要在每次使用的时候,都重新创建一个新的对象。所以,我们可以使用工厂模式对对象的创建进行封装。
  • 策略的使用:这里的 if-else 逻辑分支不多、也不复杂,这样写完全没问题。当然你也可以使用查表法

接下来我们分别实现这几个方面

策略定义:

java 复制代码
public interface ISortAlg {
  void sort(String filePath);
}

public class QuickSort implements ISortAlg {
  @Override
  public void sort(String filePath) {
    //...
  }
}

...
ExternalSort implements ISortAlg
ConcurrentExternalSort implements ISortAlg
MapReduceSort implements ISortAlg 
...

策略实现:

java 复制代码
public class SortAlgFactory {
  private static final Map<String, ISortAlg> algs = new HashMap<>();

  static {
    algs.put("QuickSort", new QuickSort());
    algs.put("ExternalSort", new ExternalSort());
    algs.put("ConcurrentExternalSort", new ConcurrentExternalSort());
    algs.put("MapReduceSort", new MapReduceSort());
  }

  public static ISortAlg getSortAlg(String type) {
    if (type == null || type.isEmpty()) {
      throw new IllegalArgumentException("type should not be empty.");
    }
    return algs.get(type);
  }
}
。。。
}

策略使用:

java 复制代码
public class Sorter {
  private static final long GB = 1000 * 1000 * 1000;
  private static final List<AlgRange> algs = new ArrayList<>();
  static {
    algs.add(new AlgRange(0, 6*GB, SortAlgFactory.getSortAlg("QuickSort")));
    algs.add(new AlgRange(6*GB, 10*GB, SortAlgFactory.getSortAlg("ExternalSort")));
    algs.add(new AlgRange(10*GB, 100*GB, SortAlgFactory.getSortAlg("ConcurrentExternalSort")));
    algs.add(new AlgRange(100*GB, Long.MAX_VALUE, SortAlgFactory.getSortAlg("MapReduceSort")));
  }

  public void sortFile(String filePath) {
    // 省略校验逻辑
    File file = new File(filePath);
    long fileSize = file.length();
    ISortAlg sortAlg = null;
    for (AlgRange algRange : algs) {
    //如果处于某一个范围就选定这个,并结束循环
      if (algRange.inRange(fileSize)) {
        sortAlg = algRange.getAlg();
        break;
      }
    }
    sortAlg.sort(filePath);
  }

  private static class AlgRange {
    private long start;
    private long end;
    //AlgRange封装策略类与策略类型,这样就能去除if else
    private ISortAlg alg;

    public AlgRange(long start, long end, ISortAlg alg) {
      this.start = start;
      this.end = end;
      this.alg = alg;
    }

    public ISortAlg getAlg() {
      return alg;
    }

    public boolean inRange(long size) {
      return size >= start && size < end;
    }
  }
}

三. 进一步:满足开闭原则:使用注解或配置文件

即便这样,当我们添加新的排序算法的时候,还是需要修改策略实现与选择的代码,并不完全符合开闭原则。

有什么办法让我们完全满足开闭原则呢?

对于 Java 语言来说,我们可以通过反射来避免对策略工厂类的修改。具体是这么做的:

  1. 我们通过一个配置文件或者自定义的 annotation 来标注都有哪些策略类;策略工厂类读取配置文件或者搜索被 annotation 标注的策略类,然后通过反射动态地加载这些策略类、创建策略对象;
  2. 当我们新添加一个策略的时候,只需要将这个新添加的策略类添加到配置文件或者用 annotation 标注即可。

当添加新的排序算法时,我们只需要改动配置文件即可,不需要改动代码。

参考:《设计模式之美》--王争

相关推荐
晨米酱14 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
数据智能老司机19 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机20 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机20 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机20 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
使一颗心免于哀伤20 小时前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
数据智能老司机2 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
烛阴2 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript
李广坤2 天前
工厂模式
设计模式