企业网站排名关键,女装高端品牌有哪些,外贸流程全步骤 外贸篇,有哪些是外国人做的网站Hiヽ(゜▽゜ )#xff0d;欢迎来到蓝染Aizen的CSDN博客~ #x1f525; 博客主页#xff1a; 【✨蓝染 の Blog#x1f618;】 #x1f496;感谢大家点赞#x1f44d; 收藏⭐ 评论✍ 文章目录 行为型模式1、模板方法模式#xff08;1#xff09;概述#xff08;2欢迎来到蓝染Aizen的CSDN博客~ 博客主页 【✨蓝染 の Blog】 感谢大家点赞 收藏⭐ 评论✍ 文章目录 行为型模式1、模板方法模式1概述2结构3案例实现4优缺点5适用场景6JDK源码解析7模板方法模式与建造者模式的区别 2、策略模式1概述2结构3案例实现4优缺点5使用场景6JDK源码解析 3、命令模式1概述2结构3案例实现4优缺点5使用场景6JDK源码解析 4、责任链模式1概述2结构3案例实现4优缺点5源码解析 5、状态模式1概述2结构3案例实现4优缺点5使用场景 6、观察者模式1概述2结构3案例实现4优缺点5使用场景6JDK中提供的实现 7、中介者模式1概述2结构3案例实现4优缺点5使用场景 8、迭代器模式1概述2结构3案例实现4优缺点5使用场景6JDK源码解析 9、访问者模式1概述2结构3案例实现4优缺点5使用场景6扩展双分派 10、备忘录模式1概述2结构3案例实现① “白箱”备忘录模式② “黑箱”备忘录模式 4优缺点5使用场景 11、解释器模式2结构3案例实现4优缺点5使用场景 行为型模式
行为型模式用于描述程序在运行时复杂的流程控制即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式前者采用继承机制来在类间分派行为后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低满足“合成复用原则”所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式分为
模板方法模式策略模式命令模式职责链模式状态模式观察者模式中介者模式迭代器模式访问者模式备忘录模式解释器模式
以上 11 种行为型模式除了模板方法模式和解释器模式是类行为型模式其他的全部属于对象行为型模式。 1、模板方法模式 1概述
在面向对象程序设计过程中程序员常常会遇到这种情况设计一个系统时知道了算法所需的关键步骤而且确定了这些步骤的执行顺序但某些步骤的具体实现还未知或者说某些步骤的实现与具体的环境相关。
例如去银行办理业务一般要经过以下4个流程取号、排队、办理具体业务、对银行工作人员进行评分等其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的可以在父类中实现但是办理具体业务却因人而异它可能是存款、取款或者转账等可以延迟到子类中实现。
定义定义一个操作中的算法骨架而将算法的一些步骤延迟到子类中使得子类可以不改变该算法结构的情况下重新定义该算法的某些特定步骤。 2结构
模板方法Template Method模式包含以下主要角色
抽象类Abstract Class负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。 模板方法定义了算法的骨架按某种顺序调用其包含的基本方法。基本方法是实现算法各个步骤的方法是模板方法的组成部分。基本方法又可以分为三种 抽象方法(Abstract Method) 一个抽象方法由抽象类声明、由其具体子类实现。具体方法(Concrete Method) 一个具体方法由一个抽象类或具体类声明并实现其子类可以进行覆盖也可以直接继承。钩子方法(Hook Method) 在抽象类中已经实现包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法这类方法名一般为isXxx返回值类型为boolean类型。 具体子类Concrete Class实现抽象类中所定义的抽象方法和钩子方法它们是一个顶级逻辑的组成步骤。 3案例实现
【例】炒菜炒菜的步骤是固定的分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下
代码如下
// 抽象类定义模板方法和基本方法
public abstract class AbstractClass {// 模板方法定义不能通过继承改变算法结构加finalpublic final void cookProcess() {// 定义基本方法算法功能执行流程pourOil(); // 具体方法heatOil(); // 具体方法pourVegetable(); // 抽象方法pourSauce(); // 抽象方法fry(); // 具体方法}// 第一步倒油是一样的所以直接实现public void pourOil() {System.out.println(倒油);}// 第二步热油是一样的所以直接实现public void heatOil() {System.out.println(热油);}// 第三步倒蔬菜是不一样的一个下包菜一个是下菜心public abstract void pourVegetable();// 第四步倒调味料是不一样public abstract void pourSauce();// 第五步翻炒是一样的所以直接实现public void fry(){System.out.println(翻炒);}
}// 炒包菜类具体子类
public class ConcreteClass_BaoCai extends AbstractClass {Overridepublic void pourVegetable() {System.out.println(下锅的蔬菜是包菜);}Overridepublic void pourSauce() {System.out.println(下锅的酱料是辣椒);}
}// 炒菜心类具体子类
public class ConcreteClass_CaiXin extends AbstractClass {Overridepublic void pourVegetable() {System.out.println(下锅的蔬菜是菜心);}Overridepublic void pourSauce() {System.out.println(下锅的酱料是蒜蓉);}
}public class Client {public static void main(String[] args) {// 炒辣椒包菜ConcreteClass_BaoCai baoCai new ConcreteClass_BaoCai();baoCai.cookProcess();System.out.println();// 炒蒜蓉菜心ConcreteClass_CaiXin caiXin new ConcreteClass_CaiXin();caiXin.cookProcess();}
}注意为防止恶意操作一般模板方法都加上 final 关键词。 4优缺点
优点
提高代码复用性 将相同部分的代码放在抽象的父类中而将不同的代码放入不同的子类中。 实现了反向控制 通过一个父类调用其子类的操作通过对子类的具体实现扩展不同的行为实现了反向控制 并符合“开闭原则”。
缺点
对每个不同的实现都需要定义一个子类这会导致类的个数增加系统更加庞大设计也更加抽象。父类中的抽象方法由子类实现子类执行的结果会影响父类的结果这导致一种反向的控制结构它提高了代码阅读的难度。 5适用场景
算法的整体步骤很固定但其中个别部分易变时这时候可以使用模板方法模式将容易变的部分抽象出来供子类实现。需要通过子类来决定父类算法中某个步骤是否执行实现子类对父类的反向控制。 6JDK源码解析
InputStream类就使用了模板方法模式。在InputStream类中定义了多个read() 方法如下
public abstract class InputStream implements Closeable {// 抽象方法要求子类必须重写public abstract int read() throws IOException;public int read(byte b[]) throws IOException {return read(b, 0, b.length);}public int read(byte b[], int off, int len) throws IOException { // 模板方法if (b null) {throw new NullPointerException();} else if (off 0 || len 0 || len b.length - off) {throw new IndexOutOfBoundsException();} else if (len 0) {return 0;}int c read(); // 调用了无参的read方法该方法是每次读取一个字节数据本质是调用了子类中的read方法if (c -1) {return -1;}b[off] (byte)c;int i 1;try {for (; i len ; i) {c read(); // 算法结构的执行多次调用read方法if (c -1) {break;}b[off i] (byte)c;}} catch (IOException ee) {}return i;}
}从上面代码可以看到无参的 read() 方法是抽象方法要求子类必须实现。而 read(byte b[]) 方法调用了 read(byte b[], int off, int len) 方法所以在此处重点看的方法是带三个参数的方法。
在该方法中第18行、27行可以看到调用了无参的抽象的 read() 方法。
总结如下 在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节并将其存储到数组的第一个索引位置读取len个字节数据。具体如何读取一个字节数据呢由子类实现。 7模板方法模式与建造者模式的区别
模板方法模式适用于流程固定但是其中的一些步骤需要根据具体情况进行替换或者扩展的场景。建造者模式的设计目的是将一个复杂对象的构建过程与其表示分离 2、策略模式 1概述
先看下面的图片我们去旅游选择出行模式有很多种可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。
作为一个程序猿开发需要选择一款开发工具当然可以进行代码开发的工具有很多可以选择Idea进行开发也可以使用eclipse进行开发也可以使用其他的一些开发工具。
定义 该模式定义了一系列算法并将每个算法封装起来使它们可以相互替换且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式它通过对算法进行封装把使用算法的责任和算法的实现分割开来并委派给不同的对象对这些算法进行管理。 2结构
策略模式的主要角色如下
抽象策略Strategy类这是一个抽象角色通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。具体策略Concrete Strategy类实现了抽象策略定义的接口提供具体的算法实现或行为。环境Context类持有一个策略类的引用最终给客户端调用。 3案例实现
【例】促销活动一家百货公司在定年度的促销活动。针对不同的特殊节日春节、618、双11推出不同的促销活动由促销员将促销活动展示给客户。类图如下
代码如下定义抽象策略类百货公司所有促销活动的共同接口
// 抽象策略类
public interface Strategy {void show();
}定义具体策略角色Concrete Strategy每个节日具体的促销活动
// 具体策略类封装算法
public class StrategyA implements Strategy {// 为春节准备的促销活动AOverridepublic void show() {System.out.println(春节活动买一送一);}
}// 具体策略类封装算法
public class StrategyB implements Strategy {// 为618准备的促销活动BOverridepublic void show() {System.out.println(618活动满200元减50元);}
}// 具体策略类封装算法
public class StrategyC implements Strategy {// 为双11准备的促销活动COverridepublic void show() {System.out.println(双11活动满1000元加一元换购任意200元以下商品);}
}定义环境角色Context用于连接上下文即把促销活动推销给客户这里可以理解为销售员
// 促销员环境类
public class SalesMan {// 聚合策略类对象private Strategy strategy;// 构造器注入public SalesMan(Strategy strategy) {this.strategy strategy;}// set方法注入public void setStrategy(Strategy strategy) {this.strategy strategy;}// 促销员向客户展示促销活动public void salesManShow() {strategy.show();}
}测试类
public class Client {public static void main(String[] args) {// 春节促销活动SalesMan salesMan new SalesMan(new StrategyA());salesMan.salesManShow();// 618促销活动salesMan.setStrategy(new StrategyB());salesMan.salesManShow();// 双11促销活动salesMan.setStrategy(new StrategyC());salesMan.salesManShow();}
}4优缺点
优点
策略类之间可以自由切换由于策略类都实现同一个接口所以使它们之间可以自由切换。易于扩展增加一个新的策略只需要添加一个具体的策略类即可基本不需要改变原有的代码符合“开闭原则“避免使用多重条件选择语句if else充分体现面向对象设计思想。
缺点
客户端必须知道所有的策略类并自行决定使用哪一个策略类。策略模式将造成产生很多策略类可以通过使用享元模式在一定程度上减少对象的数量。 5使用场景
一个系统需要动态地在几种算法中选择一种时可将每个算法封装到策略类中。一个类定义了多种行为并且这些行为在这个类的操作中以多个条件语句的形式出现可将每个条件分支移入它们各自的策略类中以代替这些条件语句将大量的if-else语句进行改写。系统中各算法彼此完全独立且要求对客户隐藏具体算法的实现细节时。系统要求使用算法的客户不应该知道其操作的数据时可使用策略模式来隐藏与算法相关的数据结构。多个类只区别在表现行为不同可以使用策略模式在运行时动态选择具体要执行的行为。 6JDK源码解析
Comparator 中的策略模式。在Arrays类中有一个 sort() 方法如下
public class Arrays{public static T void sort(T[] a, Comparator? super T c) {if (c null) {sort(a);} else {if (LegacyMergeSort.userRequested)legacyMergeSort(a, c);elseTimSort.sort(a, 0, a.length, c, null, 0, 0);}}
}Arrays就是一个环境角色类这个sort方法可以传一个新策略让Arrays根据这个策略来进行排序。就比如下面的测试类。
public class demo {public static void main(String[] args) {Integer[] data {12, 2, 3, 2, 4, 5, 1};// 实现降序排序Arrays.sort(data, new ComparatorInteger() {public int compare(Integer o1, Integer o2) {return o2 - o1;}});System.out.println(Arrays.toString(data)); // [12, 5, 4, 3, 2, 2, 1]}
}这里我们在调用Arrays的sort方法时第二个参数传递的是Comparator接口的子实现类对象。所以Comparator充当的是抽象策略角色而具体的子实现类充当的是具体策略角色。环境角色类Arrays应该持有抽象策略的引用来调用。那么Arrays类的sort方法到底有没有使用Comparator子实现类中的 compare() 方法吗让我们继续查看TimSort类的 sort() 方法代码如下
class TimSortT {static T void sort(T[] a, int lo, int hi, Comparator? super T c,T[] work, int workBase, int workLen) {assert c ! null a ! null lo 0 lo hi hi a.length;int nRemaining hi - lo;if (nRemaining 2)return; // Arrays of size 0 and 1 are always sorted// If array is small, do a mini-TimSort with no mergesif (nRemaining MIN_MERGE) {int initRunLen countRunAndMakeAscending(a, lo, hi, c);binarySort(a, lo, hi, lo initRunLen, c);return;}...} private static T int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator? super T c) {assert lo hi;int runHi lo 1;if (runHi hi)return 1;// Find end of run, and reverse range if descendingif (c.compare(a[runHi], a[lo]) 0) { // Descendingwhile (runHi hi c.compare(a[runHi], a[runHi - 1]) 0)runHi;reverseRange(a, lo, runHi);} else { // Ascendingwhile (runHi hi c.compare(a[runHi], a[runHi - 1]) 0)runHi;}return runHi - lo;}
}上面的代码中最终会跑到 countRunAndMakeAscending() 这个方法中。我们可以看见只用了compare方法所以在调用Arrays.sort方法只传具体compare重写方法的类对象就行这也是Comparator接口中必须要子类实现的一个方法。 3、命令模式 1概述
日常生活中我们出去吃饭都会遇到下面的场景。
定义将一个请求封装为一个对象使发出请求的责任和执行请求的责任分割开。两者之间通过命令对象进行沟通这样方便将命令对象进行存储、传递、调用、增加与管理。 2结构
命令模式包含以下主要角色
抽象命令类Command角色 定义命令的接口声明执行的方法。具体命令Concrete Command角色具体的命令实现命令接口通常会持有接收者并调用接收者的功能来完成命令要执行的操作。实现者/接收者Receiver角色 接收者真正执行命令的对象。任何类都可能成为一个接收者只要它能够实现命令要求实现的相应功能。调用者/请求者Invoker角色 要求命令对象执行请求通常会持有命令对象可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方也就是说相当于使用命令对象的入口。 3案例实现
将上面的案例用代码实现那我们就需要分析命令模式的角色在该案例中由谁来充当。服务员 就是调用者角色由她来发起命令。资深大厨 就是接收者角色真正命令执行的对象。订单 命令中包含订单。
类图如下
代码如下
// 抽象命令类
public interface Command {void execute(); // 只需要定义一个统一的执行方法
}// 具体命令类
public class OrderCommand implements Command {// 持有接收者对象private SeniorChef receiver;// 持有订单对象private Order order;public OrderCommand(SeniorChef receiver, Order order) {this.receiver receiver;this.order order;}Overridepublic void execute() {System.out.println(order.getDiningTable() 桌的订单);MapString, Integer foodDir order.getFoodDir();SetString names foodDir.keySet();for (String name : names) {receiver.makeFood(name, foodDir.get(name));}System.out.println(order.getDiningTable() 桌的餐品制作完成服务员请上菜\n);}
}// 订单类
public class Order {// 餐桌号码private int diningTable;// 所下餐品名称和份数的集合private MapString, Integer foodDir new HashMapString, Integer();public int getDiningTable() {return diningTable;}public void setDiningTable(int diningTable) {this.diningTable diningTable;}public MapString, Integer getFoodDir() {return foodDir;}public void setFood(String name, int num) {foodDir.put(name, num);}
}// 厨师类命令接收者
public class SeniorChef {public void makeFood(String name, int num) {System.out.println(num 份 name);}
}// 服务员类调用者/请求者角色
public class Waiter {// 持有多个命令对象private ListCommand commands new ArrayListCommand();public void setCommand(Command cmd) {commands.add(cmd);}// 发起命令请求的功能public void orderUp() {System.out.println(服务员新订单来了);for (Command command : commands) {if (command ! null) {command.execute();}}}
}// 测试类
public class Client {public static void main(String[] args) {// 创建订单1Order order1 new Order();order1.setDiningTable(1);order1.setFood(西红柿鸡蛋面, 1);order1.setFood(小杯可乐, 2);// 创建订单2Order order2 new Order();order2.setDiningTable(2);order2.setFood(尖椒肉丝盖饭, 1);order2.setFood(小杯橙汁, 1);// 创建接收者厨师对象SeniorChef receiver new SeniorChef();// 创建命令对象OrderCommand cmd1 new OrderCommand(receiver, order1);OrderCommand cmd2 new OrderCommand(receiver, order2);// 创建调用者服务员对象Waiter invoker new Waiter();invoker.setCommand(cmd1);invoker.setCommand(cmd2);// 服务员发起命令invoker.orderUp();}
}4优缺点
优点
降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类它满足“开闭原则”对扩展比较灵活。可以实现宏命令。命令模式可以与组合模式结合将多个命令装配成一个组合命令即宏命令。方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合实现命令的撤销与恢复。
缺点
使用命令模式可能会导致某些系统有过多的具体命令类。系统结构更加复杂。 5使用场景
系统需要将请求调用者和请求接收者解耦使得调用者和接收者不直接交互。系统需要在不同的时间指定请求、将请求排队和执行请求。系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。 6JDK源码解析
Runable是一个典型命令模式Runnable担当抽象命令角色Thread充当的是调用者start方法就是其执行方法
// 命令接口(抽象命令角色)
public interface Runnable {public abstract void run();
}// 调用者
public class Thread implements Runnable {private Runnable target;public synchronized void start() {if (threadStatus ! 0)throw new IllegalThreadStateException();group.add(this);boolean started false;try {start0();started true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {}}}private native void start0();
}会调用一个native方法start0(),调用系统方法开启一个线程。而接收者是对程序员开放的可以自己定义接收者。
/*** jdk Runnable 命令模式* TurnOffThread 属于具体命令角色*/
public class TurnOffThread implements Runnable{private Receiver receiver;public TurnOffThread(Receiver receiver) {this.receiver receiver;}public void run() {receiver.turnOFF();}
}public class Receiver { ... }/*** 测试类*/
public class Demo {public static void main(String[] args) {Receiver receiver new Receiver();TurnOffThread turnOffThread new TurnOffThread(receiver);Thread thread new Thread(turnOffThread);thread.start();}
}4、责任链模式 1概述
在现实生活中常常会出现这样的事例**一个请求有多个对象可以处理但每个对象的处理条件或权限不同。**例如公司员工请假可批假的领导有部门负责人、副总经理、总经理等但每个领导能批准的天数不同员工必须根据自己要请假的天数去找不同的领导签名也就是说员工必须记住每个领导的姓名、电话和地址等信息这增加了难度。这样的例子还有很多如找领导出差报销、生活中的“击鼓传花”游戏等。
定义又名职责链模式为了避免请求发送者与多个请求处理者耦合在一起将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链当有请求发生时可将请求沿着这条链传递直到有对象处理它为止。 2结构
职责链模式主要包含以下角色:
抽象处理者Handler角色定义一个处理请求的接口包含抽象处理方法和一个后继连接记住下一个对象的引用。具体处理者Concrete Handler角色实现抽象处理者的处理方法判断能否处理本次请求如果可以处理请求则处理否则将该请求转给它的后继者。客户类Client角色创建处理链并向链头的具体处理者对象提交请求它不关心处理细节和请求的传递过程。 3案例实现
现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可请假1天到3天的假还需要部门经理同意请求3天以上还需要总经理同意才行。
类图如下
代码如下
// 请假条类
public class LeaveRequest {// 姓名private String name;// 请假天数private int num;// 请假内容private String content;public LeaveRequest(String name, int num, String content) {this.name name;this.num num;this.content content;}public String getName() {return name;}public int getNum() {return num;}public String getContent() {return content;}
}// 抽象处理者类
public abstract class Handler {// 请假天数常量protected final static int NUM_ONE 1;protected final static int NUM_THREE 3;protected final static int NUM_SEVEN 7;// 该领导处理的请求天数区间private int numStart;private int numEnd;// 自关联声明后继者上级领导private Handler nextHandler;public Handler(int numStart) {this.numStart numStart;}public Handler(int numStart, int numEnd) {this.numStart numStart;this.numEnd numEnd;}// 设置上级领导对象public void setNextHandler(Handler nextHandler) {this.nextHandler nextHandler;}// 各级领导处理请假条的方法protected abstract void handleLeave(LeaveRequest leave);// 提交请假条public final void submit(LeaveRequest leave) {if (leave.getNum() 0) {System.out.println(不予批准);return;}if (leave.getNum() this.numStart) {// 该领导先进行审批this.handleLeave(leave);// 如果有上级领导并且请假天数大于该领导能处理的最大审批天数if (this.nextHandler ! null leave.getNum() this.numEnd) {// 提交给上级领导进行审批this.nextHandler.submit(leave);}else {System.out.println(请假流程结束);}}}
}// 小组长类具体的处理者
public class GroupLeader extends Handler {public GroupLeader() {super(0, Handler.NUM_ONE); // 0~1天}Overrideprotected void handleLeave(LeaveRequest leave) {System.out.println(leave.getName() 请假 leave.getNum() 天请假原因 leave.getContent());System.out.println(小组长审批同意);}
}// 部门经理类具体的处理者
public class Manager extends Handler {public Manager() {super(Handler.NUM_ONE, Handler.NUM_THREE); // 1~3天}Overrideprotected void handleLeave(LeaveRequest leave) {System.out.println(leave.getName() 请假 leave.getNum() 天请假原因 leave.getContent());System.out.println(部门经理审批同意);}
}// 总经理类具体的处理者
public class GeneralManager extends Handler {public GeneralManager() {super(Handler.NUM_THREE); // 3天}Overrideprotected void handleLeave(LeaveRequest leave) {System.out.println(leave.getName() 请假 leave.getNum() 天请假原因 leave.getContent());System.out.println(总经理审批同意);}
}// 测试类
public class Client {public static void main(String[] args) {// 创建一个请假条对象LeaveRequest leave new LeaveRequest(小明, 5, 身体不适);// 创建各级领导对象GroupLeader groupLeader new GroupLeader();Manager manager new Manager();GeneralManager generalManager new GeneralManager();// 设置处理者链groupLeader.setNextHandler(manager);manager.setNextHandler(generalManager);// 小明提交请假申请groupLeader.submit(leave);}
}4优缺点
优点
降低了对象之间的耦合度该模式降低了请求发送者和接收者的耦合度。增强了系统的可扩展性可以根据需要增加新的请求处理类满足开闭原则。增强了给对象指派职责的灵活性当工作流程发生变化可以动态地改变链内的成员或者修改它们的次序也可动态地新增或者删除责任。责任链简化了对象之间的连接一个对象只需保持一个指向其后继者的引用不需保持其他所有处理者的引用这避免了使用众多的 if 或者 if···else 语句。责任分担每个类只需要处理自己该处理的工作不能处理的传递给下一个对象完成明确各类的责任范围符合类的单一职责原则。
缺点
不能保证每个请求一定被处理。由于一个请求没有明确的接收者所以不能保证它一定会被处理该请求可能一直传到链的末端都得不到处理。对比较长的职责链请求的处理可能涉及多个处理对象系统性能将受到一定影响。职责链建立的合理性要靠客户端来保证增加了客户端的复杂性可能会由于职责链的错误设置而导致系统出错如可能会造成循环调用。 5源码解析
在javaWeb应用开发中FilterChain是职责链过滤器模式的典型应用以下是Filter的模拟实现分析:
模拟web请求Request以及web响应Response
public interface Request {}public interface Response {}模拟web过滤器Filter public interface Filter {public void doFilter(Request req,Response res,FilterChain c);}模拟实现具体过滤器
public class FirstFilter implements Filter {Overridepublic void doFilter(Request request, Response response, FilterChain chain) {System.out.println(过滤器1 前置处理);// 先执行所有request再倒序执行所有responsechain.doFilter(request, response);System.out.println(过滤器1 后置处理);}
}public class SecondFilter implements Filter {Overridepublic void doFilter(Request request, Response response, FilterChain chain) {System.out.println(过滤器2 前置处理);// 先执行所有request再倒序执行所有responsechain.doFilter(request, response);System.out.println(过滤器2 后置处理);}
}模拟实现过滤器链FilterChain
public class FilterChain {private ListFilter filters new ArrayListFilter();private int index 0;// 链式调用public FilterChain addFilter(Filter filter) {this.filters.add(filter);return this;}public void doFilter(Request request, Response response) {if (index filters.size()) {return;}Filter filter filters.get(index);index;filter.doFilter(request, response, this);}
}测试类
public class Client {public static void main(String[] args) {Request req null;Response res null;FilterChain filterChain new FilterChain();filterChain.addFilter(new FirstFilter()).addFilter(new SecondFilter());filterChain.doFilter(req, res);}
}5、状态模式 1概述
【例】通过按钮来控制一个电梯的状态一个电梯有开门状态关门状态停止状态运行状态。每一种状态改变都有可能要根据其他状态来更新处理。例如如果电梯门现在处于运行时状态就不能进行开门操作而如果电梯门是停止状态就可以执行开门操作。
类图如下
代码如下
// 电梯接口
public interface ILift {// 定义四个电梯状态的常量//public final static int OPENING_STATE 1;int OPENING_STATE 1;int CLOSING_STATE 2;int STOPPING_STATE 3;int RUNNING_STATE 4;// 设置电梯状态void setState(int state);// 电梯操作的功能void open();void close();void run();void stop();
}// 电梯类
public class Lift implements ILift {// 声明记录当前电梯状态的变量private int state;Overridepublic void setState(int state) {this.state state;}Overridepublic void open() {switch(state) {case OPENING_STATE: // 门已经开了不能再开门了// do nothingbreak;case CLOSING_STATE: // 关门状态电梯开门case STOPPING_STATE: // 电梯停了电梯开门System.out.println(电梯开门);setState(OPENING_STATE);break;case RUNNING_STATE: // 运行时电梯不能开门// do nothingbreak;}}Overridepublic void close() {switch (this.state) {case OPENING_STATE: // 只有开门状态可以关闭电梯门System.out.println(电梯关门了。。。);this.setState(CLOSING_STATE);// 关门之后电梯就是关闭状态了break;case CLOSING_STATE: // 已经是关门状态不能关门// do nothingbreak;case STOPPING_STATE: // 停止时电梯也是关着的不用关门// do nothingbreak;case RUNNING_STATE: // 运行时电梯门是关着的不能关门// do nothingbreak;}}Overridepublic void run() {switch (this.state) {case OPENING_STATE: // 电梯不能开着门就走// do nothingbreak;case CLOSING_STATE: // 门关了可以运行了System.out.println(电梯开始运行了。。。);this.setState(RUNNING_STATE);// 现在是运行状态break;case RUNNING_STATE: // 已经是运行状态了// do nothingbreak;case STOPPING_STATE:System.out.println(电梯开始运行了。。。);this.setState(RUNNING_STATE);break;}}Overridepublic void stop() {switch (this.state) {case OPENING_STATE: // 开门的电梯已经是是停止的了(正常情况下)// do nothingbreak;case CLOSING_STATE: // 关门时才可以停止System.out.println(电梯停止了。。。);this.setState(STOPPING_STATE);break;case RUNNING_STATE: // 运行时当然可以停止了System.out.println(电梯停止了。。。);this.setState(STOPPING_STATE);break;case STOPPING_STATE:// do nothingbreak;}}
}public class Client {public static void main(String[] args) {Lift lift new Lift();lift.setState(ILift.STOPPING_STATE); // 电梯是停止的lift.open(); // 开门lift.close(); // 关门lift.run(); // 运行lift.stop(); // 停止}
}问题分析
使用了大量的switch…case这样的判断if…else也是一样)使程序的可阅读性变差。扩展性很差。如果新加了断电的状态我们需要修改上面判断逻辑
定义对有状态的对象把复杂的“判断逻辑”提取到不同的状态对象中允许状态对象在其内部状态发生改变时改变其行为。 2结构
状态模式包含以下主要角色。
环境Context角色也称为上下文它定义了客户程序需要的接口维护一个当前状态并将与状态相关的操作委托给当前状态对象来处理。抽象状态State角色定义一个接口用以封装环境对象中的特定状态所对应的行为。具体状态Concrete State角色实现抽象状态所对应的行为。 3案例实现
对上述电梯的案例使用状态模式进行改进。类图如下
代码如下
// 抽象状态类
public abstract class LiftState {// 声明环境角色类对象protected Context context;public void setContext(Context context) {this.context context;}// 电梯开启操作public abstract void open();// 电梯关闭操作public abstract void close();// 电梯运行操作public abstract void run();// 电梯停止操作public abstract void stop();
}// 电梯开启状态类
public class OpeningState extends LiftState {// 当前状态要执行的方法Overridepublic void open() {System.out.println(电梯门开启...);}Overridepublic void close() {// 修改状态super.context.setLiftState(Context.CLOSING_STATE);// 调用当前状态中的context中的close方法super.context.close();}// 电梯门不能开着就跑这里什么也不做Overridepublic void run() {//do nothing}// 开门状态已经是停止的了Overridepublic void stop() {//do nothing}
}// 电梯关闭状态类
public class ClosingState extends LiftState {// 电梯门关闭这是关闭状态要实现的动作Overridepublic void close() {System.out.println(电梯门关闭...);}// 电梯门关了再打开Overridepublic void open() {super.context.setLiftState(Context.OPENING_STATE);super.context.open();}// 电梯门关了就可以运行Overridepublic void run() {super.context.setLiftState(Context.RUNNING_STATE);super.context.run();}// 电梯门关着不按楼层Overridepublic void stop() {super.context.setLiftState(Context.STOPPING_STATE);super.context.stop();}
}// 电梯运行状态类
public class RunningState extends LiftState {// 运行的时候不能开电梯门Overridepublic void open() {//do nothing}// 电梯运行时已经关闭电梯门Overridepublic void close() {//do nothing}// 这是在运行状态下要实现的方法Overridepublic void run() {System.out.println(电梯正在运行...);}// 运行的电梯允许停止Overridepublic void stop() {super.context.setLiftState(Context.STOPPING_STATE);super.context.stop();}
}// 电梯停止状态类
public class StoppingState extends LiftState {// 停止状态允许开门Overridepublic void open() {// 修改状态super.context.setLiftState(Context.OPENING_STATE);// 动作委托为CloseState来执行也就是委托给了ClosingState子类执行这个动作super.context.getLiftState().open();}// 停止状态也可以关门Overridepublic void close() {// 修改状态super.context.setLiftState(Context.CLOSING_STATE);// 动作委托为CloseState来执行也就是委托给了ClosingState子类执行这个动作super.context.getLiftState().close();}// 停止状态可以接着运行Overridepublic void run() {// 修改状态super.context.setLiftState(Context.RUNNING_STATE);// 动作委托为CloseState来执行也就是委托给了ClosingState子类执行这个动作super.context.getLiftState().run();}// 电梯门停止这是停止状态要实现的动作Overridepublic void stop() {System.out.println(电梯停止了...);}
}// 环境角色类
public class Context {// 定义对应状态对象的常量public static final OpeningState OPENING_STATE new OpeningState();public static final ClosingState CLOSING_STATE new ClosingState();public static final RunningState RUNNING_STATE new RunningState();public static final StoppingState STOPPING_STATE new StoppingState();// 定义记录当前电梯状态变量private LiftState liftState;public LiftState getLiftState() {return liftState;}// 设置当前状态对象public void setLiftState(LiftState liftState) {this.liftState liftState;// 设置当前状态中的Context对象this.liftState.setContext(this);}public void open() {this.liftState.open();}public void close() {this.liftState.close();}public void run() {this.liftState.run();}public void stop() {this.liftState.stop();}
}// 测试类
public class Client {public static void main(String[] args) {// 创建环境角色对象Context context new Context();// 设置当前电梯状态context.setLiftState(new ClosingState());context.open();context.close();context.run();context.stop();System.out.println(-------------);context.setLiftState(new ClosingState());context.open();context.run();context.close();context.stop();System.out.println(-------------);context.setLiftState(new RunningState());context.open();context.close();context.run();context.stop();}
}4优缺点
优点
将所有与某个状态有关的行为放到一个类中并且可以方便地增加新的状态只需要改变对象状态即可改变对象的行为。允许状态转换逻辑与状态对象合成一体而不是某一个巨大的条件语句块。
缺点
状态模式的使用必然会增加系统类和对象的个数。状态模式的结构与实现都较为复杂如果使用不当将导致程序结构和代码的混乱。状态模式对开闭原则的支持并不太好。 5使用场景
当一个对象的行为取决于它的状态并且它必须在运行时根据状态改变它的行为时就可以考虑使用状态模式。一个操作中含有庞大的分支结构并且这些分支决定于对象的状态时。 6、观察者模式 1概述
定义又被称为发布-订阅Publish/Subscribe模式它定义了一种一对多的依赖关系让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时会通知所有的观察者对象使他们能够自动更新自己。 2结构
在观察者模式中有如下角色
Subject抽象主题抽象被观察者抽象主题角色把所有观察者对象保存在一个集合里每个主题都可以有任意数量的观察者抽象主题提供一个接口可以增加和删除观察者对象。ConcreteSubject具体主题具体被观察者该角色将有关状态存入具体观察者对象在具体主题的内部状态发生改变时给所有注册过的观察者发送通知。Observer抽象观察者是观察者的抽象类它定义了一个更新接口使得在得到主题更改通知时更新自己。ConcrereObserver具体观察者实现抽象观察者定义的更新接口以便在得到主题更改通知时更新自身的状态。 3案例实现
【例】微信公众号在使用微信公众号时大家都会有这样的体验当你关注的公众号中有新内容更新的话它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景微信用户就是观察者微信公众号是被观察者有多个的微信用户关注了程序猿这个公众号。
类图如下
代码如下定义抽象观察者类里面定义一个更新的方法
// 抽象观察者类
public interface Observer {void update(String message);
}定义具体观察者类微信用户是观察者里面实现了更新的方法
// 具体观察者角色类
public class WeiXinUser implements Observer {private String name;public WeiXinUser(String name) {this.name name;}Overridepublic void update(String message) {System.out.println(name 收到消息 message);}Overridepublic String toString() {return name;}
}定义抽象主题类提供了attach、detach、notify三个方法
// 抽象主题角色
public interface Subject {// 添加订阅者观察者对象void attach(Observer observer);// 删除订阅者void detach(Observer observer);// 通知订阅者观察者更新消息void notify(String message);
}微信公众号是具体主题具体被观察者里面存储了订阅该公众号的微信用户并实现了抽象主题中的方法
// 公众号类具体主题角色类
public class SubscriptionSubject implements Subject {// 定义一个集合用来存储多个观察者对象微信用户private ListObserver weiXinUserList new ArrayListObserver();Overridepublic void attach(Observer observer) {System.out.println(observer 订阅了公众号);weiXinUserList.add(observer);}Overridepublic void detach(Observer observer) {System.out.println(observer 取关了公众号);weiXinUserList.remove(observer);}Overridepublic void notify(String message) {// 遍历集合通知每一个观察者进行更新for (Observer observer : weiXinUserList) {observer.update(message);}}
}客户端程序
public class Client {public static void main(String[] args) {// 创建公众号主题对象SubscriptionSubject subject new SubscriptionSubject();// 创建订阅者WeiXinUser userA new WeiXinUser(用户A);WeiXinUser userB new WeiXinUser(用户B);WeiXinUser userC new WeiXinUser(用户C);WeiXinUser userD new WeiXinUser(用户D);// 订阅公众号subject.attach(userA);subject.attach(userB);subject.attach(userC);subject.attach(userD);System.out.println(-------------------);// 公众号更新发出消息给订阅者观察者对象subject.notify(公众号更新了);System.out.println(-------------------);// 如果订阅者取消订阅公众号subject.detach(userD);subject.notify(公众号更新了);}
}4优缺点
优点
降低了目标主题对象与观察者之间的耦合关系两者之间是抽象耦合关系。被观察者发送通知所有注册的观察者都会收到信息【可以实现广播机制】
缺点
如果观察者非常多的话那么所有的观察者收到被观察者发送的通知会耗时如果被观察者有循环依赖的话那么被观察者发送通知会使观察者循环调用会导致系统崩溃 5使用场景
对象间存在一对多关系一个对象的状态发生改变会影响其他对象。当一个抽象模型有两个方面其中一个方面依赖于另一方面时。 6JDK中提供的实现
在 Java 中通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式只要实现它们的子类就可以编写观察者模式实例。
1Observable类Observable 类是抽象目标类被观察者它有一个 Vector 集合成员变量用于保存所有要通知的观察者对象下面来介绍它最重要的 3 个方法。
void addObserver(Observer o) 方法用于将新的观察者对象添加到集合中。void notifyObservers(Object arg) 方法调用集合中的所有观察者对象的 update方法通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。void setChange() 方法用来设置一个 boolean 类型的内部标志注明目标对象发生了变化。当它为true时notifyObservers() 才会通知观察者。
2Observer 接口Observer 接口是抽象观察者它监视目标对象的变化当目标对象发生变化时观察者得到通知并调用 update 方法进行相应的工作。
【例】警察抓小偷警察抓小偷也可以使用观察者模式来实现警察是观察者小偷是被观察者。代码如下小偷是一个被观察者所以需要继承Observable类
public class Thief extends Observable {private String name;public Thief(String name) {this.name name;}public void setName(String name) {this.name name;}public String getName() {return name;}public void steal() {System.out.println(小偷我偷东西了有没有人来抓我嚣张);super.setChanged(); // changed truesuper.notifyObservers();}
}警察是一个观察者所以需要让其实现Observer接口
public class Policemen implements Observer {private String name;public Policemen(String name) {this.name name;}public void setName(String name) {this.name name;}public String getName() {return name;}Overridepublic void update(Observable o, Object arg) {System.out.println(警察 ((Thief) o).getName() 我已经盯你很久了你可以保持沉默但你所说的将成为呈堂证供);}
}客户端代码
public class Client {public static void main(String[] args) {// 创建小偷对象Thief t new Thief(隔壁老王);// 创建警察对象Policemen p new Policemen(小李);// 让警察盯着小偷被观察者小偷的Vector集合中加入观察者警察t.addObserver(p);// 小偷偷东西t.steal();}
}7、中介者模式 1概述
一般来说同事类之间的关系是比较复杂的多个同事类之间互相关联时他们之间的关系会呈现为复杂的网状结构这是一种过度耦合的架构即不利于类的复用也不稳定。例如在下左图中有六个同事类对象假如对象1发生变化那么将会有4个对象受到影响。如果对象2发生变化那么将会有5个对象受到影响。也就是说同事类之间直接关联的设计是不好的。
如果引入中介者模式那么同事类之间的关系将变为星型结构从下右图中可以看到任何一个类的变动只会影响的类本身以及中介者这样就减小了系统的耦合。一个好的设计必定不会把所有的对象关系处理逻辑封装在本类中而是使用一个专门的类来管理那些不属于自己的行为。 定义又叫调停模式定义一个中介角色来封装一系列对象之间的交互使原有对象之间的耦合松散且可以独立地改变它们之间的交互。 2结构
中介者模式包含以下主要角色
抽象中介者Mediator角色它是中介者的接口提供了同事对象注册与转发同事对象信息的抽象方法。具体中介者ConcreteMediator角色实现中介者接口定义一个 List 来管理同事对象协调各个同事角色之间的交互关系因此它依赖于同事角色。抽象同事类Colleague角色定义同事类的接口保存中介者对象提供同事对象交互的抽象方法实现所有相互影响的同事类的公共功能。具体同事类Concrete Colleague角色是抽象同事类的实现者当需要与其他同事对象交互时由中介者对象负责后续的交互。 3案例实现
【例】租房现在租房基本都是通过房屋中介房主将房屋托管给房屋中介而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者租房者和房主属于同事类。
类图如下
代码如下
// 抽象中介者类
public abstract class Mediator {// 联络抽象方法public abstract void contact(String message, Person person);
}// 抽象同事类
public abstract class Person {protected String name; // 租房者或房主的姓名protected Mediator mediator; // 中介者对象public Person(String name, Mediator mediator) {this.name name;this.mediator mediator;}
}// 房主具体同事角色类
public class HouseOwner extends Person {public HouseOwner(String name, Mediator mediator) {super(name, mediator);}// 和中介联系的方法public void contact(String message) {// 房主this与中介联系mediator.contact(message, this);}// 获取信息public void getMessage(String message) {System.out.println(房主 name 收到的信息为 message);}
}// 租房者具体同事角色类
public class Tenant extends Person {public Tenant(String name, Mediator mediator) {super(name, mediator);}// 和中介联系的方法public void contact(String message) {// 租房者this与中介联系mediator.contact(message, this);}// 获取信息public void getMessage(String message) {System.out.println(租房者 name 收到的信息为 message);}
}// 房屋中介具体的中介者角色类
public class MediatorStructure extends Mediator {// 聚合房主和租房者对象private HouseOwner houseOwner;private Tenant tenant;public void setHouseOwner(HouseOwner houseOwner) {this.houseOwner houseOwner;}public void setTenant(Tenant tenant) {this.tenant tenant;}Overridepublic void contact(String message, Person person) {if (person houseOwner) {System.out.println(房屋中介接收到房主的消息向租房者转告中...);tenant.getMessage(message);}else {System.out.println(房屋中介接收到租房者的消息向房主转告中...);houseOwner.getMessage(message);}}
}// 中介者模式实质就是将类似与图的多对多关系转化成两个一对多关系降低了租房者和房主的耦合度的同时提高二者关系的可维护性可拓展性(因为是转化成了一对多的关系)
public class Client {public static void main(String[] args) {// 创建中介者对象MediatorStructure mediator new MediatorStructure();// 创建租房者同事对象Tenant tenant new Tenant(李四, mediator);// 创建房主同事对象HouseOwner houseOwner new HouseOwner(张三, mediator);// 中介者要知道具体的租房者和房主mediator.setTenant(tenant);mediator.setHouseOwner(houseOwner);// 租房者与中介者沟通tenant.contact(tenant.name 需要租三室的房子);// 房主与中介者沟通houseOwner.contact(houseOwner.name 现出租一个三室的房屋);}
}上面案例中的实质就是将类似图的多对多关系转化成两个一对多关系降低了租房者和房主的耦合度。 同时提高二者关系的可维护性可拓展性因为是转化成了一对多的关系 4优缺点
优点
松散耦合中介者模式通过把多个同事对象之间的交互封装到中介者对象里面从而使得同事对象之间松散耦合基本上可以做到互补依赖。这样一来同事对象就可以独立地变化和复用而不再像以前那样“牵一处而动全身”了。集中控制交互多个同事对象的交互被封装在中介者对象里面集中管理使得这些交互行为发生变化的时候只需要修改中介者对象就可以了当然如果是已经做好的系统那么就扩展中介者对象而各个同事类不需要做修改。一对多关联转变为一对一的关联没有使用中介者模式的时候同事对象之间的关系通常是一对多的引入中介者对象以后中介者对象和同事对象的关系通常变成双向的一对一这会让对象的关系更容易理解和实现。
缺点
当同事类太多时中介者的职责将很大它会变得复杂而庞大以至于系统难以维护。 5使用场景
系统中对象之间存在复杂的引用关系系统结构混乱且难以理解。当想创建一个运行于多个类之间的对象又不想生成新的子类时。 8、迭代器模式 1概述
定义提供一个对象来顺序访问聚合对象中的一系列数据而不暴露聚合对象的内部表示。 2结构
迭代器模式主要包含以下角色
抽象聚合Aggregate角色定义存储、添加、删除聚合元素以及创建迭代器对象的接口。具体聚合ConcreteAggregate角色实现抽象聚合类返回一个具体迭代器的实例。抽象迭代器Iterator角色定义访问和遍历聚合元素的接口通常包含 hasNext()、next() 等方法。具体迭代器Concretelterator角色实现抽象迭代器接口中所定义的方法完成对聚合对象的遍历记录遍历的当前位置。 3案例实现
【例】定义一个可以存储学生对象的容器对象将遍历该容器的功能交由迭代器实现涉及到的类如下
代码如下定义学生类
// 学生类
public class Student {private String name; // 姓名private String number; // 学号public Student() {}public Student(String name, String number) {this.name name;this.number number;}public String getName() {return name;}public void setName(String name) {this.name name;}public String getNumber() {return number;}public void setNumber(String number) {this.number number;}Overridepublic String toString() {return Student{ name name \ , number number \ };}
}定义迭代器接口声明hasNext、next方法
// 抽象迭代器角色接口
public interface StudentIterator {// 判断是否还有元素boolean hasNext();// 获取下一个元素Student next();
}定义具体的迭代器类重写所有的抽象方法
import java.util.List;// 具体迭代器角色类
public class StudentIteratorImpl implements StudentIterator {private ListStudent list;private int position; // 用来记录遍历时的位置public StudentIteratorImpl(ListStudent list) {this.list list;position 0;}Overridepublic boolean hasNext() {return position list.size();}Overridepublic Student next() {return list.get(position);}
}定义抽象容器类包含添加元素删除元素获取迭代器对象的方法
// 抽象聚合角色接口
public interface StudentAggregate {// 添加学生功能void addStudent(Student stu);// 删除学生功能void removeStudent(Student stu);// 获取迭代器对象功能StudentIterator getStudentIterator();
}定义具体的容器类重写所有的方法
import java.util.ArrayList;
import java.util.List;// 具体聚合角色类
public class StudentAggregateImpl implements StudentAggregate {private ListStudent list new ArrayList();Overridepublic void addStudent(Student stu) {list.add(stu);}Overridepublic void removeStudent(Student stu) {list.remove(stu);}Overridepublic StudentIterator getStudentIterator() {return new StudentIteratorImpl(list);}
}测试类
public class Client {public static void main(String[] args) {// 创建聚合容器对象StudentAggregateImpl aggregate new StudentAggregateImpl();// 添加元素到聚合对象aggregate.addStudent(new Student(张三, 001));aggregate.addStudent(new Student(李四, 002));aggregate.addStudent(new Student(王五, 003));aggregate.addStudent(new Student(赵六, 004));// 获取迭代器对象遍历聚合对象StudentIterator iterator aggregate.getStudentIterator();while (iterator.hasNext()) System.out.println(iterator.next());}
}4优缺点
优点
它支持以不同的方式遍历一个聚合对象在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法我们也可以自己定义迭代器的子类以支持新的遍历方式。迭代器简化了聚合类。由于引入了迭代器在原有的聚合对象中不需要再自行提供数据遍历等方法这样可以简化聚合类的设计。在迭代器模式中由于引入了抽象层增加新的聚合类和迭代器类都很方便无须修改原有代码满足 “开闭原则” 的要求。
缺点
增加了类的个数这在一定程度上增加了系统的复杂性。 5使用场景
当需要为聚合对象提供多种遍历方式时。当需要为遍历不同的聚合结构提供一个统一的接口时。当访问一个聚合对象的内容而无须暴露其内部细节的表示时。 6JDK源码解析
迭代器模式在JAVA的很多集合类中被广泛应用接下来看看JAVA源码中是如何使用迭代器模式的。
ListString list new ArrayList();
IteratorString iterator list.iterator(); //list.iterator()方法返回的肯定是Iterator接口的子实现类对象
while (iterator.hasNext()) {System.out.println(iterator.next());
}看完这段代码是不是很熟悉与我们上面代码基本类似。单列集合都使用到了迭代器我们以ArrayList举例来说明
List抽象聚合类ArrayList具体的聚合类Iterator抽象迭代器list.iterator()返回的是实现了 Iterator 接口的具体迭代器对象
具体的来看看 ArrayList的代码实现
public class ArrayListE extends AbstractListEimplements ListE, RandomAccess, Cloneable, java.io.Serializable {public IteratorE iterator() {return new Itr();}private class Itr implements IteratorE {int cursor; // 下一个要返回元素的索引int lastRet -1; // 上一个返回元素的索引int expectedModCount modCount;Itr() {}//判断是否还有元素public boolean hasNext() {return cursor ! size;}//获取下一个元素public E next() {checkForComodification();int i cursor;if (i size)throw new NoSuchElementException();Object[] elementData ArrayList.this.elementData;if (i elementData.length)throw new ConcurrentModificationException();cursor i 1;return (E) elementData[lastRet i];}...
}这部分代码还是比较简单大致就是在 iterator 方法中返回了一个实例化的 Iterator 对象。Itr是一个内部类它实现了 Iterator 接口并重写了其中的抽象方法。 注意 当我们在使用JAVA开发的时候想使用迭代器模式的话只要让我们自己定义的容器类实现java.util.Iterable并实现其中的iterator()方法使其返回一个 java.util.Iterator 的实现类就可以了。 9、访问者模式 1概述
定义封装一些作用于某种数据结构中的各元素的操作它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。 2结构
访问者模式包含以下主要角色:
抽象访问者Visitor角色定义了对每一个元素Element访问的行为它的参数就是可以访问的元素它的方法个数理论上来讲与元素类个数Element的实现类个数是一样的从这点不难看出访问者模式要求元素类的个数不能改变。具体访问者ConcreteVisitor角色给出对每一个元素类访问时所产生的具体行为。抽象元素Element角色定义了一个接受访问者的方法accept其意义是指每一个元素都要可以被访问者访问。具体元素ConcreteElement角色 提供接受访问方法的具体实现而这个具体的实现通常情况下是使用访问者提供的访问该元素类的方法。对象结构Object Structure角色定义当中所提到的对象结构对象结构是一个抽象表述具体点可以理解为一个具有容器性质或者复合对象特性的类它会含有一组元素Element并且可以迭代这些元素供访问者访问。 3案例实现
【例】给宠物喂食现在养宠物的人特别多我们就以这个为例当然宠物还分为狗猫等要给宠物喂食的话主人可以喂其他人也可以喂食。
访问者角色给宠物喂食的人具体访问者角色主人、其他人抽象元素角色动物抽象类具体元素角色宠物狗、宠物猫结构对象角色主人家
类图如下
代码如下创建抽象访问者接口
// 抽象访问者角色类
public interface Person {// 喂食宠物狗void feed(Cat cat);// 喂食宠物猫void feed(Dog dog);
}创建不同的具体访问者角色主人和其他人都需要实现 Person接口
// 主人具体访问者角色类
public class Owner implements Person {Overridepublic void feed(Cat cat) {System.out.println(主人喂食猫);}Overridepublic void feed(Dog dog) {System.out.println(主人喂食狗);}
}// 其他人具体访问者角色类
public class Someone implements Person {Overridepublic void feed(Cat cat) {System.out.println(其他人喂食猫);}Overridepublic void feed(Dog dog) {System.out.println(其他人喂食狗);}
}定义抽象节点 – 宠物
// 抽象元素角色类
public interface Animal {// 接受指定访问者访问的功能void accept(Person person);
}定义实现Animal接口的 具体节点元素
// 宠物狗具体元素角色类
public class Dog implements Animal {Overridepublic void accept(Person person) {person.feed(this); // 访问者给宠物狗喂食System.out.println(宠物狗好吃汪);}
}// 宠物猫具体元素角色类
public class Cat implements Animal {Overridepublic void accept(Person person) {person.feed(this); // 访问者给宠物猫喂食System.out.println(宠物猫好吃喵);}
}定义对象结构此案例中就是主人的家
import java.util.ArrayList;
import java.util.List;// 家对象结构类
public class Home {// 声明一个集合对象存储元素对象private ListAnimal nodeList new ArrayList();// 添加元素功能public void add(Animal animal) {nodeList.add(animal);}// 元素接受访问者访问public void action(Person person) {// 遍历集合获取每一个元素让访问者访问每一个元素for (Animal animal : nodeList) {animal.accept(person);}}
}测试类
public class Client {public static void main(String[] args) {// 创建Home对象Home home new Home();// 添加元素到Home对象home.add(new Dog());home.add(new Cat());// 创建主人对象Owner owner new Owner();// 让主人喂食所有的宠物home.action(owner);System.out.println(----------------);// 创建其他人对象Someone someone new Someone();// 让其他人喂食所有的宠物home.action(someone);}
}4优缺点
优点
扩展性好在不修改对象结构中的元素的情况下为对象结构中的元素添加新的功能。复用性好通过访问者来定义整个对象结构通用的功能从而提高复用程度。分离无关行为通过访问者来分离无关的行为把相关的行为封装在一起构成一个访问者这样每一个访问者的功能都比较单一。
缺点
对象结构变化很困难在访问者模式中每增加一个新的元素类都要在每一个具体访问者类中增加相应的具体操作这违背了“开闭原则”。违反了依赖倒置原则访问者模式依赖了具体类而没有依赖抽象类。 5使用场景
对象结构相对稳定但其操作算法经常变化的程序。对象结构中的对象需要提供多种不同且不相关的操作而且要避免让这些操作的变化影响对象的结构。 6扩展双分派
访问者模式用到了一种双分派的技术。
① 分派变量被声明时的类型叫做变量的静态类型有些人又把静态类型叫做明显类型而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map new HashMap() map变量的静态类型是 Map 实际类型是 HashMap 。根据对象的类型而对方法进行的选择就是分派(Dispatch)分派(Dispatch)又分为两种即静态分派和动态分派。
静态分派(Static Dispatch) 发生在编译时期分派根据静态类型信息发生。静态分派对于我们来说并不陌生方法重载就是静态分派。
动态分派(Dynamic Dispatch) 发生在运行时期动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。
② 动态分派通过方法的重写支持动态分派。
public class Animal {public void execute() {System.out.println(Animal);}
}public class Dog extends Animal {Overridepublic void execute() {System.out.println(dog);}
}public class Cat extends Animal {Overridepublic void execute() {System.out.println(cat);}
}public class Client {public static void main(String[] args) {Animal a new Dog();a.execute();Animal a1 new Cat();a1.execute();}
}上面代码的结果大家应该直接可以说出来这不就是多态吗运行执行的是子类中的方法。
Java编译器在编译时期并不总是知道哪些代码会被执行因为编译器仅仅知道对象的静态类型而不知道对象的真实类型而方法的调用则是根据对象的真实类型而不是静态类型。
③ 静态分派通过方法重载支持静态分派。
public class Animal {
}public class Dog extends Animal {
}public class Cat extends Animal {
}public class Execute {public void execute(Animal a) {System.out.println(Animal);}public void execute(Dog d) {System.out.println(dog);}public void execute(Cat c) {System.out.println(cat);}
}public class Client {public static void main(String[] args) {Animal a new Animal();Animal a1 new Dog();Animal a2 new Cat();Execute exe new Execute();exe.execute(a);exe.execute(a1);exe.execute(a2);}
}运行结果
这个结果可能出乎一些人的意料了为什么呢重载方法的分派是根据静态类型进行的这个分派过程在编译时期就完成了。
④ 双分派所谓双分派技术就是在选择一个方法的时候不仅仅要根据消息接收者receiver的运行时区别还要根据参数的运行时区别。
public class Animal {public void accept(Execute exe) {exe.execute(this);}
}public class Dog extends Animal {public void accept(Execute exe) {exe.execute(this);}
}public class Cat extends Animal {public void accept(Execute exe) {exe.execute(this);}
}public class Execute {public void execute(Animal a) {System.out.println(animal);}public void execute(Dog d) {System.out.println(dog);}public void execute(Cat c) {System.out.println(cat);}
}public class Client {public static void main(String[] args) {Animal a new Animal();Animal d new Dog();Animal c new Cat();Execute exe new Execute();a.accept(exe);d.accept(exe);c.accept(exe);}
}在上面代码中客户端将Execute对象做为参数传递给Animal类型的变量调用的方法这里完成第一次分派这里是方法重写所以是动态分派也就是执行实际类型中的方法同时也将自己this作为参数传递进去这里就完成了第二次分派这里的Execute类中有多个重载的方法而传递进行的是this就是具体的实际类型的对象。
说到这里我们已经明白双分派是怎么回事了但是它有什么效果呢就是可以实现方法的动态绑定我们可以对上面的程序进行修改。
运行结果如下
双分派实现动态绑定的本质就是在重载方法委派的前面加上了继承体系中覆盖的环节由于覆盖是动态的所以重载就是动态的了。 10、备忘录模式 1概述
备忘录模式提供了一种状态恢复的实现机制使得用户可以方便地回到一个特定的历史步骤当新的状态无效或者存在问题时可以使用暂时存储起来的备忘录将状态复原很多软件都提供了撤销Undo操作如 Word、记事本、Photoshop、IDEA等软件在编辑时按 CtrlZ 组合键时能撤销当前操作使文档恢复到之前的状态还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
定义又叫快照模式在不破坏封装性的前提下捕获一个对象的内部状态并在该对象之外保存这个状态以便以后当需要时能将该对象恢复到原先保存的状态。 2结构
备忘录模式的主要角色如下
发起人Originator角色记录当前时刻的内部状态信息提供创建备忘录和恢复备忘录数据的功能实现其他业务功能它可以访问备忘录里的所有信息。备忘录Memento角色负责存储发起人的内部状态在需要的时候提供这些内部状态给发起人。管理者Caretaker角色对备忘录进行管理提供保存与获取备忘录的功能但其不能对备忘录的内容进行访问与修改。 备忘录有两个等效的接口 窄接口管理者(Caretaker)对象和其他发起人对象之外的任何对象看到的是备忘录的窄接口(narror Interface)这个窄接口只允许他把备忘录对象传给其他的对象。宽接口与管理者看到的窄接口相反发起人对象可以看到一个宽接口(wide Interface)这个宽接口允许它读取所有的数据以便根据这些数据恢复这个发起人对象的内部状态。 3案例实现
【例】游戏挑战BOSS游戏中的某个场景一游戏角色有生命力、攻击力、防御力等数据在打Boss前和后一定会不一样的我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。
要实现上述案例有两种方式
“白箱”备忘录模式“黑箱”备忘录模式 ① “白箱”备忘录模式
备忘录角色对任何对象都提供一个接口即宽接口备忘录角色的内部所存储的状态就对所有对象公开。类图如下
代码如下
// 游戏角色类发起人角色
public class GameRole {private int vit; // 生命力private int atk; // 攻击力private int def; // 防御力// 初始化内部状态public void initState() {this.vit 100;this.atk 100;this.def 100;}// 战斗public void fight() {this.vit 0;this.atk 0;this.def 0;}// 保存角色状态功能public RoleStateMemento saveState() {return new RoleStateMemento(vit, atk, def);}// 恢复角色初始化状态public void recoverState(RoleStateMemento roleStateMemento) {// 将备忘录对象中存储的状态赋值给当前对象的成员this.vit roleStateMemento.getVit();this.atk roleStateMemento.getAtk();this.def roleStateMemento.getDef();}// 展示内部状态功能public void stateDisplay() {System.out.println(角色生命力 vit);System.out.println(角色攻击力 atk);System.out.println(角色防御力 def);}public int getVit() {return vit;}public void setVit(int vit) {this.vit vit;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk atk;}public int getDef() {return def;}public void setDef(int def) {this.def def;}
}// 备忘录角色类存储历史状态
public class RoleStateMemento {private int vit; // 生命力private int atk; // 攻击力private int def; // 防御力public RoleStateMemento() {}public RoleStateMemento(int vit, int atk, int def) {this.vit vit;this.atk atk;this.def def;}public int getVit() {return vit;}public void setVit(int vit) {this.vit vit;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk atk;}public int getDef() {return def;}public void setDef(int def) {this.def def;}
}// 备忘录对象管理者对象
public class RoleStateCaretaker {// 声明RoleStateMemento类型的变量private RoleStateMemento roleStateMemento;public RoleStateMemento getRoleStateMemento() {return roleStateMemento;}public void setRoleStateMemento(RoleStateMemento roleStateMemento) {this.roleStateMemento roleStateMemento;}
}// 白箱备忘录模式-测试类
public class Client {public static void main(String[] args) {System.out.println(--------大战Boss前-------);// 创建游戏角色对象GameRole gameRole new GameRole();gameRole.initState(); // 初始化状态gameRole.stateDisplay();// 将游戏角色内部状态进行备份// 创建管理者对象RoleStateCaretaker roleStateCaretaker new RoleStateCaretaker();roleStateCaretaker.setRoleStateMemento(gameRole.saveState());// 进行战斗System.out.println(--------大战Boss后-------);gameRole.fight();gameRole.stateDisplay();System.out.println(--------恢复之前状态-------);// 通过管理者获取备忘录对象gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());gameRole.stateDisplay();}
}分析白箱备忘录模式是破坏封装性的。但是通过程序员自律同样可以在一定程度上实现模式的大部分用意。 ② “黑箱”备忘录模式
备忘录角色对发起人对象提供一个宽接口而为其他对象提供一个窄接口。在Java语言中实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。
将 RoleStateMemento 设为 GameRole 的内部类从而将 RoleStateMemento 对象封装在 GameRole 里面在外面提供一个标识接口 Memento 给 RoleStateCaretaker 及其他对象使用。这样 GameRole 类看到的是 RoleStateMemento 所有的接口而RoleStateCaretaker 及其他对象看到的仅仅是标识接口 Memento 所暴露出来的接口从而维护了封装型。类图如下
代码如下窄接口Memento这是一个标识接口因此没有定义出任何的方法
// 备忘录接口对外提供窄接口
public interface Memento {
}定义发起人类 GameRole并在内部定义备忘录内部类 RoleStateMemento该内部类设置为私有的
// 游戏角色类发起人角色
public class GameRole {private int vit; // 生命力private int atk; // 攻击力private int def; // 防御力// 初始化内部状态public void initState() {this.vit 100;this.atk 100;this.def 100;}// 战斗public void fight() {this.vit 0;this.atk 0;this.def 0;}// 保存角色状态功能public RoleStateMemento saveState() {return new RoleStateMemento(vit, atk, def);}// 恢复角色初始化状态public void recoverState(Memento memento) {RoleStateMemento roleStateMemento (RoleStateMemento) memento;// 将备忘录对象中存储的状态赋值给当前对象的成员this.vit roleStateMemento.getVit();this.atk roleStateMemento.getAtk();this.def roleStateMemento.getDef();}// 展示内部状态功能public void stateDisplay() {System.out.println(角色生命力 vit);System.out.println(角色攻击力 atk);System.out.println(角色防御力 def);}public int getVit() {return vit;}public void setVit(int vit) {this.vit vit;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk atk;}public int getDef() {return def;}public void setDef(int def) {this.def def;}private class RoleStateMemento implements Memento {private int vit; // 生命力private int atk; // 攻击力private int def; // 防御力public RoleStateMemento() {}public RoleStateMemento(int vit, int atk, int def) {this.vit vit;this.atk atk;this.def def;}public int getVit() {return vit;}public void setVit(int vit) {this.vit vit;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk atk;}public int getDef() {return def;}public void setDef(int def) {this.def def;}}
}负责人角色类 RoleStateCaretaker 能够得到的备忘录对象是以 Memento 为接口的由于这个接口仅仅是一个标识接口因此负责人角色不可能改变这个备忘录对象的内容
// 备忘录对象管理者对象
public class RoleStateCaretaker {// 声明Memento接口变量private Memento memento;public Memento getMemento() {return memento;}public void setMemento(Memento memento) {this.memento memento;}
}客户端测试类
// 黑箱备忘录模式-测试类
public class Client {public static void main(String[] args) {System.out.println(--------大战Boss前-------);// 创建游戏角色对象GameRole gameRole new GameRole();gameRole.initState(); // 初始化状态gameRole.stateDisplay();// 将游戏角色内部状态进行备份// 创建管理者对象RoleStateCaretaker roleStateCaretaker new RoleStateCaretaker();roleStateCaretaker.setMemento(gameRole.saveState());// 进行战斗System.out.println(--------大战Boss后-------);gameRole.fight();gameRole.stateDisplay();System.out.println(--------恢复之前状态-------);// 通过管理者获取备忘录对象gameRole.recoverState(roleStateCaretaker.getMemento());gameRole.stateDisplay();}
}4优缺点
优点
提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。实现了内部状态的封装。除了创建它的发起人之外其他对象都不能够访问这些状态信息黑箱备忘录。简化了发起人类。发起人不需要管理和保存其内部状态的各个备份所有状态信息都保存在备忘录中并由管理者进行管理这符合单一职责原则。
缺点
资源消耗大。如果要保存的内部状态信息过多或者特别频繁将会占用比较大的内存资源。 5使用场景
需要保存与恢复数据的场景如玩游戏时的中间结果的存档功能。需要提供一个可回滚操作的场景如 Word、记事本、Photoshopidea等软件在编辑时按 CtrlZ 组合键还有数据库中事务操作。 11、解释器模式 如上图设计一个软件用来进行加减计算。我们第一想法就是使用工具类提供对应的加法和减法的工具方法。
// 用于两个整数相加
public static int add(int a,int b){return a b;
}// 用于两个整数相加
public static int add(int a,int b,int c){return a b c;
}// 用于n个整数相加
public static int add(Integer ... arr) {int sum 0;for (Integer i : arr) {sum i;}return sum;
}上面的形式比较单一、有限如果形式变化非常多这就不符合要求因为加法和减法运算两个运算符与数值可以有无限种组合方式。比如 12345、123-4等等。显然现在需要一种翻译识别机器能够解析由数字以及 - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话能够逐个节点的进行读取解析运算这就是解释器模式的思维。
定义 给定一个语言定义它的文法表示并定义一个解释器这个解释器使用该标识来解释语言中的句子。 在解释器模式中我们需要将待解决的问题提取出规则抽象为一种“语言”。比如加减法运算规则为由数值和±符号组成的合法序列“13-2” 就是这种语言的句子。解释器就是要解析出来语句的含义。但是如何描述规则呢
文法语法规则文法是用于描述语言的语法结构的形式规则。
expression :: value | plus | minus
plus :: expression ‘’ expression
minus :: expression ‘-’ expression
value :: integer注意 这里的符号“::”表示“定义为”的意思竖线 | 表示或左右的其中一个引号内为字符本身引号外为语法。 上面规则描述为 表达式可以是一个值也可以是plus或者minus运算而plus和minus又是由表达式结合运算符构成值的类型为整型数。
抽象语法树在计算机科学中抽象语法树AbstractSyntaxTreeAST或简称语法树Syntax tree是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构树上的每个节点都表示源代码中的一种结构。用树形来表示符合文法规则的句子。 2结构
解释器模式包含以下主要角色。
抽象表达式Abstract Expression角色定义解释器的接口约定解释器的解释操作主要包含解释方法 interpret()。终结符表达式Terminal Expression角色是抽象表达式的子类用来实现文法中与终结符相关的操作文法中的每一个终结符都有一个具体终结表达式与之相对应。非终结符表达式Nonterminal Expression角色也是抽象表达式的子类用来实现文法中与非终结符相关的操作文法中的每条规则都对应于一个非终结符表达式。环境Context角色通常包含各个解释器需要的数据或是公共的功能一般用来传递被所有解释器共享的数据后面的解释器可以从这里获取这些值。客户端Client主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树然后调用解释器的解释方法当然也可以通过环境角色间接访问解释器的解释方法。 3案例实现
【例】设计实现加减法的软件
代码如下
// 抽象表达式类
public abstract class AbstractExpression {public abstract int interpret(Context context);
}// 封装变量的类终结符表达式角色
public class Variable extends AbstractExpression {// 声明存储语法中的变量名的成员变量private String name;public Variable(String name) {this.name name;}// 从环境里获取对应的变量数据Overridepublic int interpret(Context context) {// 直接返回变量的值return context.getValue(this);}Overridepublic String toString() {return name;}
}// 加法表达式类非终结符表达式角色
public class Plus extends AbstractExpression {private AbstractExpression left; // 号左边的表达式private AbstractExpression right; // 号右边的表达式public Plus(AbstractExpression left, AbstractExpression right) {this.left left;this.right right;}Overridepublic int interpret(Context context) {// 将左边表达式的结果和右边表达式的结果进行相加return left.interpret(context) right.interpret(context);}Overridepublic String toString() {return ( left.toString() right.toString() );}
}// 减法表达式类非终结符表达式角色
public class Minus extends AbstractExpression {private AbstractExpression left; // -号左边的表达式private AbstractExpression right; // -号右边的表达式public Minus(AbstractExpression left, AbstractExpression right) {this.left left;this.right right;}Overridepublic int interpret(Context context) {// 将左边表达式的结果和右边表达式的结果进行相减return left.interpret(context) - right.interpret(context);}Overridepublic String toString() {return ( left.toString() - right.toString() );}
}// 环境角色类
public class Context {// 定义一个Map集合用来存储变量及对应的值private MapVariable, Integer map new HashMap();// 添加变量的功能public void assign(Variable var, Integer value) {map.put(var, value);}// 根据变量获取对应的值public int getValue(Variable var) {return map.get(var);}
}// 测试类
public class Client {public static void main(String[] args) {// 创建环境对象Context context new Context();// 创建多个变量对象Variable a new Variable(1);Variable b new Variable(2);Variable c new Variable(3);Variable d new Variable(4);// 将变量存储到环境对象中context.assign(a, 1);context.assign(b, 2);context.assign(c, 3);context.assign(d, 4);// 获取抽象语法树 a b - c dAbstractExpression expression new Minus(a, new Plus(new Minus(b, c), d)); // (a-((b-c)d))// 解释计算int result expression.interpret(context);System.out.println(expression result); // (1-((2-3)4))-2}
}4优缺点
优点
易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类因此可以方便地实现一个简单的语言。实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的这些类的代码编写都不会特别复杂。增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类原有表达式类代码无须修改符合 “开闭原则”。
缺点
对于复杂文法难以维护。在解释器模式中每一条规则至少需要定义一个类因此如果一个语言包含太多文法规则类的个数将会急剧增加导致系统难以管理和维护。执行效率较低。由于在解释器模式中使用了大量的循环和递归调用因此在解释较为复杂的句子时其速度很慢而且代码的调试过程也比较麻烦。 5使用场景
当语言的文法较为简单且执行效率不是关键问题时。当问题重复出现且可以用一种简单的语言来进行表达时。当一个语言需要解释执行并且语言中的句子可以表示为一个抽象语法树的时候。 本篇到这里就结束啦感谢各位大佬观看希望本文能够为您提供有益的参考和启示创作不易还请各位大佬点赞关注支持~ 祝大家学习进步工作顺利天天开心哦 (●’◡’●) 让我们一起在编程道路上不断前行