本系列文章共分为六篇:
设计模式(一)设计模式的分类与区别
设计模式(二)创建型模式介绍及实例
设计模式(三)结构型模式介绍及实例
设计模式(四)行为型模式介绍及实例(上)
设计模式(五)行为型模式介绍及实例(下)
设计模式(六)设计模式的常见应用
定义一个中介对象来封装一系列对象之间的交互
,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。
一句话总结:禁止多个对象之间相互"通信",交给中介者去进行,所有对象和交互者"通信"即可。
中介者模式UML图示如下:
Colleague叫抽象同事类,而ConcreteColleague是具体同事类,每个具体同事类只知道自己的行为,而不了解其他同事类的情况,他们都认识中介者对象;Mediator抽象中介者,定义了同事对象到中介者对象的接口,ConcreteMediator是具体中介者对象,实现抽象类的方法,它需要知道所有具体同事类,并从具体同事接收消息,向具体同事对象发出命令。
优点:
降低了对象之间的耦合性
,使得对象易于独立地被复用。缺点:
此处以在班级里收作业为例,一个班级里有不同的课代表,他们会发起收作业的动作,同时,他们又要交作业,这恰好符合中介者模式。示例代码如下:
/*抽象中介者*/
public abstract class Mediator {
public abstract void register(Student stu); //将stu加入通知列表
public abstract void relay(Student stu); //转发消息
}
/*具体中介者*/
public class RealMediator extends Mediator{
private List<Student> stus=new ArrayList<Student>();
public void register(Student stu){
if(!stus.contains(stu)){
stus.add(stu);
stu.setMedium(this);
}
}
public void relay(Student stu1){
for(Student stu:stus){
if(!stu.equals(stu1)){
((Student)stu).receive();
}
}
}
}
/*抽象同事类*/
public abstract class Student {
protected Mediator mediator;
public void setMedium(Mediator mediator){
this.mediator=mediator;
}
public abstract void receive();
public abstract void send();
}
/*具体同事类:物理课代表*/
public class PhysicsClassrepresentative extends Student{
@Override
public void receive() {
System.out.println("物理课代表说:收到,马上交化学作业");
}
@Override
public void send() {
System.out.println("物理课代表说:收物理作业了");
mediator.relay(this); //请中介者转发
}
}
/*具体同事类:化学课代表*/
public class ChemistryClassrepresentative extends Student{
@Override
public void receive() {
System.out.println("化学课代表说:收到,马上交物理作业");
}
@Override
public void send() {
System.out.println("化学课代表说:收化学作业了");
mediator.relay(this); //请中介者转发
}
}
/*测试类*/
public class MediatorTest {
public static void main(String[] args) {
Mediator md=new RealMediator();
Student stu1,stu2;
stu1=new PhysicsClassrepresentative();
stu2=new ChemistryClassrepresentative();
md.register(stu1);
md.register(stu2);
stu1.send();
System.out.println("-------------");
stu2.send();
}
}
结果如下:
物理课代表说:收物理作业了
化学课代表说:收到,马上交物理作业
化学课代表说:收化学作业了
物理课代表说:收到,马上交化学作业
对象之间存在复杂的网状结构关系
而导致依赖关系混乱且难以复用时。 提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
。
一句话总结:如何遍历对象。
优点:
访问一个聚合对象的内容而无须暴露它的内部表示
。遍历
任务交由迭代器完成,这简化了聚合类。以不同方式遍历一个集合
,甚至可以自定义迭代器的子类以支持新的遍历。封装性良好
,为遍历不同的聚合结构提供一个统一的接口。缺点:
此处以中国知名的手机品牌为例,这些品牌有华为、小米、OPPO、VIVO、锤子等。示例代码如下:
/*抽象聚合*/
public interface Aggregate {
public void add(Phone phone);
public void remove(Phone phone);
public Iterator getIterator();
}
/*具体聚合*/
public class ConcreteAggregate implements Aggregate{
private List<Phone> phoneList=new ArrayList<Phone>();
public void add(Phone phone){
phoneList.add(phone);
}
public void remove(Phone phone){
phoneList.remove(phone);
}
public Iterator getIterator(){ return(new ConcreteIterator(phoneList)); }
}
/*抽象迭代器*/
public interface Iterator {
Phone first();
Phone next();
boolean hasNext();
}
/*具体迭代器*/
public class ConcreteIterator implements Iterator{
private List<Phone> list=null;
private int index=-1;
public ConcreteIterator(List<Phone> list){
this.list=list;
}
public boolean hasNext(){
if(index<list.size()-1)
return true;
else
return false;
}
public Phone first(){
index=0;
Phone phone=list.get(index);;
return phone;
}
public Phone next(){
Phone phone=null;
if(this.hasNext()){
phone=list.get(++index);
}
return phone;
}
}
/*实体类:手机*/
public class Phone {
private String name;
public Phone(String name){
this.name = name;
}
public String getName(){ return this.name; }
}
/*测试类*/
public class IteratorTest {
public static void main(String[] args) {
Aggregate ag=new ConcreteAggregate();
ag.add(new Phone("华为"));
ag.add(new Phone("小米"));
ag.add(new Phone("OPPO"));
ag.add(new Phone("VIVO"));
ag.add(new Phone("锤子"));
System.out.print("聚合的手机品牌有:\n");
Iterator it=ag.getIterator();
while(it.hasNext()){
Phone phone=it.next();
System.out.print(phone.getName()+"\n");
}
Phone firstPhone=it.first();
System.out.println("\n第一是:"+firstPhone.getName());
}
}
测试结果如下:
聚合的手机品牌有:
华为
小米
OPPO
VIVO
锤子
第一是:华为
多种遍历方式
时。访问一个聚合对象的内容而无须暴露其内部细节的表示
时。给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。
一句话总结:解释带有特定语法的句子。
优点:
缺点:
解释器模式UML图示:
AbstractException(抽象表达式),声明一个抽象的解释操作,这个接口为抽象语法树中所有节点所共享。
TerminalException(终结符表达式),实现与文法中的终结符相关联的解释操作。实现抽象表达式中所要求的接口。
NotTerminalException(非终结符表达式),为文法中的非终结符实现解释操作。
Context,包含解释器之外的一些全局信息。
此处以员工是否需要参加新人培训为例,在无锡和北京两地工作的3年以下的员工需要参加,其他的则不需要。代码示例如下:
/*抽象表达式*/
public interface Expression {
public boolean interpret(String info);
}
/*终结表达式*/
public class TerminalExpression implements Expression{
private Set<String> set= new HashSet<String>();
public TerminalExpression(String[] data){
for(int i=0;i<data.length;i++)set.add(data[i]);
}
public boolean interpret(String info){
if(set.contains(info)){
return true;
}
return false;
}
}
/*非终结符表达式*/
public class AndExpression implements Expression{
private Expression city=null;
private Expression person=null;
public AndExpression(Expression city,Expression person){
this.city=city;
this.person=person;
}
public boolean interpret(String info){
String s[]=info.split(",");
return city.interpret(s[0])&&person.interpret(s[1]);
}
}
/*环境*/
public class Context {
private String[] citys={"无锡","北京"};
private String[] persons={"1年","2年","3年"};
private Expression cityPerson;
public Context(){
Expression city=new TerminalExpression(citys);
Expression person=new TerminalExpression(persons);
cityPerson=new AndExpression(city,person);
}
public void train(String info){
boolean ok=cityPerson.interpret(info);
if(ok) System.out.println("您入职年份较少,需要参加新人培训");
else System.out.println("您入职年份较多或工作地不在无锡、北京,不用再参加新人培训");
}
}
/*测试类*/
public class InterpreterTest {
public static void main(String[] args) {
Context personnel=new Context();
personnel.train("无锡,2年");
personnel.train("北京,5年");
personnel.train("无锡,3年");
personnel.train("广州,2年");
personnel.train("北京,2年");
}
}
测试结果如下:
您入职年份较少,需要参加新人培训
您入职年份较多或工作地不在无锡、北京,不用再参加新人培训
您入职年份较少,需要参加新人培训
您入职年份较多或工作地不在无锡、北京,不用再参加新人培训
您入职年份较少,需要参加新人培训
语言的文法较为简单
,且执行效率不是关键问题时。注意:尽量不要在重要的模块中使用解释器模式,否则维护会是一个很大的问题。在项目中可以使用shell、JRuby、Groovy等脚本语言来代替解释器模式,弥补Java编译型语言的不足。
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任
分割开。
一句话总结:将命令/请求封装为对象,在对象与对象之间传递信息,降低对象之间的耦合度。
Command模式将操作的执行逻辑封装到一个个Command对象中,解耦了操作发起者和操作执行逻辑之间的耦合关系:操作发起者要进行一个操作,不用关心具体的执行逻辑,只需创建一个相应的Command实例,调用它的执行接口即可。
命令模式UML图示如下:
Command类,用来声明执行操作的接口。
ConcreteCommand类,将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现Execute。
Invoker类,要求该命令执行这个请求。
Receiver类,知道如何实施与执行一个与请求相关的操作,任何类都可能成为一个接收者。
优点:
缺点:
此处以项目经理追问各研发小组进度为例,项目经理为调用者,查询项目进度为命令,各研发小组Leader为接收者。示例代码如下:
/*抽象命令*/
public abstract class Command {
public abstract void execute();
}
/*具体命令:查询项目进度*/
public class QueryProjectCommand extends Command{
private GroupLeaderA groupLeaderA;
private GroupLeaderB groupLeaderB;
QueryProjectCommand(){
groupLeaderA = new GroupLeaderA();
groupLeaderB = new GroupLeaderB();
}
public void execute(){
groupLeaderA.action();
groupLeaderB.action();
}
}
/*接收者A:A研发组Leader*/
public class GroupLeaderA {
public void action(){
System.out.println("A项目组进度为:已完成70%功能开发");
}
}
/*接收者B:B研发组Leader*/
public class GroupLeaderB {
public void action(){
System.out.println("B项目组进度为:已完成50%功能开发");
}
}
/*调用者:项目经理*/
public class ProjectManager {
private Command command;
public ProjectManager(Command command){
this.command=command;
}
public void setCommand(Command command){
this.command=command;
}
public void call(){
System.out.println("项目经理查询各小组研发进度");
command.execute();
}
}
/*测试类*/
public class CommandTest {
public static void main(String[] args){
Command cmd = new QueryProjectCommand();
ProjectManager projectManager = new ProjectManager(cmd);
System.out.println("客户访问调用者(项目经理)的call()方法...");
projectManager.call();
}
}
输出结果如下:
客户访问调用者(项目经理)的call()方法…
项目经理查询各小组研发进度
A项目组进度为:已完成70%功能开发
B项目组进度为:已完成50%功能开发
请求调用者与请求接收者解耦
时,命令模式使得调用者和接收者不直接交互。经常增加或删除命令
时,命令模式比较方便实现这些功能。命令的撤销(Undo)操作和恢复(Redo)
操作时,可以将命令对象存储起来,采用备忘录模式来实现。 为了避免请求发送者与多个请求处理者耦合
在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链
;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
一句话总结:多个请求处理者记录对下一个处理者的引用(形成一条链),降低请求发起者和多个处理者之间的耦合。
责任链模式UML图示如下:
Handler类,定一个处理请示的接口。
ConcreteHandler类,具体处理者,处理它所负责的请求,可访问它的后继者,如果可处理该请求,就处理之,否则就将该请求转发给它的后继者。
注意事项:链中节点数量需要控制,避免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。
优点:
降低了对象之间的耦合度
增强了系统的可扩展性
增强了给对象指派职责的灵活性
责任链简化了对象之间的连接
责任分担
缺点:
不能保证每个请求一定被处理
较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响
。此处以代购为例,不同的代购者可以购买不同价位的产品,代购者A可以购买0-300元的产品,代购者B可以购买300-600元的产品,代购者C可以购买600元以上的产品。示例代码如下:
/*抽象处理者:代购者*/
public abstract class AgentBuyer {
private AgentBuyer next;
public void setNext(AgentBuyer next){
this.next=next;
}
public AgentBuyer getNext(){ return next; }
//处理请求的方法,此处实现的方法没有返回值。其实在实现处理请求的方法上,可以设定一个方法返回值,用来决定是否还将责任链上的处理继续进行下去。
public abstract void buyGoods(int money);
}
/*具体处理者:代购者A*/
public class AgentBuyerA extends AgentBuyer{
@Override
public void buyGoods(int money) {
if(money>0 && money <= 300) {
System.out.println("您可以通过代购者A购买"+money+"元的商品");
}
else{
if(getNext() != null) {
getNext().buyGoods(money);
}
}
}
}
/*具体处理者:代购者B*/
public class AgentBuyerB extends AgentBuyer{
@Override
public void buyGoods(int money) {
if(money>300 && money <= 600) {
System.out.println("您可以通过代购者B购买"+money+"元的商品");
}else{
if(getNext() != null) {
getNext().buyGoods(money);
}
}
}
}
/*具体处理者:代购者C*/
public class AgentBuyerC extends AgentBuyer{
@Override
public void buyGoods(int money) {
System.out.println("您可以通过代购者C购买"+money+"元的商品");
}
}
/*测试类*/
public class ResponsibilityTest {
public static void main(String[] args){
//组装责任链
AgentBuyer gentBuyerA =new AgentBuyerA();
AgentBuyer gentBuyerB =new AgentBuyerB();
AgentBuyer gentBuyerC =new AgentBuyerC();
gentBuyerA.setNext(gentBuyerB);
gentBuyerB.setNext(gentBuyerC);
gentBuyerA.buyGoods(500);
gentBuyerA.buyGoods(1000);
}
}
输出结果如下:
您可以通过代购者B购买500元的商品
您可以通过代购者C购买1000元的商品
多个对象可以处理一个请求
,哪个对象处理该请求由运行时刻自动确定。动态指定一组对象处理请求
,或添加新的处理者。不明确指定请求处理者
的情况下,向多个处理者中的一个提交请求。责任链的一个典型应用是拦截器/过滤器模式。
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式
。
它将对数据的操作与数据结构进行分离
,是行为类模式中最复杂的一种模式。
一句话总结:定义一个对象,可以在不改变复杂数据对象结构的前提下,访问数据结构中的各元素。
访问者模式UML图示如下:
Visitor类,为对象结构中ConcreteElement的每一个类声明一个Visit操作。
ConcreteVisitor1和ConcreteVisitor2类,具体访问者,实现每个由Visitor声明的操作。
Element类,定义一个Accept操作,它以一个访问者为参数。
ConcreteElementA和ConcreteElementB类,具体元素,实现accept操作。
优点:
在不修改对象结构中的元素
的情况下,为对象结构中的元素添加新的功能
。数据结构与作用于结构上的操作解耦
,使得操作集合可相对自由地演化而不影响系统的数据结构。把相关的行为封装在一起,构成一个访问者
,使每一个访问者的功能都比较单一。缺点:
每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作(也就是说访问者模式适合元素结构比较固定的场景)
,这违背了“开闭原则”。此处以老师查询学生成绩为例,一个学生有在不同科目上有不同的成绩,老师除了询问本科目的成绩之外,有时也会询问一下其他科目的成绩,来看下学生是否在其他科目投入精力过多,导致自己教授的科目成绩下降。示例代码如下:
/*抽象访问者:老师*/
public interface Teacher {
void query(PhysicsAchievement physicsAchievement);
void query(ChemistryAchievement chemistryAchievement);
}
/*具体访问者1:物理老师*/
public class PhysicsTeacher implements Teacher{
public void query(PhysicsAchievement element){
System.out.println("物理老师查询物理课成绩,"+element.operationA());
}
public void query(ChemistryAchievement element){
System.out.println("物理老师查询化学课成绩,"+element.operationB());
}
}
/*具体访问者2:化学老师*/
public class ChemistryTeacher implements Teacher{
public void query(PhysicsAchievement element){
System.out.println("化学老师查询物理课成绩,"+element.operationA());
}
public void query(ChemistryAchievement element){
System.out.println("化学老师查询化学课成绩,"+element.operationB());
}
}
/*抽象元素:成绩*/
public interface Achievement {
void accept(Teacher teacher);
}
/*具体元素1:物理成绩*/
public class PhysicsAchievement implements Achievement{
public void accept(Teacher teacher){
teacher.query(this);
}
public String operationA(){ return "物理成绩85分"; }
}
/*具体成绩2:化学成绩*/
public class ChemistryAchievement implements Achievement{
public void accept(Teacher teacher){
teacher.query(this);
}
public String operationB(){ return "化学成绩90分"; }
}
/*对象结构*/
public class ObjectStructure {
private List<Achievement> list=new ArrayList<Achievement>();
public void accept(Teacher visitor){
Iterator<Achievement> i=list.iterator();
while(i.hasNext()){
((Achievement) i.next()).accept(visitor);
}
}
public void add(Achievement element){
list.add(element);
}
public void remove(Achievement element){
list.remove(element);
}
}
/*测试类*/
public class VisitorTest {
public static void main(String[] args){
ObjectStructure os=new ObjectStructure();
os.add(new PhysicsAchievement());
os.add(new ChemistryAchievement());
Teacher visitor=new PhysicsTeacher();
os.accept(visitor);
visitor=new ChemistryTeacher();
os.accept(visitor);
}
}
结果如下:
物理老师查询物理课成绩,物理成绩85分
物理老师查询化学课成绩,化学成绩90分
化学老师查询物理课成绩,物理成绩85分
化学老师查询化学课成绩,化学成绩90分