访问者模式
访问者模式
访问者模式是一种行为设计模式, 它能将算法与其所作用的对象隔离开来。
模式动机
假如某个软件团队开发了一款能够使用巨型图像中地理信息的应用程序。 图像中的每个节点既能代表复杂实体 (例如一座城市), 也能代表更精细的对象 (例如工业区和旅游景点等)。 如果节点代表的真实对象之间存在公路, 那么这些节点就会相互连接。 在程序内部, 每个节点的类型都由其所属的类来表示, 每个特定的节点则是一个对象。
一段时间后, 软件团队接到了实现将图像导出到 XML 文件中的任务,如下图所示。
图8.2.1 将图像导出为 XML。
这些工作最初看上去非常简单。 该团队计划为每个节点类添加导出函数, 然后递归执行图像中每个节点的导出函数。 解决方案简单且优雅: 使用多态机制可以让导出方法的调用代码不会和具体的节点类相耦合。
但团队的系统架构师拒绝批准对已有节点类进行修改。 他认为这些代码已经是产品了, 不想冒险对其进行修改, 因为修改可能会引入潜在的缺陷。
所有节点的类中都必须添加导出至 XML 文件的方法, 但如果在修改代码的过程中引入了任何缺陷, 那么整个程序都会面临风险。
此外, 他还质疑在节点类中包含导出 XML 文件的代码是否有意义。 这些类的主要工作是处理地理数据。 导出 XML 文件的代码放在这里并不合适。
还有另一个原因, 那就是在此项任务完成后, 营销部门很有可能会要求程序提供导出其他类型文件的功能, 或者提出其他奇怪的要求。 这样你很可能会被迫再次修改这些重要但脆弱的类。
该模式适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:
- 新功能会不会与现有功能出现兼容性问题?
- 以后会不会再需要添加?
- 如果类不允许修改代码怎么办?
面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦。
总结来说,在软件设计中,下面的情况可以使用访问者模式。
- 在实际使用时,对同一集合对象的操作并不是唯一的,对相同的元素对象可能存在多种不同的操作方式。而且这些操作方式并不稳定,可能还需要增加新的操作,以满足新的业务需求。此时,访问者模式就是一个值得考虑的解决方案。
- 访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式。
简单来说,访问者模式就是一种分离****对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。
模式定义
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。
模式结构
访问者模式包含如下角色:
- Vistor: 抽象访问者。定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的。
- ConcreteVisitor: 具体访问者。给出对每一个元素类访问时所产生的具体行为。
- Element: 抽象元素。定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
- ConcreteElement: 具体元素。提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
- ObjectStructure: 对象结构。定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。
模式时序图
模式抽象代码分析
抽象访问者类:
具体访问者类:
抽象元素:
具体元素:
对象结构:
模式分析
- 访问者模式中对象结构存储了不同类型的元素对象,以供不同访问者访问。
- 访问者模式包括两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者,一个是元素层次结构,提供了抽象元素和具体元素。
- 相同的访问者可以以不同的方式访问不同的元素,相同的元素可以接受不同访问者以不同访问方式访问。在访问者模式中,增加新的访问者无须修改原有系统,系统具有较好的可扩展性
模式实例
在本示例中,希望将一组几何形状导出到 XML文件。 存在的问题是,不能直接更改形状的代码,或者尽量少地修改形状代码。为此,使用访问者模式建立了一个基类,允许将任何行为添加到形状层次结构中,而无需更改这些类的现有代码。
Shape.java
Dot.java
Circle.java
Rectangle.java
CompoundShape.java
Visitor.java
Visitor.java
Client.java
模式优点
- 使得增加新的访问操作变得很容易。
- 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散到一个个的元素类中。
- 可以跨过类的等级结构访问属于不同的等级结构的元素类。
- 让用户能够在不修改现有类层次结构的情况下,定义该类层次结构的操作。
模式缺点
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,违背了开闭原则的要求。
- 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。
模式适用环境
- 一个对象结构包含很多类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作污染这些对象的类,也不希望在增加新操作时修改这些类。访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
模式应用
Java类库实例
javax.lang.model.element.AnnotationValue和AnnotationValueVisitor
javax.lang.model.element.Element和ElementVisitor
javax.lang.model.type.TypeMirror和TypeVisitor
java.nio.file.FileVisitor和SimpleFileVisitor
javax.faces.component.visit.VisitContext和VisitCallback
在一些编译器的设计中运用了访问者模式,程序代码是被访问的对象,它包括变量定义、变量赋值、逻辑运算、算术运算等语句,编译器需要对代码进行分析,如检查变量是否定义、变量是否赋值、算术运算是否合法等,可以将不同的操作封装在不同的类中,如检查变量定义的类、检查变量赋值的类、检查算术运算是否合法的类,这些类就是具体访问者,可以访问程序代码中不同类型的语句。在编译过程中除了代码分析外,还包含代码优化、空间分配和代码生成等部分,也可以将每一个不同编译阶段的操作封装到了跟该阶段有关的一个访问者类中。
在常用的Java XML处理技术DOM4J中,可以通过访问者模式的方式来读取并解析XML文档,VisitorSupport是DOM4J提供的Visitor接口的默认适配器,具体访问者只需继承VisitorSupport类即可。
模式扩展
\1. 与其他模式联用
- 由于访问者模式需要对对象结构进行操作,而对象结构本身是一个元素对象的集合,因此访问者模式经常需要与迭代器模式联用,在对象结构中使用迭代器来遍历元素对象。
- 在访问者模式中,元素对象可能存在容器对象和叶子对象,因此可以结合组合模式来进行设计。
\2. 访问者模式以一种倾斜的方式支持开闭原则,增加新的访问者方便,但是增加新的元素很困难。
行为型软件设计模式
行为型软件设计模式
行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。
行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。
通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象 之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。
行为型模式分为类行为型模式和对象行为型模式两种:
- 类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。
- 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。
迭代器模式
迭代器模式是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。
模式动机
集合是编程中最常使用的数据类型之一,通常可以被认为是一组对象的容器。
大部分集合使用简单列表存储元素。 但有些集合还会使用栈、 树、 图和其他复杂的数据结构,如下图所示。
集合这种数据类型虽然是一种用于存储的数据结构,但同时需要提供访问其存储数据的方法,特别是一种可以遍历其存储所有数据的方法。
也就是说,无论集合的构成方式如何, 它都必须提供某种访问元素的方式, 便于其他代码使用其中的元素。 集合应提供一种能够遍历元素的方式, 且保证它不会周而复始地访问同一个元素。
对于基于数组或者列表的集合而言, 访问或者遍历其元素可以直接通过下标完成,这种对象遍历的方法是简单且唯一的。 但对于树和图这种复杂数据结构,应该如何遍历其中的元素呢? 而且复杂数据结构的遍历方法是多样的。比如对于树形数据结构,就可以适用深度优先算法、广度优先或者随机存取等算法来遍历树结构。
一种简单的做法是,将这些遍历算法作为这些集合数据结构类的公共成员函数。但不断向集合类中添加遍历算法,首先会导致单个类的功能过于臃肿,集合数据结构类承担太多的功能,一方面提供添加和删除等功能,还要提供遍历访问功能。其次,集合数据结构类的首要功能是 “高效存储数据”,在遍历过程中,需要保存遍历状态,其和元素的添加和删除混杂在一起,容易引起混乱;添加过多的遍历算法会导致类的职责模糊问题,违反单一职责的设计原则。最后,不论是列表,还是树或者图,一般都会继承自共有的父类——泛型类,有些算法可能是根据特定应用订制的, 将其加入泛型集合类中会显得非常奇怪。
另一方面,从客户端来看,使用多种集合的客户端代码可能并不关心存储数据的方式(如同SQL数据库的使用者并不会关心底层数据库是MySql、SQL Server,还是Oracle一样),其更关心的是用这些数据结构存储数据后,如何访问(遍历)其中的所有数据。 不过由于不同的集合数据结构类提供不同的元素访问方式, 客户端代码将不得不与特定集合类进行耦合。
在这样的场景下,动态变化的是遍历数据对象的方法。一个自然而然的想法是,将遍历数据功能抽象形成单独的类,其专门负责遍历集合数据结构类中数据——这就是迭代器设计模式。
总结
- 一个聚合对象,如一个列表(List)或者一个集合(Set),应该提供一种方法来让别人可以访问它的元素,而又不需要暴露它的内部结构。
- 针对不同的需要,可能还要以不同的方式遍历整个聚合对象,但是我们并不希望在聚合对象的抽象层接口中充斥着各种不同遍历的操作。
- 在迭代器模式中,提供一个外部的迭代器来对聚合对象进行访问和遍历,迭代器定义了一个访问该聚合元素的接口,并且可以跟踪当前遍历的元素,了解哪些元素已经遍历过而哪些没有。
模式定义
迭代器模式(Iterator Pattern) :提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor)。
迭代器模式是一种对象行为型模式。
迭代器模式就是顺序访问聚集中的对象,这是集合中非常常见的一种操作。其包含两层意思:
- 需要遍历的对象,即聚集对象,
- 迭代器对象,用于对聚集对象进行遍历访问。
模式结构
模式时序图
模式抽象代码分析
- 迭代器接口
- 具体迭代器
- 集合接口声明
- 具体集合类
模式分析
- 聚合是一个管理和组织数据对象的数据结构。聚合对象主要拥有两个职责:一是存储内部数据;二是遍历内部数据。
- 将遍历聚合对象中数据的行为提取出来,封装到一个迭代器中,通过专门的迭代器来遍历聚合对象的内部数据,这就是迭代器模式的本质。迭代器模式是“单一职责原则”的完美体现。
- 在迭代器模式中应用了工厂方法模式,聚合类充当工厂类,而迭代器充当产品类,由于定义了抽象层,系统的扩展性很好,在客户端可以针对抽象聚合类和抽象迭代器进行编程。
- java编程语言的类库都已经实现了迭代器模式,因此在实际使用中很少自定义迭代器,只需要直接使用Java语言中已定义好的迭代器即可。
模式实例
在本例中, 迭代器模式用于遍历一个封装了访问微信好友关系功能的特殊集合。 该集合提供使用不同方式遍历档案资料的多个迭代器。
“好友 (friends)” 迭代器可用于遍历指定档案的好友。 “同事 (colleagues)” 迭代器也提供同样的功能, 但仅包括与目标用户在同一家公司工作的好友。 这两个迭代器都实现了同一个通用接口, 客户端能在不了解认证和发送 REST 请求等实现细节的情况下获取档案。
客户端仅通过接口与集合和迭代器交互, 也就不会同具体类耦合。 如果你决定将应用连接到全新的社交网络, 只需提供新的集合和迭代器类即可, 无需修改现有代码。
SocialNetwork.java
Webchat.java
LinkedIn.java
ProfileIterator.java
WebchatIterator.java
LinkedInIterator.java
Profile.java
SocialSpammer.java
Client.java
模式优点
- 单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 可对客户端代码和集合进行整理。
- 开闭原则。 可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。
- 可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。
- 相似的, 可以暂停遍历并在需要时继续。
模式缺点
- 如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。
- 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。
模式适用环境
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 需要为聚合对象提供多种遍历方式。
- 为遍历不同的聚合结构提供一个统一的接口。
模式应用
该模式在 Java 代码中很常见。 许多框架和程序库都会使用它来提供遍历其集合的标准方式。
\1. 下面是该模式在核心 Java 程序库中的一些示例:
java.util.Iterator的所有实现 (还有 java.util.Scanner)。
\2. JDK1.2 引入了新的Java聚合框架Collections
- Collection是所有Java聚合类的根接口。
- 在JDK类库中,Collection的iterator()方法返回一个java.util.Iterator类型的对象,而其子接口java.util.List的listIterator()方法返回一个java.util.ListIterator类型的对象,ListIterator是Iterator的子类。它们构成了Java语言对迭代器模式的支持,Java语言的java.util.Iterator接口就是迭代器模式的应用。
识别方法: 迭代器可以通过导航方法 (例如 next和 previous等) 来轻松识别。 使用迭代器的客户端代码可能没有其所遍历的集合的直接访问权限。
模式扩展
Java迭代器,•在JDK中,Iterator接口具有如下3个基本方法:
- Object next():通过反复调用next()方法可以逐个访问聚合中的元素。
- boolean hasNext():hasNext()方法用于判断聚合对象中是否还存在下一个元素,为了不抛出异常,必须在调用next()之前先调用hasNext()。如果迭代对象仍然拥有可供访问的元素,那么hasNext()返回true。
- void remove():用于删除上次调用next()时所返回的元素
总结
- 声明迭代器接口。 该接口必须提供至少一个方法来获取集合中的下个元素。 但为了使用方便, 你还可以添加一些其他方法, 例如获取前一个元素、 记录当前位置和判断迭代是否已结束。
- 声明集合接口并描述一个获取迭代器的方法。 其返回值必须是迭代器接口。 如果你计划拥有多组不同的迭代器, 则可以声明多个类似的方法。
- 为希望使用迭代器进行遍历的集合实现具体迭代器类。 迭代器对象必须与单个集合实体链接。 链接关系通常通过迭代器的构造函数建立。
- 在你的集合类中实现集合接口。 其主要思想是针对特定集合为客户端代码提供创建迭代器的快捷方式。 集合对象必须将自身传递给迭代器的构造函数来创建两者之间的链接。
- 检查客户端代码, 使用迭代器替代所有集合遍历代码。 每当客户端需要遍历集合元素时都会获取一个新的迭代器。
No module named 'pyspark'
ModuleNotFoundError: No module named ‘pyspark’
root@gu-virtual-machine:/usr/local/spark/mycode/remdup# python3 remdup.py
Traceback (most recent call last):
File “/usr/local/spark/mycode/remdup/remdup.py”, line 1, in
from pyspark import SparkContext
ModuleNotFoundError: No module named ‘pyspark’
1.找到.bashrc文件在哪
1 | /home/hadoop |
编辑环境变量
1 | export PYSPARK_HOME=/usr/local/spark |
其中```py4j-0.10.9.5-src.zip``
需要在/usr/local/spark/python/lib/
中自己找自己的是啥版本
然后
source .bashrc
chatgpt 的prompt
怎么将PySpark安装目录添加到PYTHONPATH环境变量中
https://spark.apache.org/docs/latest/api/python/getting_started/install.html
Spark入门:初级编程实践
1.pyspark交互式编程
(1) 该系总共有多少学生;
1 | lines = sc.textFile("file:///usr/local/spark/sparksqldata/Data01.txt") |
答案为:265人
(2) 该系共开设了多少门课程;
1 | lines = sc.textFile("file:///usr/local/spark/sparksqldata/Data01.txt") |
答案为8门
(3) Tom同学的总成绩平均分是多少;
1 | lines = sc.textFile("file:///usr/local/spark/sparksqldata/Data01.txt") |
Tom同学的平均分为30.8分
(4) 求每名同学的选修的课程门数;
1 | lines = sc.textFile("file:///usr/local/spark/sparksqldata/Data01.txt") |
答案共265行
(‘Lewis’, 4)
(‘Mike’, 3)
(‘Walter’, 4)
(‘Conrad’, 2)
(‘Borg’, 4)
……
(5) 该系DataBase课程共有多少人选修;
1 | lines = sc.textFile("file:///usr/local/spark/sparksqldata/Data01.txt") |
答案为126人
(6) 各门课程的平均分是多少;
1 | lines = sc.textFile("file:///usr/local/spark/sparksqldata/Data01.txt") |
答案为:
(‘ComputerNetwork’, 51.9)
(‘Software’, 50.91)
(‘DataBase’, 50.54)
(‘Algorithm’, 48.83)
(‘OperatingSystem’, 54.94)
(‘Python’, 57.82)
(‘DataStructure’, 47.57)
(‘CLanguage’, 50.61)
(7)使用累加器计算共有多少人选了DataBase这门课。
1 | lines = sc.textFile("file:///usr/local/spark/sparksqldata/Data01.txt") |
2.编写独立应用程序实现数据去重
对于两个输入文件A和B,编写Spark独立应用程序,对两个文件进行合并,并剔除其中重复的内容,得到一个新文件C。下面是输入文件和输出文件的一个样例,供参考。
输入文件A的样例如下:
20170101 x
20170102 y
20170103 x
20170104 y
20170105 z
20170106 z
输入文件B的样例如下:
20170101 y
20170102 y
20170103 x
20170104 z
20170105 y
根据输入的文件A和B合并得到的输出文件C的样例如下:
20170101 x
20170101 y
20170102 y
20170103 x
20170104 y
20170104 z
20170105 y
20170105 z
20170106 z
【参考答案】
实验答案参考步骤如下:
1 | (1)假设当前目录为/usr/local/spark/mycode/remdup,在当前目录下新建一个remdup.py文件,复制下面代码; |
1 | (2)最后在目录/usr/local/spark/mycode/remdup下执行下面命令执行程序(注意执行程序时请先退出pyspark shell,否则会出现“地址已在使用”的警告); |
Cookie和Session用户认证
什么是cookie和session
1 | http://127.0.0.1:8000/admin/list/ |
- 无状态&短链接
操作系统——进程同步——哲学家进餐问题
3-23-2023
设计模式--外观模式
django_搜索校验分页
搜索
1 | ##搜索 |
1 |
|
1 | PrettyNum.objects.filter(id=12) 等于12 |
1 | PrettyNum.objects.filter(mobile='233') 等于 PrettyNum.objects.filter(mobile__startswitch="139") 筛选出以139开头 |
1 | # use |
校验
1 | from django.core.exceptions import ValidationError |
1 | from django.core.validators import RegexValidator |
1 | class PrettyForm(forms.ModelForm): |
1 | data_dict={} |
分页
1 | qurylist=PrettyNum.objects.all()#所有的 |
1 | qurylist=PrettyNum.objects.filter(id=4)[0:10]#id为4的前十页 |
1 | qurylist=PrettyNum.objects.all()[0:10]#第一页 |
1 | qurylist=PrettyNum.objects.all()[10:20]#第二页 |
1 | qurylist=PrettyNum.objects.all()[20:30]#第三页 |
1 | page=int(request.GET.get('page',1)) |
1 | <nav aria-label="Page navigation"> |
1 | #分页 |
1 | def pretty_list(request): |
1 | {% extends 'layout.html' %} |
BootStrap样式父类
1 | from django.shortcuts import render,HttpResponse,redirect |