怎么制作一个网站内容,网站制作的合同,旅游网网站建设方案,怎么在网上推销产品文章目录 案例引入要求传统方案 介绍基本介绍应用场景登场角色尚硅谷版本《图解设计模式》版本 案例实现案例一实现拓展 案例二(个人感觉这个案例较好)实现分析拓展一拓展二拓展三 总结额外知识双重分发 文章说明 案例引入
要求
测评系统需求#xff1a;将观众分为男人和女人… 文章目录 案例引入要求传统方案 介绍基本介绍应用场景登场角色尚硅谷版本《图解设计模式》版本 案例实现案例一实现拓展 案例二(个人感觉这个案例较好)实现分析拓展一拓展二拓展三 总结额外知识双重分发 文章说明 案例引入
要求
测评系统需求将观众分为男人和女人对歌手进行测评当看完某个歌手表演后得到他们对该歌手不同的评价(比如 成功、失败 等)
传统方案 Man和Woman里面都有“成功”、“失败”的方法
【分析】
如果系统比较小这样设置是可以的但是考虑系统增加越来越多新的功能时对代码改动较大如需要增加一个新的评价方式就需要在Man和Woman类中同时添加违反了ocp原则不利于维护扩展性不好比如增加了新的人员类型或者增加新的评价都需要修改很多代码
【改进】
使用访问者模式
介绍
基本介绍
在数据结构中保存着许多元素我们会对这些元素进行“处理”。这时“处理”代码放在哪里比较好呢通常的做法是将它们放在表示数据结构的类中。但是如果“处理”有许多种呢这种情况下每当增加一种处理我们就不得不去修改表示数据结构的类。在Visitor模式中数据结构与处理被分离开来。我们编写一个表示“访问者”的类来访问数据结构中的元素并把对各元素的处理交给访问者类。这样当需要增加新的处理时我们只需要编写新的访问者然后让数据结构可以接受访问者的访问即可即访问者模式主要将数据结构与数据操作分离解决数据结构和操作耦合性问题访问者模式的基本工作原理是: 在被访问的类里面提供一个对外接待访问者的接口
应用场景
需要对一个对象结构中的对象进行很多不同操作而且这些操作彼此没有关联需要避免让这些操作污染这些对象的类可以选用访问者模式解决
登场角色 Visitor 是抽象访问者为该对象结构中的ConcreteElement的每一个类声明一个visit方法ConcreteVisitor是一个具体的访问者实现Visitor声明的每个方法ObjectStructure能枚举它的元素可以提供一个高层的接口用来允许访问者访问元素比如案例一的ObjectStructure类的display方法Element定义一个accept 方法接收一个访问者对象ConcreteElement为具体元素实现了accept 方法
尚硅谷版本
《图解设计模式》版本 Visitor(访问者)Visitor角色负责对数据结构中每个具体的元素(ConcreteElement角色)声明一个用于访问XXXXX的visit(XXXXX)方法。visit(XXXXX)是用于处理XXXXX的方法负责实现该方法的是ConcreteVisitor角色ConcreteVisitor(具体的访问者)ConcreteVisitor角色负责实现 Visitor角色所定义的接口(API)。它要实现所有的visit(XXXXX)方法即实现如何处理每个ConcreteElement角色Element(元素)Element角色表示Visitor角色的访问对象。它声明了接受访问者的accept方法。accept 方法接收到的参数是Visitor角色ConcreteElement(具体元素)ConcreteElement角色负责实现Element角色所定义的接口(API)ObjectStructure(对象数据结构)ObjectStructur角色负责处理Element角色的集合能够枚举它的元素案例二的Directory类同时扮演该角色和ConcreteElement角色
案例实现
案例一 实现
【ActionVisitor】
package com.atguigu.visitor;public abstract class Action {/*** 得到男性 的测评* param man*/public abstract void getManResult(Man man);/*** 得到女性 的测评* param woman*/public abstract void getWomanResult(Woman woman);
}【SuccessConcreteVisitor】
package com.atguigu.visitor;public class Success extends Action {Overridepublic void getManResult(Man man) {System.out.println( 男人给的评价该歌手很成功 !);}Overridepublic void getWomanResult(Woman woman) {System.out.println( 女人给的评价该歌手很成功 !);}}【FailConcreteVisitor】
package com.atguigu.visitor;public class Fail extends Action {Overridepublic void getManResult(Man man) {System.out.println( 男人给的评价该歌手失败 !);}Overridepublic void getWomanResult(Woman woman) {System.out.println( 女人给的评价该歌手失败 !);}}【PersonElement】
package com.atguigu.visitor;public abstract class Person {/*** 提供一个方法让访问者可以访问** param action*/public abstract void accept(Action action);
}【WomanConcreteElement 】
package com.atguigu.visitor;/*** 这里我们使用到了双分派, 即首先在客户端程序中将具体状态作为参数传递到Woman中(第一次分派)* 然后 Woman 类调用作为参数的 具体方法 中方法getWomanResult, 同时将自己(this)作为参数传入完成第二次的分派* 即互为对方方法的参数*/
public class Woman extends Person{Overridepublic void accept(Action action) {action.getWomanResult(this);}}【ManConcreteElement 】
package com.atguigu.visitor;public class Man extends Person {Overridepublic void accept(Action action) {// 自己认为是什么结果就是什么结果action.getManResult(this);}}【ObjectStructure】
package com.atguigu.visitor;import java.util.LinkedList;
import java.util.List;/*** 数据结构管理很多人Man , Woman*/
public class ObjectStructure {/*** 维护了一个集合*/private ListPerson persons new LinkedList();/*** 将元素增加到list在访问者模式中一般使用attach不使用add** param p*/public void attach(Person p) {persons.add(p);}/*** 移除** param p*/public void detach(Person p) {persons.remove(p);}/*** 显示测评情况* param action*/public void display(Action action) {for (Person p : persons) {p.accept(action);}}
}【客户端】
package com.atguigu.visitor;public class Client {public static void main(String[] args) {//创建ObjectStructureSystem.out.println(添加观众);ObjectStructure objectStructure new ObjectStructure();objectStructure.attach(new Man());objectStructure.attach(new Woman());//成功System.out.println(测评结果是成功晋级);Success success new Success();objectStructure.display(success);System.out.println(测评结果是失败);Fail fail new Fail();objectStructure.display(fail);}}【运行】
添加观众
测评结果是成功晋级男人给的评价该歌手很成功 !女人给的评价该歌手很成功 !
测评结果是失败男人给的评价该歌手失败 !女人给的评价该歌手失败 !Process finished with exit code 0拓展
上面的程序使用了双重分发所谓双重分发是指不管类怎么变化我们都能找到期望的方法运行。双重分发意味着得到执行的操作取决于请求的种类和两个接收者的类型。假设我们要添加一个Wait的状态类考察Man类和Woman类的反应由于使用了双重分发只需增加一个Action子类即可在客户端调用即可不需要改动任何其他类的代码
【增加类Wait】
package com.atguigu.visitor;public class Wait extends Action {Overridepublic void getManResult(Man man) {System.out.println( 男人给的评价是该歌手待定 ..);}Overridepublic void getWomanResult(Woman woman) {System.out.println( 女人给的评价是该歌手待定 ..);}}【客户端】
package com.atguigu.visitor;public class Client {public static void main(String[] args) {//创建ObjectStructureSystem.out.println(添加观众);ObjectStructure objectStructure new ObjectStructure();objectStructure.attach(new Man());objectStructure.attach(new Woman());System.out.println(测评结果是待定);Wait wait new Wait();objectStructure.display(wait);}}【运行】
添加观众
测评结果是待定男人给的评价是该歌手待定 ..女人给的评价是该歌手待定 ..Process finished with exit code 0案例二(个人感觉这个案例较好) 实现
【访问者抽象类】
package com.atguigu.visitor.Sample;/*** 访问者抽象类* 依赖要访问的数据结构File和Directory*/
public abstract class Visitor {/*** 访问File类的方法** param file*/public abstract void visit(File file);/*** 访问Directory类的方法** param directory*/public abstract void visit(Directory directory);
}【接受访问的接口】
package com.atguigu.visitor.Sample;/*** 接受访问的接口*/
public interface Element {/*** 接受访问** param v*/public abstract void accept(Visitor v);
}【接受访问的抽象类】
这个类不需要实现accept方法因为不是最终被访问的类
package com.atguigu.visitor.Sample;import java.util.Iterator;public abstract class Entry implements Element {/*** 获取名字** return*/public abstract String getName();/*** 获取大小** return*/public abstract int getSize();/*** 增加目录条目** param entry* return* throws FileTreatmentException*/public Entry add(Entry entry) throws FileTreatmentException {// 对 Directory 才有效这里先简单报个错让它自己重写一遍throw new FileTreatmentException();}/*** 生成Iterator** return* throws FileTreatmentException*/public Iterator iterator() throws FileTreatmentException {// 对 Directory 才有效这里先简单报个错让它自己重写一遍throw new FileTreatmentException();}/*** 显示字符串** return*/public String toString() {return getName() ( getSize() );}
}【异常类】
package com.atguigu.visitor.Sample;public class FileTreatmentException extends RuntimeException {public FileTreatmentException() {}public FileTreatmentException(String msg) {super(msg);}
}【接受访问的具体类FileConcreteElement角色】
package com.atguigu.visitor.Sample;public class File extends Entry {private String name;private int size;public File(String name, int size) {this.name name;this.size size;}public String getName() {return name;}public int getSize() {return size;}public void accept(Visitor v) {// 把自己交给访问者访问v.visit(this);}
}【接受访问的具体类DirectoryConcreteElement角色、ObjectStructure角色】
package com.atguigu.visitor.Sample;import java.util.ArrayList;
import java.util.Iterator;public class Directory extends Entry {/*** 文件夹名字*/private String name;/*** 目录条目集合*/private ArrayList dir new ArrayList();/*** 构造函数* param name*/public Directory(String name) {this.name name;}/*** 获取名字* return*/public String getName() {return name;}/*** 获取大小* return*/public int getSize() {int size 0;Iterator it dir.iterator();while (it.hasNext()) {Entry entry (Entry) it.next();size entry.getSize();}return size;}/*** 增加目录条目* param entry* return*/public Entry add(Entry entry) {dir.add(entry);return this;}/*** 生成Iterator* return*/public Iterator iterator() {return dir.iterator();}/*** 接受访问者的访问* param v*/public void accept(Visitor v) {v.visit(this);}
}【具体访问者】
package com.atguigu.visitor.Sample;import java.util.Iterator;public class ListVisitor extends Visitor {/*** 当前访问的文件夹的名字*/private String currentdir ;/*** 在访问文件时被调用* 访问的是文件就简单输出一下文件的信息* param file*/public void visit(File file) {System.out.println(currentdir / file);}/*** 在访问文件夹时被调用* 访问的是文件夹不仅输出文件夹的信息还要递归输出子文件和子文件夹的相关信息* param directory*/public void visit(Directory directory) {System.out.println(currentdir / directory);String savedir currentdir;currentdir currentdir / directory.getName();Iterator it directory.iterator();while (it.hasNext()) {Entry entry (Entry) it.next();// 继续让当前访问者访问子文件或者文件夹entry.accept(this);}currentdir savedir;}
}【主类】
package com.atguigu.visitor.Sample;public class Main {public static void main(String[] args) {try {System.out.println(Making root entries...);Directory rootdir new Directory(root);Directory bindir new Directory(bin);Directory tmpdir new Directory(tmp);Directory usrdir new Directory(usr);rootdir.add(bindir);rootdir.add(tmpdir);rootdir.add(usrdir);bindir.add(new File(vi, 10000));bindir.add(new File(latex, 20000));// 接受访问打印整个根目录下面的所有文件信息rootdir.accept(new ListVisitor()); System.out.println();System.out.println(Making user entries...);Directory yuki new Directory(yuki);Directory hanako new Directory(hanako);Directory tomura new Directory(tomura);usrdir.add(yuki);usrdir.add(hanako);usrdir.add(tomura);yuki.add(new File(diary.html, 100));yuki.add(new File(Composite.java, 200));hanako.add(new File(memo.tex, 300));tomura.add(new File(game.doc, 400));tomura.add(new File(junk.mail, 500));// 接受访问打印整个根目录下面的所有文件信息rootdir.accept(new ListVisitor()); } catch (FileTreatmentException e) {e.printStackTrace();}}
}【运行】
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)Process finished with exit code 0分析
ConcreteVisitor 角色的开发可以独立于File类和Directory类。也就是说Visitor模式提高了File类和Directory类作为组件的独立性。如果将进行处理的方法定义在File类和Directory类中当每次要扩展功能增加新的“处理”时就不得不去修改File类和Directory类
拓展一
在示例程序中增加一个FileFindvistor类用于将带有指定后缀名的文件手机起来存储到集合中
【FileFindvistor】
package com.atguigu.visitor.A1;import java.util.ArrayList;
import java.util.Iterator;public class FileFindVisitor extends Visitor {private String filetype;private ArrayList found new ArrayList();/*** 指定.后面的文件后缀名如.txt** param filetype*/public FileFindVisitor(String filetype) {this.filetype filetype;}/*** 获取已经找到的文件** return*/public Iterator getFoundFiles() {return found.iterator();}/*** 在访问文件时被调用** param file*/public void visit(File file) {if (file.getName().endsWith(filetype)) {// 将符合格式的文件添加到集合中found.add(file);}}/*** 在访问文件夹时被调用** param directory*/public void visit(Directory directory) {Iterator it directory.iterator();while (it.hasNext()) {Entry entry (Entry) it.next();entry.accept(this);}}
}【主类】
package com.atguigu.visitor.A1;import java.util.Iterator;public class Main {public static void main(String[] args) {try {Directory rootdir new Directory(root);Directory bindir new Directory(bin);Directory tmpdir new Directory(tmp);Directory usrdir new Directory(usr);rootdir.add(bindir);rootdir.add(tmpdir);rootdir.add(usrdir);bindir.add(new File(vi, 10000));bindir.add(new File(latex, 20000));Directory yuki new Directory(yuki);Directory hanako new Directory(hanako);Directory tomura new Directory(tomura);usrdir.add(yuki);usrdir.add(hanako);usrdir.add(tomura);yuki.add(new File(diary.html, 100));yuki.add(new File(Composite.java, 200));hanako.add(new File(memo.tex, 300));hanako.add(new File(index.html, 350));tomura.add(new File(game.doc, 400));tomura.add(new File(junk.mail, 500));// 筛选出.html结尾的文件FileFindVisitor ffv new FileFindVisitor(.html); rootdir.accept(ffv);// 输出.html结尾的文件System.out.println(HTML files are:);Iterator it ffv.getFoundFiles(); while (it.hasNext()) { File file (File)it.next(); System.out.println(file.toString());} } catch (FileTreatmentException e) {e.printStackTrace();}}
}【运行】
HTML files are:
diary.html (100)
index.html (350)Process finished with exit code 0拓展二
Directory类的getSize方法的作用是获取文件夹大小请编写一个获取大小的SizeVisitor类用它替换掉 Directory类的getSize方法
【SizeVisitor】
import java.util.Iterator;public class SizeVisitor extends Visitor {private int size 0;public int getSize() {return size;}public void visit(File file) {size file.getSize();}public void visit(Directory directory) {Iterator it directory.iterator();while (it.hasNext()) {Entry entry (Entry) it.next();entry.accept(this);}}
}【修改Directory的方法】
package com.atguigu.visitor.A2;import java.util.ArrayList;
import java.util.Iterator;public class Directory extends Entry {private String name;private ArrayList dir new ArrayList();public Directory(String name) { // 构造函数this.name name;}public String getName() { // 获取名字return name;}public int getSize() {// 使用visitor来替换原来的方式SizeVisitor v new SizeVisitor();accept(v);return v.getSize();}public Entry add(Entry entry) {dir.add(entry);return this;}public Iterator iterator() {return dir.iterator();}public void accept(Visitor v) {v.visit(this);}
}拓展三
基于java.util.ArrayList类编写一个具有Element接口的ElementArrayList类使得Directory类和File类可以被add至ElementArrayList 中而且它还可以接受(accept) ListVisitor 的实例访问它
【ElementArrayList】
package com.atguigu.visitor.A3;import java.util.ArrayList;
import java.util.Iterator;/*** 继承ArrayList这样就不用定义集合的add remove等操作*/
class ElementArrayList extends ArrayList implements Element {public void accept(Visitor v) {// 使用迭代器遍历Iterator it iterator();while (it.hasNext()) {Element e (Element)it.next();e.accept(v);}}
}由于visit方法不用传入ElementArrayList类作为参数因此不用修改Visitor
【主类】
package com.atguigu.visitor.A3;public class Main {public static void main(String[] args) {try {Directory root1 new Directory(root1);root1.add(new File(diary.html, 10));root1.add(new File(index.html, 20));Directory root2 new Directory(root2);root2.add(new File(diary.html, 1000));root2.add(new File(index.html, 2000));ElementArrayList list new ElementArrayList();list.add(root1);list.add(root2);list.add(new File(etc.html, 1234));list.accept(new ListVisitor());} catch (FileTreatmentException e) {e.printStackTrace();}}
}【运行】
/root1 (30)
/root1/diary.html (10)
/root1/index.html (20)
/root2 (3000)
/root2/diary.html (1000)
/root2/index.html (2000)
/etc.html (1234)Process finished with exit code 0总结
【优点】
访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高访问者模式可以对功能进行统一可以做报表、UI、拦截器与过滤器适用于数据结构相对稳定的系统如果一个系统有比较稳定的数据结构又有经常变化的功能需求那么访问者模式就是比较合适的易于增加 ConcreteVisitor 角色
【缺点】
具体元素对访问者公布细节也就是访问者关注其他类的内部细节如Success里面传入了Man且调用其accept方法这是迪米特法则不建议的这样造成了具体元素变更比较困难违背了依赖倒转原则。访问者依赖的是具体元素而不是抽象元素Action里面依赖的是Man和Woman而不是Person难以增加ConcreteElement 角色。一旦增加了ConcreteElement 角色需要在Visitor类中声明新的visit方法而且所有的ConcreteVisitor都需要实现这个方法
额外知识
双重分发
// accept (接受)方法的调用方式
element.accept(visitor);// visit(访问)方法的调用方式
visitor.visit(element);ConcreteElement和ConcreteVisitor这两个角色互相调用共同决定了实际进行的处理
文章说明
本文章为本人学习尚硅谷的学习笔记文章中大部分内容来源于尚硅谷视频点击学习尚硅谷相关课程也有部分内容来自于自己的思考发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识如有侵权请联系删除最后对尚硅谷的优质课程表示感谢。本人还同步阅读《图解设计模式》书籍图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社2017.1进而综合两者的内容让知识点更加全面