什么是设计模式
记录一个设计模式需有四个基本要素:
- 名称:一个模式的名称高度概括该模式的本质,有利于该行业统一术语、便于交流使用(简单地说,见名知义。)
- 问题:描述应该在何时使用模式,解释设计问题和问题存在的前因后果,描述在怎样的环境下使用该模式。
- 方案:描述设计的组成部分、它们之间的相互关系及各自的职责和写作方式。
- 效果:描述模式的应用效果及使用模式应当权衡的问题。主要效果包括使用模式对系统的灵活性、扩展性和复用性的影响。
(一)、策略模式
策略模式:定义一系列算法(方法),把它们一个个封装起来,并且使它们可以相互替换。
结构中包含以下3种角色:
- 策略(Strategy):策略是一个接口,该接口定义若干个算法标识,即定义了若干个抽象方法。
- 具体策略(ConcreteStrategy):实现策略接口中的类及所定义的抽象方法。
- 上下文(Context):上下文是依赖于策略接口的类,即上下文包含策略声明的变量。上下文提供一个方法,用于委托策略变量调用具体策略所实现的策略几口中的方法。
策略模式的类图:
例子:
策略接口
public interface Strategy {
void algorithm();
}
具体策略
class ConcreateStrategyA implements Strategy{
@Override
public void algorithm() {
System.out.println("A");
}
}
class ConcreateStrategyB implements Strategy{
@Override
public void algorithm() {
System.out.println("B");
}
}
上下文
public class Context {
Strategy strategy;
void setStrategy(Strategy strategy){
this.strategy = strategy;
}
//定义一个方法,并委托策略变量调用具体策略中的所实现的方法
void lookAlgorithm(){
strategy.algorithm();
}
}
client
public static void main(String[] args) {
Context con = new Context();
Strategy stra;
stra = new CreateStrategyA();
con.setStrategy(stra);
con.algorithm();
}
1.策略模式的优点:
- 上下文和策略模式是松耦合关系。因此上下文只知道它要使用某一实现Strategy接口的实例,但不需要知道具体是哪一类。
- 策略模式满足“开-闭”原则。当增加新的具体策略时,不需要修改上下文类的代码,上下文就可以引用新的具体策略的实例。
2.适合使用策略模式的情景:
- 一个类定义了多种行为,并且这些行为在这个类的方法中以多个条件语句的形式出现,那么可以使用策略模式来避免在类中大量使用条件语句。
- 程序主要的类(上下文)不希望暴露复杂的、与算法相关的数据结构
- 需要使用一个算法的不同变体。
(二)、状态模式
状态模式:允许一个对象在其 内部状态 改变时改变它的行为,对象看起来修改了它的类。
状态模式包含3种角色:
- 抽象状态(State):抽象状态是一个接口或抽象类,定义了与环境的一个特定状态相关的若干个方法。
- 具体状态(Concreate State):具体状态是实现(扩展)抽象状态的类。
- 环境(Context):环境是一个类,该类含有抽象状态声明的变量,可以引用任何具体状态类的实例
状态模式的类图:
例子:
抽象状态
public abstract class State {
public abstract void handle();
}
具体状态
class ConcreateStateA extends State {
@Override
public void handle() {
System.out.println("A");
}
}
class ConcreateStateB extends State {
@Override
public void handle() {
System.out.println("B");
}
}
环境
public class Context {
State state;
public void setState(State state) {
this.state = state;
}
public void request(){
state.handle();
}
}
Client
public class Client {
public static void main(String[] args) {
Context context = new Context();
State state = new ConcreateStateB();
context.setState(state);
context.request();
}
}
1.状态模式的优点
- 使用一个类封装对象的一种状态,很容易增加新的状态
- 在状态模式中,环境中不必出现大量的条件判断语句。环境实例所呈现的状态变得更加清晰、容易理解。
- 使用状态模式可以让用户程序很方便地切换环境实例的状态。
- 不会让环境的实例中出现内部状态不一致的情况。
2.适合使用状态模式的情景
- 一个对象的行为依赖于它的状态,并且它必须在运行时刻根据状态改变它的行为。
- 需要编写大量的条件分支语句来决定一个操作的行为,而且这些条件恰好表示对象的一种状态
策略模式 VS 状态模式
核心
策略模式:核心是将一系列的操作拆分成可若干可单独重复使用的轮子,特定条件下直接选取其中一个使用,而不是传递条件,使用if else来进行条件判断以执行相应的操作。
状态模式:核心是将对象每一个状态做的事情分别交给每一个单独的状态对象处理,并且由状态自己控制向其他状态的转移;行为类仅向外提供方便用户使用的接口;
对扩展状态不是特别友好,需要修改其他状态的转移。其次其实现比较灵活,用不好容易出错。
相同点
两者通过将行为和状态拆分成一系列小的组件,由条件和状态进行功能更替,这样符合开闭原则,便于扩展。此外均可作为if else或者分支的替换方案;支持的最大行为和状态均有限;
不同点
- 策略模式中,类的功能是根据当前条件主动更改;
- 状态模式中,类的功能是被动由当前状态更改;
- 策略模式中每个行为或算法之间没有关联;
- 状态模式中的状态之间有关联,并且状态本身控制着状态转移;
更多可参考:策略模式和状态模式的区别
(三)命令模式
命令模式(别名:动作、事务):讲一个请求封装为一个对象,从而使你可用不同的请求对客服进行参数化,对请求排队或记录日志,以及支持可撤销的操作。
命令模式的结构中包括4种角色:
- 命令接口(Command):命令是一个接口,规定了用来封装“请求”的若干个方法。
- 具体命令(ConcreateCommeand):具体命令是实现命令接口的类的实例。
- 请求者(Invoker):请求者是一个包含Command接口变量的类的实例。请求者中的Command接口的变量可以存放任何具体命令的引用。请求者负责调用具体命令,让具体命令执行那些封装了“请求”的方法。
- 接受者(Receiver):接收者是一个类的实例,该实例负责执行与请求相关的操作。
命令模式的类图:
例子:
命令接口
public interface Command {
void execute();
}
请求者
public class Invoker {
Command command;
public void setCommand(Command command) {
this.command = command;
}
public void startExecuteCommand(){
command.execute();
}
}
接受者
public class ReceiverA {
public void attack(){
System.out.println("炮火攻击");
}
}
public class ReceiverB {
public void attack(){
System.out.println("埋雷");
}
}
具体命令
public class ConcreteCommandA implements Command{
ReceiverA receiverA;
ConcreteCommandA(ReceiverA receiverA){
this.receiverA = receiverA;
}
@Override
public void execute() {
receiverA.attack();
}
}
public class ConcreteCommandB implements Command{
ReceiverB receiverB;
ConcreteCommandB(ReceiverB receiverB){
this.receiverB = receiverB;
}
@Override
public void execute() {
receiverB.attack();
}
}
application
public class application {
public static void main(String[] args) {
//接受者
ReceiverA receiverA = new ReceiverA();
ReceiverB receiverB = new ReceiverB();
//请求者
Invoker invoker = new Invoker();
//具体命令
Command command = new ConcreteCommandA(receiverA);
invoker.setCommand(command);
invoker.startExecuteCommand();
command = new ConcreteCommandB(receiverB);
invoker.setCommand(command);
invoker.startExecuteCommand();
}
}
1.命令模式的优点
(1)在命令模式当中,请求者不直接与接受者交互,即请求者不包含接受者的引用。因此彻底消除彼此之间的耦合
(2)满足“开-闭”原则。如果增加新的具体命令和该命令的接受者,不必修改调用者的代码,调用者就可以使用新的命令对象;反之,如果增加新的调用者,不必修改现有的具体命令和接受者,新增的调用者就可以使用已有的具体命令。
(3)由于请求者的请求被封装到具体命令中,那么就可以将具体命令保存到持久化媒介中,在需要的时候,重新执行这个具体命令。因此,使用命令模式可以记录日志。
(4)使用命令模式可以对请求者的“请求”进行排队。每个请求都各自对应一个具体命令,因此可以按照一定的顺序执行这些具体命令。
2.适合使用命令模式的情景:
(1)程序需要在不同的时刻指定、排列和请求,即在不同的时刻指定、排列和执行命令对象。
(2)程序需要提供撤销操作
(四)中介者者模式
中介者模式:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式的结构包含4种角色:
- 中介者(Mediator):中介者是一个接口,该接口定义了用于对同事(Colleague)对象之间进行同信的方法。
- 具体中介者(ConcreteMediator):具体中介者是实现中介者接口的类。要允许具体中介者包含所有具体同事(ConcreteColleague)的引用,比如允许具体中介者通过组合或方法的参数来调用任何一个同事,并通过实现中介者接口中的方法来满足具体同事之间的通信请求。
- 同事(Colleague):同事是一个接口,规定具体同事需要实现的方法。
- 具体同事(ConcreteColleague):具体同事是实现同事接口的类。具体同事需要包含具体中介者的引用,一个具体同事需要和其他同事交互时,只需将自己的请求通知给它所包含的具体中介者即可
中介者模式的类图:
例子:
中介者
public interface Mediator {
void registerColleague(Colleague colleague);
void deliverMess(String mess,String ...person);
}
同事
public interface Colleague {
void giveMess(String mess,String ...person);
void receiverMess(String s);
void setName(String s);
String getName();
void setMediator(Mediator mediator);
}
具体中介者
public class ConcreteMediator implements Mediator{
ArrayList<Colleague> list;
ConcreteMediator(){
list = new ArrayList<>();
}
@Override
public void registerColleague(Colleague colleague) {
list.add(colleague);
}
@Override
public void deliverMess(String mess, String... person) {
Colleague colleague;
for(int i = 0;i < person.length;i++){
for(int j = 0;j < list.size();j++){
colleague = list.get(j);
if(colleague.getName().equals(person[i]))
colleague.receiverMess(mess);
}
}
}
}
具体同事
public class ConcreteColleague implements Colleague{
Mediator mediator;
String name;
@Override
public void giveMess(String mess, String... person) {
String s = name + "给出的信息," + mess;
mediator.deliverMess(s,person);
}
@Override
public void receiverMess(String s) {
System.out.println(name + "收到:" + s);
}
@Override
public void setName(String s) {
this.name = s;
}
@Override
public String getName() {
return name;
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
mediator.registerColleague(this);
}
}
Application
public class Application {
public static void main(String[] args) {
Mediator mediator = new ConcreteMediator();
Colleague A = new ConcreteColleague();
Colleague B = new ConcreteColleague();
Colleague C = new ConcreteColleague();
A.setMediator(mediator);
B.setMediator(mediator);
C.setMediator(mediator);
A.setName("A");
B.setName("B");
C.setName("C");
A.giveMess("出租","B","C");
B.giveMess("求租","A");
C.giveMess("求租","A");
}
}
运行效果图:
1.中介者模式的优点
(1)可以避免许多对象为了之间的通信而相互显式引用,不仅系统难以维护,而且也使其它系统难以复用这些对象
(2)可以通过中介者将原本分布于多个对象之间的交互行为集中在一起。当这些对象之间需要改变之间的通信行为时,只需使用一个具体中介者即可,不必修改各个具体同事的代码,即这些同事可被重用。
(3)具体中介者使得各个具体同事完全解耦,修改任何一个具体同事的代码不会影响其他同事。
(4)具体中介者集中了同事之间是如何交互的细节,使得系统比较清楚地知道整个系统中的同事是如何交互的。
(5)当一些对象想互相通信,但又无法相互包含对方的引用,那么中介者模式就可以使得这些对象互相通信。
注意:由于具体中介者集中了同事之间是如何交互的细节,可能使得具体中介者变得非常复杂,增加了维护的难度。
2.适合使用中介者模式的情景
(1)许多对象以复杂的方式交互,所导致的依赖关系使系统难以理解和维护。
(2) 一个对象引用其他很多对象,导致难以复用该对象。
(五)责任链模式
责任链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
责任链模式的结构中包括2种角色:
- 处理者(Handler):处理者是一个接口,负责规定具体处理者处理用户的请求的方法以及具体处理者设置后继对象的方法。
- 具体处理者(ConcreteHandler):具体处理者是实现处理者接口的类的实例。具体处理者通过调用处理者接口规定的方法处理用户的请求,即在接到用户的请求后,处理者将调用接口规定的方法,在执行该方法的过程中,如果发现能处理用户的请求则处理,否则就反馈无法处理的信息给用户,然后将用户的请求传递给自己的后继对象。
责任链模式的类图:
例子:
处理者:
public interface Handler {
void handlerRequest(String number);
void setNextHandler(Handler handler);
}
具体处理者:
public class Beijing implements Handler{
private Handler handler;
private ArrayList<String> numberList;//存放号码,实际项目应该是数据库
Beijing(){
numberList = new ArrayList<>();
numberList.add("京KD123");
numberList.add("京KD124");
numberList.add("京KD125");
}
@Override
public void handlerRequest(String number) {
if(numberList.contains(number)){
System.out.println(number + " 属于北京地区 yes");
}else {
System.out.println(number + " 不属于北京地区 no 下个请求");
if(handler != null)
handler.handlerRequest(number);
}
}
@Override
public void setNextHandler(Handler handler) {
this.handler = handler;
}
}
public class Shanghai implements Handler{
private Handler handler;
private ArrayList<String> numberList;//存放号码,实际项目应该是数据库
Shanghai(){
numberList = new ArrayList<>();
numberList.add("泸HJ123");
numberList.add("泸HJ124");
numberList.add("泸HJ125");
}
@Override
public void handlerRequest(String number) {
if(numberList.contains(number)){
System.out.println(number + " 属于上海地区 yes");
}else {
System.out.println(number + " 不属于上海地区 no 下个请求");
if(handler != null)
handler.handlerRequest(number);
}
}
@Override
public void setNextHandler(Handler handler) {
this.handler = handler;
}
}
Applicat
public class Application {
public static void main(String[] args) {
Handler beijing,shanghai;
beijing = new Beijing();
shanghai = new Shanghai();
beijing.setNextHandler(shanghai);
//shanghai.setNextHandler(tianjin);
beijing.handlerRequest("京KD123");
beijing.handlerRequest("泸HJ123");
}
}
运行效果图
1.责任链模式的优点
(1)责任链中的对象只和自己的后继是弱耦合关系,和其他对象毫无关联,这使得编写处理者对象以及创建责任链变得非常容易。
(2)当在处理者中分配职责时,责任链给应用程序更多的灵活性。
(3)应用程序可以动态的改变处理者之间的先后顺序。
(4)应用程序可以动态地增加、删除处理者或重新指派处理者的职责。
(5)使用责任链的用户不必知道处理者的信息,用户不会知道到底是哪个对象处理了它的请求。
2.适合使用责任链模式的情景
(1)有许多对象可以处理用户的请求,希望程序在运行期间自动确定处理用户的那个对象。
(2)希望用户不必明确制定可处理用户请求的对象集合。
(六)模板方法模式
模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构,即可重定义该算法的某些特定步骤
模板方法模式包括两种角色:
- 抽象模板(Abstract Template):抽象模板是一个抽象类。抽象模板定义了若干个方法以表示一个算法的各个步骤,这若干个方法中有抽象方法也有非抽象方法,其中的抽象方法称作原语操作。重要的一点是,抽象模板中还定义了一个称作模板方法的方法,该方法不仅包含抽象模板中表示算法步骤的方法调用,而且也可以包含定义在抽象模板中的其他对象的方法调用,即模板方法定义了算法的骨架。
- 具体模板(Concrete Template):具体模板是抽象模板的子类,实现抽象方法中的原语操作。
模板方法的类图:
例子:
抽象模板
public abstract class AbstractTemplate {
File[] allFiles;
File dir;
AbstractTemplate(File dir){
this.dir = dir;
}
final void templateMethod(){
allFiles = dir.listFiles();
sort();
printFiles();
}
protected abstract void printFiles();
protected abstract void sort();
}
具体模板
ConcreteTemplate1.java
public class ConcreteTemplate1 extends AbstractTemplate{
ConcreteTemplate1(File dir) {
super(dir);
}
@Override
protected void sort() {
for(int i = 0;i < allFiles.length; i++){
for(int j = i+1;j < allFiles.length; j++){
if(allFiles[j].lastModified() < allFiles[i].lastModified()){
File file = allFiles[j];
allFiles[j] = allFiles[i];
allFiles[i] = file;
}
}
}
}
@Override
public void printFiles() {
for(int i = 0;i < allFiles.length;i++){
long time = allFiles[i].lastModified();
Date date = new Date(time);
SimpleDateFormat matter = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss");
String str = matter.format(date);
String name = allFiles[i].getName();
int k = i+1;
System.out.println(k+" "+name+"("+str+")");
}
}
}
ConcreteTemplate2.java
public class ConcreteTemplate2 extends AbstractTemplate{
ConcreteTemplate2(File dir) {
super(dir);
}
@Override
public void sort() {
for(int i = 0;i < allFiles.length;i++){
for(int j = i+1;j < allFiles.length;j++){
if(allFiles[j].length() < allFiles[i].length()){
File file = allFiles[i];
allFiles[i] = allFiles[j];
allFiles[j] = file;
}
}
}
}
@Override
public void printFiles() {
for(int i = 0;i < allFiles.length;i++){
long fileSize = allFiles[i].length();
String name = allFiles[i].getName();
int k = i+1;
System.out.println(k+" "+name+"("+fileSize+" 字节)");
}
}
}
Application.java
public class Application {
public static void main(String[] args) {
File dir = new File("G:nodejs");
AbstractTemplate template = new ConcreteTemplate1(dir);
System.out.println(dir.getPath()+"目录下的文件:");
template.templateMethod();
template = new ConcreteTemplate2(dir);
System.out.println(dir.getPath()+"目录下的文件:");
template.templateMethod();
}
}
运行效果图:
1.模板方法模式的优点
(1)可以通过在抽象模板中定义模板方法给出成熟的算法步骤,同时又不限制步骤的细节,具体模板实现细节不会改变整个算法的框架。
(2)在抽象模板模式中,可以通过钩子方法对某些步骤进行挂钩,具体模板通过钩子可以选择算法骨架中的某些步骤。
2.适合使用模板方法模式的情景
(1)设计者需要给出一个算法的固定步骤,并将某些步骤的具体实现留给子类来实现。
(2)需要对代码进行重构,将各个子类公共行为提取出来集中到一个共同的父类中以避免代码重复。
(七)观察者模式
观察者模式(别名:依赖、发布-订阅):定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都得到通知并被自动刷新
观察者模式的结构中包括4种角色:
-
主题(Subject):主题是一个接口,该接口规定了具体主题需要实现的方法,比如,添加、删除观察者以及通知观察者更新数据的方法。
-
观察者(Observer):观察者是一个接口,该接口规定了具体观察者用来更新数据的方法。
-
具体主题(ConcreteSubject):具体主题是实现主题接口类的一个实例,该实例包含有可以经常发生变化的数据。具体主题需使用一个集合,比如ArrayList,存放观察者的引用,以便数据变化时通知具体观察者。
-
具体观察者(ConcreteObserver):具体观察者是实现观察者接口类的一个实例。具体观察者包含具体主题引用,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,使自己成为它的观察者,或让这个具体主题将自己从具体主题的集合中删除,使自己不再是它的观察者。
观察者模式的类图:
例子:
主题
public interface Subject {
void addObserver(Observer observer);
void deleteObserver(Observer observer);
void notifyObservers();
void getNewMess(String str);
}
观察者
public interface Observer {
void hearTelephone(String heardMess);
}
具体主题
SeekJobCenter.java
public class SeekJobCenter implements Subject {
String mess;
boolean changed;
ArrayList<Observer> personList; //存放观察者引用的数组线性表
SeekJobCenter(){
personList = new ArrayList<Observer>();
mess = "";
changed = false;
}
public void addObserver(Observer o){
if(!(personList.contains(o))){
personList.add(o); //把观察者的引用添加到数组线性表
}
}
public void deleteObserver(Observer o){
if(personList.contains(o)){
personList.remove(o); //把观察者的引用移除数组线性表
}
}
public void notifyObservers(){
if(changed){ //通知所有的观察者
for(int i = 0;i < personList.size();i++){
Observer observer = personList.get(i);
observer.hearTelephone(mess); //让所有的观察者接听电话
}
}
}
public void getNewMess(String str){ //判断信息是否是新发布的
if(str.equals(mess)){
changed = false;
}
else{
mess = str;
changed = true;
}
}
}
具体观察者
UniversityStudent.java
public class UniversityStudent implements Observer{
Subject subject;
File myFile;
UniversityStudent(Subject subject,String fileName){
this.subject = subject;
subject.addObserver(this); //使当前实例成为subject所使用的具体主题的观察者
myFile = new File(fileName);
}
public void hearTelephone(String heardMess) {
try{
System.out.println("------------------");
RandomAccessFile out1 = new RandomAccessFile(myFile,"rw");
out1.seek(out1.length());
byte[] b = heardMess.getBytes();
out1.write(b); //更新文件中的内容
System.out.print("我是一个大学生,");
System.out.println("我向文件"+myFile.getName()+"写入如下内容:");
System.out.println(heardMess);
}
catch(IOException exp){
System.out.println(exp.toString());
}
}
}
HaiGui.java
public class HaiGui implements Observer{
Subject subject;
File myFile;
HaiGui(Subject subject , String fileName){
this.subject = subject;
subject.addObserver(this); //使当前实例成为subject所引用的具体主题的观察者
myFile = new File(fileName);
}
public void hearTelephone(String heardMess) {
try{
boolean boo = heardMess.contains("java程序员")||heardMess.contains("软件");
if(boo){
RandomAccessFile out = new RandomAccessFile(myFile,"rw");
out.seek(out.length());
byte[] b = heardMess.getBytes();
out.write(b);
System.out.print("我是一个海归");
System.out.println("我向文件"+myFile.getName()+"写入如下内容:");
System.out.println(heardMess);
}
else{
System.out.println("我是海归,这次的信息中没有我需要的信息");
}
}
catch(IOException exp){
System.out.println(exp.toString());
}
}
}
Appliaction
public class Application {
public static void main(String[] args) {
Subject center = new SeekJobCenter(); //具体主题center
UniversityStudent zhang = new UniversityStudent(center,"A.txt"); //具体观察者zhang
HaiGui wang = new HaiGui(center,"B.txt"); //具体观察者wang
center.getNewMess("腾讯公司需要10个Java程序员。"); //具体主题给出新信息
center.notifyObservers(); //具体主题通知信息
center.getNewMess("百度公司需要8个动画设计师。");
center.notifyObservers();
center.getNewMess("考研公司需要9个电工。");
center.notifyObservers();
center.getNewMess("少天公司需要9个电工。"); //信息不是新的
center.notifyObservers(); //观察者不会执行更新操作
}
}
运行效果图:
1.观察者模式的优点
(1)具体主题和具体观察者是松耦合关系。由于主题接口仅仅依赖于观察者接口,因此具体主题只是知道它的观察者是实现观察者接口的某个类的实例,但不需要知道具体是哪个类。同样,由于观察者仅仅依赖于主题接口,因此具体观察者只是知道它依赖的主题是实现主题接口的某个类的实例,但不需要知道具体是哪个类。
(2)观察者模式满足“开-闭原则”。主题接口仅仅依赖于观察者接口,这样,就可以让创建具体主题的类也仅仅是依赖于观察者接口,因此,如果增加新的实现观察者接口的类,不必修改创建具体主题的类的代码。同样,创建具体观察者的类仅仅依赖于主题接口,如果增加新的实现主题接口的类,也不必修改创建具体观察者类的代码。
2.适合使用观察者模式的情景
(1)当一个对象的数据更新时需要通知其他对象,但这个对象又不希望和被通知的那些对象形成紧耦合。
(2)当一个对象的数据更新时,这个对象需要让其他对象也各自更新自己的数据,但这个对象不知道具体有多少对象需要更新数据。
(八)访问者模式
访问者模式:表示一个作用于某对象结构中的各个元素的操作。它使你可以在不改变各个元素的类的前提下定义作用于这些元素的新操作。
访问者模式包括4中角色:
-
抽象元素(Element):一个抽象类,该类定义了接受访问者的accept操作。
-
具体元素(Concrete Element):Element的子类。
-
对象结构(Object Structure):一个集合,用于存放Element对象,提供遍历它自己的方法。
-
**抽象访问者(Visitor):**一个接口,该接口定义操作对象(Concrete Element的实例)的方法。
-
具体访问者(Concrete Visitor):实现Visitor接口的类。
访问者模式的类图:
例子:
抽象访问者
public interface Visitor {
public double visit(AmmeterElement element);
}
抽象元素
public abstract class AmmeterElement {
public abstract void accept(Visitor v);
public abstract double showElectricAmount();
public abstract void setElectricAmount(double n);
}
具体访问者
HomeAmmeterVisitor.java
public class HomeAmmeterVisitor implements Visitor{
@Override
public double visit(AmmeterElement element) {
double charge = 0;
double unitOne = 0.6,unitTwo = 1.05;
int basic = 6000;
double n = element.showElectricAmount();
if(n <= basic)
charge = n*unitOne;
else
charge = basic*unitOne + (n-basic)*unitTwo;
return charge;
}
}
IndustryAmmeterVisitor.java
public class IndustryAmmeterVisitor implements Visitor{
@Override
public double visit(AmmeterElement element) {
double charge = 0;
double unitOne = 1.52,unitTwo = 2.78;
int basic = 15000;
double n = element.showElectricAmount();
if(n <= basic)
charge = n*unitOne;
else
charge = basic*unitOne + (n-basic)*unitTwo;
return charge;
}
}
具体元素
public class Ammeter extends AmmeterElement{
double electricAmount; //电表的电量
@Override
public void accept(Visitor v) {
double cost = v.visit(this); //让访问者访问当前元素
System.out.println("当前电表的用户需要交纳的电费:" + cost + "元");
}
@Override
public double showElectricAmount() {
return electricAmount;
}
@Override
public void setElectricAmount(double n) {
electricAmount = n;
}
}
Application
public class Application {
public static void main(String[] args) {
Visitor visitor = new HomeAmmeterVisitor();
Ammeter ammeter = new Ammeter();
ammeter.setElectricAmount(5678);
ammeter.accept(visitor);
visitor = new IndustryAmmeterVisitor();
ammeter.setElectricAmount(5678);
ammeter.accept(visitor);
}
}
运行效果图
1.访问者模式的优点
(1)可以在不改变一个集合中元素类的情况下,增加新的施加于该元素上的新操作。
(2)可以将集合中各个元素的某些操作集中到访问者中,不仅便于集合的维护,也有利于集合中元素的复用。
2.适合使用访问者模式的情景
(1)一个对象结构中,比如某个集合中,包含有很多对象,想对集合中对象增加新的操作。
(2)需要对集合中的对象进行很多不同并且不相关的操作,而又不想修改对象的类,就可以使用访问者模式。 访问者模式可以在Visitor类中集中定义一些关于集合中对象的操作。