本文将会很少涉及 dotnet 的知识,主要讲用定义过滤的方式解除过程业务的耦合。在一些业务上,可以从业务层面或逻辑层面明显分为几层,每一层之前的数据相互依赖或处理顺序相互依赖,但逻辑都独立。此时如果将业务处理放在过程处理里面,将会让过程处理耦合具体业务。而定义过滤的方式为让过程逻辑只是搭建框架为主,具体业务通过注入过滤的形式加入到处理

假设我有某个业务需要处理,这个业务分为两个大步骤,分别是 F1 步骤和 F2 步骤。而 F1 里面自然有 F1 步骤的业务,同理 F2 也一样。而我的业务上对于数据处理的过程要求比较高,在过程处理上面的逻辑相对复杂,而如果将 F1 的业务和 F2 的业务放进来,大概的逻辑会是这样

// 复杂的过程处理代码
// 通用的过程处理业务代码
// F1 业务需要处理的代码
// 其他诡异的逻辑业务代码
// 过程处理的业务代码
// F2 业务需要处理的代码
// 其他处理代码

总体看起来逻辑将会比较复杂,同时耦合度也将会比较高

读到这里的小伙伴是否有一个疑问,在什么时候就能定义出过程处理的逻辑,而其中的 F1 业务和 F2 业务是如何能定义出哪些代码是属于哪个步骤

用一个比较具体的例子说明

我需要在 WPF 中处理一个视频文件,视频文件的处理包含了视频文件本身的专业逻辑,也就是如何解码视频文件,如何将视频文件拼接为一张张图片。而处理视频文件包含了业务上的事情,业务上的事情包含以下三个部分

  1. 打开视频文件之前需要看看这个视频文件的编码格式,如果不清真就提示用户(判断格式)
  2. 每一个视频转出来的图片,在拿到数据之前需要做一点点优化,如在数据上添加一个水印(添加水印)
  3. 从图片数据转图片完成之后,需要随机拿出一些图片给用户预览(提供预览)

而如果将上面的业务逻辑混合到整个视频处理逻辑上,大概会是这个样子

  1. 打开视频文件,分批加入到内存
  2. 判断格式(业务第一个步骤)
  3. 解码拿到 HEAD 数据
  4. 加载声音
  5. 获取视频分辨率,创建空白数据
  6. 杂七杂八的专业处理逻辑
  7. 按照一秒30张图片组合出视频处理,将视频一秒拆为 30 张图片
  8. 以下为视频的每一张图片处理逻辑
  9. 解析出视频中的图片
  10. 添加水印(业务第二个步骤)
  11. 将图片做一些优化
  12. 奇特的图片处理
  13. 保存图片到文件
  14. 提供预览(业务第三个步骤)
  15. 压缩处理的所有图片
  16. 我也编不出的业务逻辑

大概来说,其实上面的逻辑可以分为三个大部分,第一个部分就是算法部分,或者说专业逻辑代码。这部分主要就是如何解码视频,如何将视频转图片以及优化图片等逻辑。这些逻辑基本都是很通用的,同时这部分逻辑也应该做到很独立。业务上使用只是调用方法传入参数而已,不应该将具体代码写入到耦合某个业务里面

第二个部分就是定义处理的过程,其实上面的逻辑应该可以分为以下过程

  1. 从文件加载到内存
  2. 解码视频
  3. 从视频转图片
  4. 处理图片
  5. 保存处理逻辑

这里面大部分步骤都是几乎做到通用的,也就是在这些步骤上只是填充专业逻辑。这部分逻辑适合创建一个 NuGet 库存放,这样在多个项目之间都能共用。但是现在的问题来了,我的业务逻辑分散在三个过程里面

  1. 文件打开后
  2. 图片处理时
  3. 图片处理完成

而其中 文件打开后 这个过程在此业务中只用一次。而后面两个过程将会根据具体视频执行多次

这里的业务逻辑就是第三个部分。这里的逻辑划分其实和代码执行顺序没有直接关系,而是根据代码逻辑的层次划分。进一步说,其实第一部分专业逻辑和第二部分定义处理的过程这两个部分不是紧密的关系。假设咱有很多不同的专业逻辑,如针对不同的视频采用不同的处理方式,但是这些处理方式之前的处理过程是差不多的,也就是第二个部分定义处理的过程部分可以独立出来,根据具体功能填写具体的专业逻辑。也就是从层次上,第二部分属于框架层面,但如果我将第三部分业务逻辑放在了整个处理逻辑里面,那么此时的功能就都无法独立

是否可以让第三部分业务逻辑分离?其实从上面说的内容,连第一部分都可以分离。假设将第二部分框架层面的部分作为框架使用,那么框架就是独立的,但是框架没有任何具体的功能。此时通过在框架各个部分填补专业逻辑可以让第一部分和第二部分联合作出实际的功能

但是想要完成功能还需要有业务的存在,此时的业务就不能写入到库的代码中。这里的库指的是如 NuGet 一样的代码库,或者说是通用代码里面,通用代码不含各个产品的具体业务

既然在第二部分已经可以定义出框架了,那么可以在框架里面应用过滤的方式进行解耦。在框架里面可以定义逻辑处理的顺序,在关键的处理里面开放注入接口。如在视频文件打开之后,此时添加一个可以注入的点,可以让业务层注入业务逻辑

而此时注入的部分的建议是注入一个接口,在框架里面定义了过程用到传入的数据,在某些处理的过程里面可以让开发者注入具体的实现类,通过接口进行约束和获取数据进行处理的方式,就是本文说的定义过滤的方式解耦

例如有简化的逻辑,我的框架的定义如下

    interface IFooHandler
    {
        void AddF1Filter(IF1Filter filter);
        void AddF2Filter(IF2Filter filter);
    }

框架里面提供了添加两个不同的业务过滤的方法,而这两个不同的业务过滤将会在整个过程的不同步骤进行调用,同时也使用两个不同的接口限定了具体的业务逻辑的注入的类的实现方式。这个方法的优点在于,可以将业务的逻辑放在具体的业务上做,而框架和库的部分只是做通用的处理逻辑。换句话说是将不通用的代码作为接口的方式提出,而在业务层进行注入,注入的方式就是调用框架给出的方法传入对应的接口实现。如上面代码,框架在两个步骤里面给出了两个可以注入的方法,通过调用框架的这两个方法传入具体实现就能做到在框架处理的过程调用注入的业务

而对比将所有逻辑都写在一起的优势在于降低耦合,原先的业务逻辑可以分散写,同时还能访问上下文更多信息。而现在只能通过约束的接口,如上面代码的 IF1Filter 定义,此时将可以让业务逻辑不影响到框架里面的逻辑,换句话是框架里面只需要了解接口的内容而不需要了解具体业务

而这个过滤的方式可以在框架里面各个地方提供,甚至框架可以通过判断有注入实际业务和没有注入的行为,如有注入业务时采用业务特殊方法替换原有的逻辑,如下面代码定义

        public void SetF1Filter(IF1Filter filter)
        {
            Filter = filter;
        }

        private IF1Filter Filter { get; set; } = DefaultFilter;

        private static readonly IF1Filter DefaultFilter = new F1Filter();

如果用户没有注入实际的逻辑,那么将会使用 DefaultFilter 的逻辑,如果用户注入了,那么将使用用户的逻辑

对于部分业务处理,如上面说到的在文件打开之后的处理,此时的处理不应该限定只有一个,于是如上面代码定义,可以让用户调用方法多次,之后进行每个用户传入的业务处理

        private static void HandleFoo(IFooHandler fooHandler)
        {
            fooHandler.AddF1Filter(new F1Filter());
            fooHandler.AddF1Filter(new F1Filter());
            fooHandler.AddF1Filter(new F1Filter());
        }

如上面代码可以让框架在 F1 步骤处理三次,虽然上面代码都是传入一样的类创建

而如果将整个注入好了逻辑的框架作为一个实例存放,在软件中的不同业务使用,此时也许会遇到某些业务需要添加一点功能,而某些业务不需要的情况,如下面代码的 fooHandle 是已经注入好了业务逻辑的实例,但是此时我需要在某个业务里面对他的处理过程进行一点更改

            var fooHandle = new FooHandler();
            HandleFoo(fooHandle);

此时的修改如果依然对 fooHandle 进行注入不加任何逻辑,那么此时将会影响到其他使用的业务,这里在 C# 里面可以采用 using 的方法,大概的写法如下

            var f1Filter = new F1Filter();
            using (f1Filter)
            {
                fooHandle.AddF1Filter(f1Filter);
                // 其他业务
            }

在本次处理完成 F1Filter 和业务之后,将会调用 f1Filter 的释放代码,在释放代码里面可以反过来调用 fooHandle 的移除添加方法

大概实现逻辑请看我的代码 这里面只是简单定义


本文会经常更新,请阅读原文: https://dotnet-campus.github.io//post/%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3-%E5%AE%9A%E4%B9%89%E8%BF%87%E6%BB%A4%E7%9A%84%E6%96%B9%E5%BC%8F%E8%A7%A3%E8%80%A6.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 lindexi (包含链接: https://dotnet-campus.github.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系