learn.lianglianglee.com/专栏/白话设计模式 28 讲(完)/22 深入解读过滤器模式:制作一杯鲜纯细腻的豆浆.md.html
2022-05-11 19:04:14 +08:00

758 lines
24 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>22 深入解读过滤器模式:制作一杯鲜纯细腻的豆浆.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/白话设计模式 28 讲(完)/00 生活中的设计模式:启程之前,请不要错过我.md.html">00 生活中的设计模式:启程之前,请不要错过我.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/01 监听模式:坑爹的热水器.md.html">01 监听模式:坑爹的热水器.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/02 适配模式:身高不够鞋来凑.md.html">02 适配模式:身高不够鞋来凑.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/03 状态模式:人与水的三态.md.html">03 状态模式:人与水的三态.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/04 单例模式:你是我生命的唯一.md.html">04 单例模式:你是我生命的唯一.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/05 职责模式:我的假条去哪了.md.html">05 职责模式:我的假条去哪了.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/06 中介模式:找房子问中介.md.html">06 中介模式:找房子问中介.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/07 代理模式:帮我拿一下快递.md.html">07 代理模式:帮我拿一下快递.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/08 装饰模式:你想怎么穿就怎么穿.md.html">08 装饰模式:你想怎么穿就怎么穿.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/09 工厂模式:你要拿铁还是摩卡.md.html">09 工厂模式:你要拿铁还是摩卡.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/10 迭代模式:下一个就是你了.md.html">10 迭代模式:下一个就是你了.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/11 组合模式:自己组装电脑.md.html">11 组合模式:自己组装电脑.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/12 构建模式:想要车还是庄园.md.html">12 构建模式:想要车还是庄园.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/13 克隆模式:给你一个分身术.md.html">13 克隆模式:给你一个分身术.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/14 策略模式:怎么来不重要,人到就行.md.html">14 策略模式:怎么来不重要,人到就行.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/15 命令模式:大闸蟹,走起!.md.html">15 命令模式:大闸蟹,走起!.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/16 备忘模式:好记性不如烂笔头.md.html">16 备忘模式:好记性不如烂笔头.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/17 享元模式:颜料很贵必须充分利用.md.html">17 享元模式:颜料很贵必须充分利用.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/18 外观模式:学妹别慌,学长帮你.md.html">18 外观模式:学妹别慌,学长帮你.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/19 访问模式:一千个读者一千个哈姆雷特.md.html">19 访问模式:一千个读者一千个哈姆雷特.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/20 生活中的设计模式:与经典设计模式的不解渊源.md.html">20 生活中的设计模式:与经典设计模式的不解渊源.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/21 生活中的设计模式:那些未完待续的设计模式.md.html">21 生活中的设计模式:那些未完待续的设计模式.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/白话设计模式 28 讲(完)/22 深入解读过滤器模式:制作一杯鲜纯细腻的豆浆.md.html">22 深入解读过滤器模式:制作一杯鲜纯细腻的豆浆.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/23 深入解读对象池技术:共享让生活更便捷.md.html">23 深入解读对象池技术:共享让生活更便捷.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/24 深入解读回调机制:把你技能亮出来.md.html">24 深入解读回调机制:把你技能亮出来.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/25 谈谈我对设计模式的理解.md.html">25 谈谈我对设计模式的理解.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/26 谈谈我对设计原则的思考.md.html">26 谈谈我对设计原则的思考.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/27 谈谈我对项目重构的看法.md.html">27 谈谈我对项目重构的看法.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>22 深入解读过滤器模式:制作一杯鲜纯细腻的豆浆</h1>
<p>故事剧情】</p>
<blockquote>
<p>腊八已过,粥已喝,马上就要过年了!别人家的公司现在都是开年会、发现金红包、发 iPone、发平衡车什么的而 Tony 什么也没有,只能默默地躲在朋友圈的角落里,好在最后一周还算发了一个慰问品——九阳豆浆机。</p>
<p>豆浆机已经有了怎么制作一杯鲜纯细腻的豆浆呢Tony 在网上找了一些资料,摸索了半天总算学会了,准备周末买一些大豆,自制早餐!</p>
<p>把浸泡过的大豆放进机器再加入半壶水然后选择模式并按下“启动”键15 分钟后就可以了,但这并没有完,因为还有最关键的一步,那就是往杯子倒豆浆的时候要用过滤网把豆渣过虑掉。这样,一杯美味的阳光早餐就出来了。</p>
</blockquote>
<p><img src="assets/1b943e50-8e53-11e8-80d1-2d51ff7e1c55.jpg" alt="img" /></p>
<h3>用程序来模拟生活</h3>
<p>世间万物,唯有爱与美食不可辜负,吃的健康才能活的出彩。在上面制作豆浆的过程中,豆浆机很重要,但过滤网更关键,因为它直接影响了豆桨的质量。下面我们用程序来模拟一下这关键的步骤。</p>
<p>源码示例:</p>
<pre><code class="language-python">class FilterScreen:
&quot;&quot;&quot;过滤网&quot;&quot;&quot;
def doFilter(self, rawMaterials):
for material in rawMaterials:
if (material == &quot;豆渣&quot;):
rawMaterials.remove(material)
return rawMaterials
</code></pre>
<p>测试代码:</p>
<pre><code class="language-python">def testFilterScreen():
rawMaterials = [&quot;豆浆&quot;, &quot;豆渣&quot;]
print(&quot;过滤前:&quot;, rawMaterials)
filter = FilterScreen()
filteredMaterials = filter.doFilter(rawMaterials)
print(&quot;过滤后:&quot;, filteredMaterials)
</code></pre>
<p>输出结果:</p>
<pre><code>过滤前: ['豆浆', '豆渣']
过滤后: ['豆浆']
</code></pre>
<h3>从剧情中思考过滤器模式</h3>
<p>在上面的示例中,豆浆机中有豆浆和豆渣,往杯子里倒的过程中,用过滤网把豆渣过滤掉才能获得更加鲜嫩细腻的豆浆。过滤网起着一个过滤的作用,在程序中也有一种类似的机制,叫<strong>过滤器模式</strong></p>
<h4>过滤器模式</h4>
<blockquote>
<p>过滤器模式就是将一组对象,根据某种规则,过滤掉一些不符合要求的对象的过程。</p>
</blockquote>
<p>如在互联网上发布信息时敏感词汇的过滤,在 Web 接口的请求与响应时,对请求和响应信息的过滤。过滤器模式的核心思想非常简单:就是把不需要的信息过滤掉,怎么判定哪些是不需要的信息呢?这就需要制定规则。过滤的过程如下图:</p>
<p><img src="assets/3e919c40-8e53-11e8-80d1-2d51ff7e1c55.jpg" alt="enter image description here" /></p>
<p>举一更加形象的例子,在基建行业中,沙子是最重要的原材料之一,这些沙子很多是从江河中打捞上来的,而打捞上来的不只有沙子,还有小石头和水。若要得到这些颗粒均匀的沙子,就必须把水和石头过滤掉。</p>
<h4>与职责模式的联系</h4>
<p>在《[生活中的职责模式——我的假条去哪了]》一文中,我们讲了职责模式(也就是责任链模式)。过滤器与责任链的相似之处是处理过程都是一环一环地进行,不同之处在于责任链中责任的传递一般会有一定的顺序,而过滤器通常没有这种顺序,所以过滤器会比责任链还简单。</p>
<h3>过滤器模式的模型抽象</h3>
<p>一些熟悉 Python 的读者可能会觉得上面示例中的这种写法太麻烦了Python 本身就自带了 filter() 函数。用下面这段代码就能轻松搞定,结果是一样的,但代码少了好几行:</p>
<pre><code class="language-python">def testFilter():
rawMaterials = [&quot;豆浆&quot;, &quot;豆渣&quot;]
print(&quot;过滤前:&quot;, rawMaterials)
filteredMaterials = list(filter(isSoybeanMilk, rawMaterials))
print(&quot;过滤后:&quot;, filteredMaterials)
def isSoybeanMilk(material):
return material == &quot;豆浆&quot;
</code></pre>
<p>能提出这个问题,说明你是带思考阅读本文的。之所以要这么写,有以下两个原因:</p>
<ul>
<li>Python 自带的 filter() 是函数式编程(即面向过程式的编程),而设计模式讲述的是一种面向对象的设计思想。</li>
<li>filter() 函数只是进行简单的数组中对象的过滤对于一些更复杂的需求如对不符合要求的对象不是过滤掉而是进行替换filter() 函数是难以应付的。</li>
</ul>
<h4>代码框架</h4>
<p>基于上面这些问题的思考,我们可以对过滤器模式进行进一步的重构和优化,抽象出过滤器模式的框架模型。</p>
<pre><code class="language-python">from abc import ABCMeta, abstractmethod
# 引入ABCMeta和abstractmethod来定义抽象类和抽象方法
class Filter(metaclass=ABCMeta):
&quot;&quot;&quot;过滤器&quot;&quot;&quot;
@abstractmethod
def doFilter(self, elements):
&quot;&quot;&quot;过滤方法&quot;&quot;&quot;
pass
class FilterChain(Filter):
&quot;过滤器链&quot;
def __init__(self):
self._filters = []
def addFilter(self, filter):
self._filters.append(filter)
def removeFilter(self, filter):
self._filters.remove(filter)
def doFilter(self, elements):
for filter in self._filters:
filter.doFilter(elements)
</code></pre>
<h4>类图</h4>
<p>上面的代码框架可用类图表示如下:</p>
<p><img src="assets/4cd24ac0-8e53-11e8-9cd7-51dfc6c66063.jpg" alt="enter image description here" /></p>
<p>Filter 是所有过滤器的抽象类,定义了统一的过滤接口 doFilter()。FilterA 和 FilterB 是具体的过滤器类一个类定义一个过滤规则。FilterChain 是一个过滤器链,它可以包含多个过滤器,并管理这些过滤器,在过滤对象元素时,包含的每一个过滤器都会进行一次过滤。</p>
<h4>基于框架的实现</h4>
<p>有了上面的代码框架之后,我们要实现示例代码的功能就会更简单明确了。最开始的示例代码假设它为 version 1.0,那么再看看基于框架的 version 2.0 吧。</p>
<pre><code class="language-python">class FilterScreen(Filter):
&quot;&quot;&quot;过滤网&quot;&quot;&quot;
def doFilter(self, elements):
for material in elements:
if (material == &quot;豆渣&quot;):
elements.remove(material)
return elements
</code></pre>
<p>测试代码不用变。自己跑一下,会发现输出结果和之前的是一样的。</p>
<h4>模型说明</h4>
<h5>设计要点</h5>
<p>过滤器模式中主要有三个角色,在设计过滤器模式时要找到并区分这些角色:</p>
<ul>
<li><strong>过滤的目标Target</strong> 即要被过滤的对象,通常是一个对象数组(对象列表)。</li>
<li><strong>过滤器Filter</strong> 负责过滤不需要的对象,一般一个规则对应一个类。</li>
<li><strong>过滤器链FilterChain</strong> 即过滤器的集合,负责管理和维护过滤器,用这个对象进行过滤时,它包含的每一个子过滤器都会进行一次过滤。这个类并不总是需要的,但如果有多个过滤器,有这个类将会带来极大的方便。</li>
</ul>
<h4>优缺点</h4>
<p>优点:</p>
<ul>
<li>将对象的过滤、校验逻辑抽离出来,降低系统的复杂度。</li>
<li>过滤规则可实现重复利用。</li>
</ul>
<p>缺点:性能较低,每个过滤器都会对每一个元素进行遍历。如果有 n 个元素、m 个过滤器,则复杂度为 O(mn)。</p>
<h3>实战应用</h3>
<p>我们在互联网上发布信息时,经常被进行敏感词的过滤;在表单提交的信息要以 HTML 的形式进行显示,会对一些特殊字符的进行转换。这时,我们就需要用过滤器模式对提交的信息进行过滤和处理。</p>
<p><strong>源码示例:</strong></p>
<pre><code class="language-python">import re
# 引入正则表达式库
class SensitiveFilter(Filter):
&quot;&quot;&quot;敏感词过滤&quot;&quot;&quot;
def __init__(self):
self.__sensitives = [&quot;黄色&quot;, &quot;台独&quot;, &quot;贪污&quot;]
def doFilter(self, elements):
# 敏感词列表转换成正则表达式
regex = &quot;&quot;
for word in self.__sensitives:
regex += word + &quot;|&quot;
regex = regex[0: len(regex) - 1]
# 对每个元素进行过滤
newElements = []
for element in elements:
item, num = re.subn(regex, &quot;&quot;, element)
newElements.append(item)
return newElements
class HtmlFilter(Filter):
&quot;HTML特殊字符转换&quot;
def __init__(self):
self.__wordMap = {
&quot;&amp;&quot;: &quot;&amp;amp;&quot;,
&quot;'&quot;: &quot; &amp;apos;&quot;,
&quot;&gt;&quot;: &quot;&amp;gt;&quot;,
&quot;&lt;&quot;: &quot;&amp;lt;&quot;,
&quot;\&quot;&quot;: &quot; &amp;quot;&quot;,
}
def doFilter(self, elements):
newElements = []
for element in elements:
for key, value in self.__wordMap.items():
element = element.replace(key, value)
newElements.append(element)
return newElements
</code></pre>
<p><strong>测试代码:</strong></p>
<pre><code class="language-python">def testFiltercontent():
contents = [
'有人出售黄色书:&lt;黄情味道&gt;',
'有人企图搞台独活动, ——&quot;造谣咨询&quot;',
]
print(&quot;过滤前的内容:&quot;, contents)
filterChain = FilterChain()
filterChain.addFilter(SensitiveFilter())
filterChain.addFilter(HtmlFilter())
newContents = filterChain.doFilter(contents)
print(&quot;过滤后的内容:&quot;, newContents)
</code></pre>
<p><strong>输出结果:</strong></p>
<pre><code>过滤前的内容: ['有人出售黄色书:&lt;黄情味道&gt;', '有人企图搞台独活动, ——&quot;造谣咨询&quot;']
过滤后的内容: ['有人出售书:&amp;lt;黄情味道&amp;gt;', '有人企图搞活动, —— &amp;quot;造谣咨询 &amp;quot;']
</code></pre>
<h3>应用场景</h3>
<ul>
<li>敏感词过滤、舆情监测。</li>
<li>需要对对象列表(或数据列表)进行检验、审查或预处理的场景。</li>
<li>对网络接口的请求和响应进行拦截,如对每一个请求和响应记录日志,以便日后分析。</li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/白话设计模式 28 讲(完)/21 生活中的设计模式:那些未完待续的设计模式.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/白话设计模式 28 讲(完)/23 深入解读对象池技术:共享让生活更便捷.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997bacecde3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>