3.2.5 外观模式

是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一的接口,外部应用程序不需要关心子系统是怎么实现的,这样大大降低了应用程序的复杂性,提高了应用程序的可维护性。(其实就是模块定义的接口facade,系统之间相互调用的时候用的

外观模式的优点:

  • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
  • 对客户屏蔽了子系统的组件,减少了客户处理对象的数目,并使得子系统使用起来更加容易。
  • 降低了大型软件系统中的编译依赖性,简化了系统在不同平台的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

外观模式的缺点:

  • 不能很好的限制客户使用子系统类
  • 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。如果引入抽象的外观类,则在一定程度上可以解决该问题。

外观模式的结构和实现

外观模式结构比较简单,主要定义了一个高层的接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。
    外观模式的结构:

  • 外观角色:为多个子系统提供一个统一的接口。
  • 子系统:实现了系统的部分功能,客户端可以通过外观角色访问它。
  • 客户角色:通过一个外观角色访问各个子系统的角色。

//抽象外观角色 - 为了不违背开闭原则,增加抽象外观角色

public abstract class AbstractFacade {

public abstract void method1();

public abstract void method2();

}

// 具体外观角色

public class Facade1 extends AbstractFacade {

private System01 system01 = new System01();

private System02 system02 = new System02();

private System03 system03 = new System03();

@Override

public void method1() {

// TODO Auto-generated method stub

system01.run();

system02.run();

}

@Override

public void method2() {

// TODO Auto-generated method stub

system02.run();

system03.run();

}

}

// 具体外观角色

public class Facade2 extends AbstractFacade {

private System01 system01 = new System01();

private System02 system02 = new System02();

private System03 system03 = new System03();

@Override

public void method1() {

// TODO Auto-generated method stub

system01.run();

system02.run();

system03.run();

}

@Override

public void method2() {

// TODO Auto-generated method stub

system03.run();

}

}

// 子系统 01

public class System01 {

public void run(){

System.out.println("子系统_01被调用...");

}

}

//子系统 02

public class System02 {

public void run(){

System.out.println("子系统_02被调用...");

}

}

//子系统 03

public class System03 {

public void run(){

System.out.println("子系统_03被调用...");

}

}

public class Main {

public static void main(String[] args) {

AbstractFacade facade = new Facade2();

facade.method1();

}

}

运行结果:

子系统_01被调用...

子系统_02被调用...

子系统_03被调用...

外观模式的应用场景

通常情况下如下几种情况可以使用外观模式:

  • 对分层结构系统构建时候,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖
  • 当一个复杂系统的子系统很多时,外观模式可为其设计一个简单的接口供外部使用
  • 当客户端与多个子系统之间存在很大联系,引入外观模式可以将他们分离,从而提高了子系统的可靠性和移植性。

3.2.6 享元模式

运用共享技术有效的支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
    享元模式的优点:相同的对象只需要保存一份,降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
    享元模式的缺点:

  • 为了使对象可以共享,需要将一些不能共享的对象外部化,增加了系统的复杂性
  • 读取享元模式的外部状态会使得运行时间稍微变长。

享元模式的结构和实现

享元模式的结构

享元模式中存在两种状态:

  • 内部状态:不会随着环境的变化而改变的可共享部分。
  • 外部状态:随着环境的改变而改变的不可共享的部分。享元模式的实现要领就是区分应用中的两种状态,并将外部状态外部化。

享元模式的结构:

  • 抽象享元角色:是所有具体的享元元素得基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  • 具体享元角色:实现抽象享元角色中的所规定的接口。
  • 非享元角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
  • 享元工厂角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在则创建一个新的享元对象给客户。

//抽象享元角色 - 建筑

public interface JianZhu {

public void use();

}

//具体享元角色 - 体育场

public class TiYuGuan implements JianZhu{

private String name;

private String shape;

private String yundong;

public TiYuGuan(String yundong) {

super();

this.yundong = yundong;

}

@Override

public void use() {

// TODO Auto-generated method stub

System.out.println("体育馆被使用来召开运动:("+yundong +")体育场形状为:("+shape+")运动名称为("+name+")");

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getShape() {

return shape;

}

public void setShape(String shape) {

this.shape = shape;

}

public String getYundong() {

return yundong;

}

public void setYundong(String yundong) {

this.yundong = yundong;

}

}

//角色工厂

public class JianZhuFactory {

private static final Map<String,TiYuGuan> factory = new HashMap<String, TiYuGuan>();

public static TiYuGuan getTyg(String yundong){

TiYuGuan tyg = factory.get(yundong);

if(null == tyg){

tyg = new TiYuGuan(yundong);

factory.put(yundong, tyg);

}

return tyg;

}

public static int getSize(){

return factory.size();

}

}

public class Main {

public static void main(String[] args) {

JianZhuFactory factory = new JianZhuFactory();

for(int i = 0 ; i < 10 ; i++){

//自始至终总共有10个数据,但是只创建了一个共享对象

TiYuGuan tiyuguan = factory.getTyg("足球");

tiyuguan.setName("中国体育馆");

tiyuguan.setShape("圆形");

tiyuguan.use();

System.out.println(factory.getSize());

}

}

}

运行结果:

体育馆被使用来召开运动:(足球)体育场形状为:(圆形)运动名称为(中国体育馆)

1

体育馆被使用来召开运动:(足球)体育场形状为:(圆形)运动名称为(中国体育馆)

1

体育馆被使用来召开运动:(足球)体育场形状为:(圆形)运动名称为(中国体育馆)

1

体育馆被使用来召开运动:(足球)体育场形状为:(圆形)运动名称为(中国体育馆)

1

体育馆被使用来召开运动:(足球)体育场形状为:(圆形)运动名称为(中国体育馆)

1

体育馆被使用来召开运动:(足球)体育场形状为:(圆形)运动名称为(中国体育馆)

1

体育馆被使用来召开运动:(足球)体育场形状为:(圆形)运动名称为(中国体育馆)

1

体育馆被使用来召开运动:(足球)体育场形状为:(圆形)运动名称为(中国体育馆)

1

体育馆被使用来召开运动:(足球)体育场形状为:(圆形)运动名称为(中国体育馆)

1

体育馆被使用来召开运动:(足球)体育场形状为:(圆形)运动名称为(中国体育馆)

1

享元模式的应用场景

  • 系统中采用大量相同或者相似的对象,这些对象耗费大量的内存资源
  • 大部分的对象可以按照内部状态进行分组,且可将不同的部分外部化,这样每一组只需要保存一个内部状态
  • 由于享元模式需要额外维护一个保存享元的数据结构,所以应当有足够的享元实例才能使用享元模式

3.2.7 组合模式

组合模式的定义和特点

组合模式的定义

组合模式有时又叫做部分-整体模式,它是一种对象组合成树状的层次结构模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性。

组合模式的特点

组合模式的优点:

  • 组合模式使得客户端代码可以一致的处理单个对象和组合对象,无需关心自己处理的是单个对象还是组合对象,简化了客户端代码
  • 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源码,满足“开闭原则”。

组合模式的缺点:

  • 设计比较复杂,客户端需要花更多的时间来清理类之间的层次关系
  • 不容易限制容器中的构建
  • 不容易用继承的方法来增加构件的新功能

组合模式的结构和实现

组合模式的结构

组合模式主要包含以下结构:

  • 抽象构件角色:它的主要作用是为了树叶构件和树枝构件声明的公共接口,并实现他们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件来完成
  • 树叶构件角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中声明的公共接口
  • 树枝结构角色:是组合关系中分支节点对象,它有子节点。实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含Add(),Remove(),GetChild()等方法。

组合模式分为透明式的组合模式和安全式的组合模式

  • 透明式组合模式:在该方式中,由于抽象构件声明了子类中的全部方法,所以客户端无需区别树叶对象和树枝对象,对客户端来说是透明的。但缺点:树叶构件本来没有Add(),Remove()及GetChild()方法,却要他们(空实现或抛异常),这样会带来一些列的安全问题。
  • 安全式组合模式:在该方式中,将管理子构件的方法移到树枝构件当中,抽闲构件和树叶构件没有对子对象的管理方法,这样避免了透明式组合模式中的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。

//透明式组合模式

//抽象构件角色

public interface Compent {

public void add(Compent c);

public void remove(Compent c);

public Compent getChild(int i);

public void operation();

}

// 树枝组建角色

public class Branch implements Compent{

private List<Compent> children = new ArrayList<Compent>();

@Override

public void add(Compent c) {

// TODO Auto-generated method stub

children.add(c);

}

@Override

public void remove(Compent c) {

// TODO Auto-generated method stub

children.remove(c);

}

@Override

public Compent getChild(int i) {

// TODO Auto-generated method stub

return children.get(i);

}

@Override

public void operation() {

// TODO Auto-generated method stub

for(Compent o : children){

o.operation();

}

}

}

public class Leaf implements Compent{

private String name;

@Override

public void add(Compent c) {

// TODO Auto-generated method stub

}

@Override

public void remove(Compent c) {

// TODO Auto-generated method stub

}

@Override

public Compent getChild(int i) {

// TODO Auto-generated method stub

return null;

}

@Override

public void operation() {

// TODO Auto-generated method stub

System.out.println("树叶"+name+":被访问!");

}

public Leaf(String name) {

super();

this.name = name;

}

}

运行结果:

树叶leaf1:被访问!

树叶leaf3:被访问!

树叶leaf2:被访问!

说明:假如李先生到韶关“天街e角”生活用品店购物,用 1 个红色小袋子装了 2 包婺源特产(单价 7.9 元)、1 张婺源地图(单价 9.9 元);用 1 个白色小袋子装了 2 包韶关香藉(单价 68 元)和 3 包韶关红茶(单价 180 元);用 1 个中袋子装了前面的红色小袋子和 1 个景德镇瓷器(单价 380 元);用 1 个大袋子装了前面的中袋子、白色小袋子和 1 双李宁牌运动鞋(单价 198 元)。

最后“大袋子”中的内容有:{1 双李宁牌运动鞋(单价 198 元)、白色小袋子{2 包韶关香菇(单价 68 元)、3 包韶关红茶(单价 180 元)}、中袋子{1 个景德镇瓷器(单价 380 元)、红色小袋子{2 包婺源特产(单价 7.9 元)、1 张婺源地图(单价 9.9 元)}}},现在要求编程显示李先生放在大袋子中的所有商品信息并计算要支付的总价。

// 袋子 - 抽象类角色

public interface Bag {

public void add(Bag bag);

public void remove(Bag bag);

public Bag getChild(int i);

public void operation();

public int calculation();

}

//树枝角色

public class BagBranch implements Bag{

private List<Bag> childs = new ArrayList<Bag>();

private String name;

@Override

public void add(Bag bag) {

// TODO Auto-generated method stub

childs.add(bag);

}

@Override

public void remove(Bag bag) {

// TODO Auto-generated method stub

childs.remove(bag);

}

@Override

public Bag getChild(int i) {

// TODO Auto-generated method stub

return childs.get(i);

}

@Override

public void operation() {

// TODO Auto-generated method stub

for(Bag bag : childs){

bag.operation();

}

}

@Override

public int calculation() {

// TODO Auto-generated method stub

int s = 0;

for(Bag bag : childs){

s += bag.calculation();

}

return s;

}

public BagBranch(String name) {

super();

this.name = name;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

//树叶角色

public class BagDetail implements Bag{

private String name ;

private int price;

private int num;

@Override

public void add(Bag bag) {

// TODO Auto-generated method stub

}

@Override

public void remove(Bag bag) {

// TODO Auto-generated method stub

}

@Override

public Bag getChild(int i) {

// TODO Auto-generated method stub

return null;

}

@Override

public void operation() {

// TODO Auto-generated method stub

System.out.println("商品名称:("+name+"),数量:("+num+"),单价:("+price+")元,总价="+(this.calculation())+"元");

}

@Override

public int calculation() {

// TODO Auto-generated method stub

return num * price;

}

public BagDetail(String name, int price, int num) {

super();

this.name = name;

this.price = price;

this.num = num;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getPrice() {

return price;

}

public void setPrice(int price) {

this.price = price;

}

public int getNum() {

return num;

}

public void setNum(int num) {

this.num = num;

}

}

public class Main {

public static void main(String[] args) {

Bag bag = new BagBranch("大袋子");

Bag middleBag = new BagBranch("中袋子");

Bag redBag = new BagBranch("红袋子");

Bag te = new BagDetail("特产", 8, 2);

Bag map = new BagDetail("地图", 10, 1);

redBag.add(te);

redBag.add(map);

middleBag.add(redBag);

Bag shoses = new BagDetail("李宁鞋", 168, 1);

Bag whiteBag = new BagBranch("白袋子");

Bag xiang = new BagDetail("香记", 68, 2);

Bag tea = new BagDetail("红茶", 180, 3);

whiteBag.add(xiang);

whiteBag.add(tea);

bag.add(middleBag);

bag.add(shoses);

bag.add(whiteBag);

bag.operation();

int all = bag.calculation();

System.out.println("总价"+all);

}

}

运行结果:

商品名称:(特产),数量:(2),单价:(8)元,总价=16元

商品名称:(地图),数量:(1),单价:(10)元,总价=10元

商品名称:(李宁鞋),数量:(1),单价:(168)元,总价=168元

商品名称:(香记),数量:(2),单价:(68)元,总价=136元

商品名称:(红茶),数量:(3),单价:(180)元,总价=540元

总价870

  • 1组合模式的应用场景
  • 在需要标识一个对象整体与部分的层次结构的时候
  • 要求对用户隐藏的组合对象与单个对象不同,用户可以用统一的接口使用组合构件中的所有对象的场合。

3.3 行为型模式概述

行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间相互协作共同完成的单个对象都无法完成的任务,它涉及算法及对象间责任的分配。
    行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或者聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

  • 模板方法模式
  • 策略模式
  • 命令模式
  • 职责链模式
  • 状态模式
  • 观察者模式
  • 中介者模式
  • 迭代器模式
  • 访问者模式
  • 备忘录模式
  • 解释器模式

3.3.1 模板方法模式

模板方法的模式定义,定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中操作,使得子类在不改变该算法的结构情况下重定义该算法的某些特定的步骤。它是一种类行为模式。
    模板方法的主要优点:

  • 它封装了不变的部分,扩展可变的部分。把认为不变的部分算法在父类中实现,而把可变的部分在子类中继承实现,便于子类继续扩展。
  • 父类中提取了公共的部分代码,便于代码复用
  • 部分方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则

模板方法的缺点:

  • 对每个不同的实现都需要定义一个子类,会导致类的数量增加,系统更加庞大,设计也更加抽象
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,导致了一部分方向的控制结构,提高代码的阅读难度。

模板方式的结构和实现

模板方法模式需要注意抽象类与具体类之间的协作,用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的方向控制技术。

模板方式的结构

  • 抽象类:负责给出一个算法的骨架和轮廓,它由一个模板方法和若干基本方法组成。

    1. 模板方法:定义了算法的骨架,按照某种顺序调用包含的基本方法
    2. 基本方法:是整个算法中的一个步骤,包含以下几种类型
      • 抽象方法:在抽象类中声明,由具体子类实现
      • 具体方法:在抽象类中已经实现,在具体子类可以继承或者重写它
      • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法
  • 具体子类:实现抽象类中所有定义的抽象方法和钩子方法,他们是一个顶级逻辑的一个组成步骤。

出国留学:

国留学手续一般经过以下流程:

索取学校资料,

提出入学申请,

办理因私出国护照,出境卡和公证,

申请签证,

体检,

订机票,

准备行装,

抵达目标学校等,

其中有些业务对各个学校是一样的,但有些业务因学校不同而不同,所以比较适合用模板方法模式来实现。

在本实例中,我们先定义一个出国留学的抽象类 StudyAbroad,里面包含了一个模板方法 TemplateMethod(),该方法中包含了办理出国留学手续流程中的各个基本方法,其中有些方法的处理由于各国都一样,所以在抽象类中就可以实现,但有些方法的处理各国是不同的,必须在其具体子类(如美国留学类 StudyInAmerica)中实现。如果再增加一个国家,只要增加一个子类就可以了

//出国留学 - 抽象类

public abstract class StudyAboard {

//模范方法

public void templateMethod(){

lookingForSchool(); //1.获取学校资料-抽象方法

applyForEnrol();//2.提出入学申请-抽象方法

applayForPassport();//3.办理因私申请护照,出境卡和公证-基本方法

applyForVisa();//4.申请签证-基本方法

readyGoAbroad();//5.体检、订机票、准备行装-基本方法

arriving();//6.抵达-抽象方法

}

//1.获取学校资料-抽象方法

public abstract void lookingForSchool();

//2.提出入学申请-抽象方法

public abstract void applyForEnrol();

//3.办理因私申请护照,出境卡和公证-基本方法

public void applayForPassport(){

System.out.println("三.办理因私出国护照、出境卡和公证:");

System.out.println("  1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。");

System.out.println("  2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。");

}

//4.申请签证-基本方法

public void applyForVisa(){

System.out.println("四.申请签证:");

System.out.println("  1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;");

System.out.println("  2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。");

}

//5.体检、订机票、准备行装-基本方法

public void readyGoAbroad(){

System.out.println("五.体检、订机票、准备行装:");

System.out.println("  1)进行身体检查、免疫检查和接种传染病疫苗;");

System.out.println("  2)确定机票时间、航班和转机地点。");

}

//6.抵达-抽象方法

public abstract void arriving();

}

public class StudyAmerica extends StudyAboard{

@Override

public void lookingForSchool() {

// TODO Auto-generated method stub

System.out.println("一.索取学校以下资料:");

System.out.println("  1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;");

System.out.println("  2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;");

System.out.println("  3)了解该学校的住宿、交通、医疗保险情况如何;");

System.out.println("  4)该学校在中国是否有授权代理招生的留学中介公司?");

System.out.println("  5)掌握留学签证情况;");

System.out.println("  6)该国政府是否允许留学生合法打工?");

System.out.println("  8)毕业之后可否移民?");

System.out.println("  9)文凭是否受到我国认可?");

}

@Override

public void applyForEnrol() {

// TODO Auto-generated method stub

System.out.println("二.入学申请:");

System.out.println("  1)填写报名表;");

System.out.println("  2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;");

System.out.println("  3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。");

}

@Override

public void arriving() {

// TODO Auto-generated method stub

System.out.println("六.抵达目标学校:");

System.out.println("  1)安排住宿;");

System.out.println("  2)了解校园及周边环境。");

}

}

public class Main {

public static void main(String[] args) {

StudyAboard studyAboard = new StudyAmerica();

studyAboard.templateMethod();

}

}

运行结果:

一.索取学校以下资料:

1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;

2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;

3)了解该学校的住宿、交通、医疗保险情况如何;

4)该学校在中国是否有授权代理招生的留学中介公司?

5)掌握留学签证情况;

6)该国政府是否允许留学生合法打工?

8)毕业之后可否移民?

9)文凭是否受到我国认可?

二.入学申请:

1)填写报名表;

2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;

3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。

三.办理因私出国护照、出境卡和公证:

1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。

2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。

四.申请签证:

1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;

2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。

五.体检、订机票、准备行装:

1)进行身体检查、免疫检查和接种传染病疫苗;

2)确定机票时间、航班和转机地点。

六.抵达目标学校:

1)安排住宿;

2)了解校园及周边环境。

模板方法模式应用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽离出来,供子类实现。
  • 当多个子类存在公共的行为时,可以将其提出出来并集中到一个父类中以避免代码重复。首先,要识别代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  • 当需要控制子类的扩展时,模板方法只在特定的调用钩子操作,这样留只允许这些点进行扩展。

3.3.2 策略模式

策略模式的定义与特点

策略模式的定义

该模式定义一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的的变化不会影响使用他们的客户。策略模式属于对象行为模式,通过对算法的封装,把使用算法的责任和算法的实现分割开来,并威派格不同的对象对这些算法进行管理。

优点:

  • 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
  • 策略模式提供一系列的可用重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  • 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  • 策略模式提供了对开闭原则的完美支持,可以在修改源代码的情况下,灵活增加新算法。
  • 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

缺点:

  • 客户端必须了解所有的策略算法的却别,以便适时选择恰当的算法类
  • 策略模式造成了很多的策略类

策略模式的结构与实现

策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类,策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性。

策略模式的结构:

  • 抽象策略类:定义了一个公共接口,各种不同的算法以不同的实现这个接口,环境角色使得这个接口调用不同的算法,一般使用接口或者抽象类来实现。
  • 具体策略类:实现了抽象策略定义的接口,提供具体的算法实现。
  • 环境类(Context):持有一个策略类的引用,最终给客户端使用。

// 抽象类 - 抽象策略类

public interface Strategy {

//策略方法

public void strategyMethod();

}

//具体策略类- A

public class JuTiStrategyA implements A{

@Override

public void strategyMethod() {

// TODO Auto-generated method stub

System.out.println("具体策略A的策略方法被访问!");

}

}

public class JuTiStrategyB implements B{

@Override

public void strategyMethod() {

// TODO Auto-generated method stub

System.out.println("具体策略B的策略方法被访问!");

}

}

public class Main {

public static void main(String[] args) {

Strategy strategy = new JuTiStrategyA();

Context context = new Context();

context.setStrategy(strategy);

context.strategyMethod();

Strategy b = new JuTiStrategyB();

context.setStrategy(b);

context.strategyMethod();

}

}

运行结果:

具体策略A的策略方法被访问!

具体策略B的策略方法被访问!

策略模式的应用场景

  • 一个系统需要动态的在集中算法中选择一种时,可将每个算法封装到策略中
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自策略类中以代替这些条件语句。
  • 系统中各个算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏于算法相关的数据结构。
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

3.3.3 命令模式

命令模式的定义与特点

命令模式的定义

将一个请求封装成一个对象,使发出请求的责任和执行请求的责任分开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

命令模式的优点

  • 降低系统的耦合度,命令模式能将调用操作的对象与实现操作的对象解耦
  • 增加或删除命令非常方便,采用命令模式增加与删除命令不会影响其他类,满足“开闭原则”,对扩展比较灵活
  • 可以实现宏命令,命令模式可以与组合模式结合,将多个命令装配城一个组合命令,即宏命令。
  • 方便实现Undo和Redo操作,命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复

命令模式的缺点

  • 可能产生大量的具体命令类,因为对每一个具体操作都需要设计一个具体命令类,将增加系统的复杂度

命令模式的结构与实现

命令模式的结构

  • 抽象命令类角色:声明执行命令的接口,拥有执行命令的抽象方法execute();
  • 具体命令角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  • 调用者/请求者角色:是请求的发送者,它通常用用很多的命令对象,并通过访问命令对象来执行相关请求,踏不直接访问接收者。

// 抽象命令角色:声明执行命令的接口

public interface Command {

//拥有执行命令的抽象方法execute();

public void execute();

}

// 接收者:执行命令的功能相关操作,是具体命令对象业务的真正实现

public class ReceiverA {

//实现命令

public void action(){

System.out.println("接收命令A");

}

}

// 接收者:执行命令的功能相关操作,是具体命令对象业务的真正实现

public class ReceiverB {

//实现命令

public void action(){

System.out.println("接收命令B");

}

}

// 具体命令类角色:实现抽象命令类,

public class JuTiCommandA implements Command{

private ReceiverA receiverA;

@Override

public void execute() {

// TODO Auto-generated method stub

receiverA.action();

}

public ReceiverA getReceiverA() {

return receiverA;

}

public void setReceiverA(ReceiverA receiverA) {

this.receiverA = receiverA;

}

public JuTiCommandA() {

this.receiverA = new ReceiverA();

}

}

// 具体命令类角色:实现抽象命令类,

public class JuTiCommandB implements Command{

private ReceiverB receiverB;

@Override

public void execute() {

// TODO Auto-generated method stub

receiverB.action();

}

public ReceiverB getReceiverB() {

return receiverB;

}

public void setReceiverB(ReceiverB receiverB) {

this.receiverB = receiverB;

}

public JuTiCommandB() {

this.receiverB = new ReceiverB();

}

}

// 调用者角色:是请求的发送者,通常用友很多的命令对象,并通过访问命令对象来执行相关请求,不直接访问接收者

public class Invoker {

private Command command;

public void cell(){

command.execute();

}

public Command getCommand() {

return command;

}

public void setCommand(Command command) {

this.command = command;

}

public Invoker(Command command) {

super();

this.command = command;

}

}

public class Main {

public static void main(String[] args) {

Invoker invoker = null;

Command command = new JuTiCommandA();

invoker = new Invoker(command);

invoker.cell();

Command command2 = new JuTiCommandB();

invoker = new Invoker(command2);

invoker.cell();

}

}

运行结果:

接收命令A

接收命令B

命令模式的应用实例

【例1】用命令模式实现客户去餐馆吃早餐的实例。

分析:客户去餐馆可选择的早餐有肠粉、河粉和馄饨等,客户可向服务员选择以上早餐中的若干种,服务员将客户的请求交给相关的厨师去做。这里的点早餐相当于“命令”,服务员相当于“调用者”,厨师相当于“接收者”,所以用命令模式实现比较合适。

首先,定义一个早餐类(Breakfast),它是抽象命令类,有抽象方法 cooking(),说明要做什么;再定义其子类肠粉类(ChangFen)、馄饨类(HunTun)和河粉类(HeFen),它们是具体命令类,实现早餐类的 cooking() 方法,但它们不会具体做,而是交给具体的厨师去做;具体厨师类有肠粉厨师(ChangFenChef)、馄蚀厨师(HunTunChef)和河粉厨师(HeFenChef),他们是命令的接收者,

// 抽象命令类

public interface BreakFast {

public abstract void cooking();

}

//接收者 - 服务员

public class ChangFenChef{

public void diancan(){

System.out.println("点一份河粉");

}

}

//接收者

public class HeFenChef{

public void diancan(){

System.out.println("点一份河粉");

}

}

//接收者

public class HunTunChef{

public void diancan(){

System.out.println("点一份馄饨");

}

}

//具体实现者 - 厨师

public class HeFen implements BreakFast{

private HeFenChef HeFenChef;

@Override

public void cooking() {

// TODO Auto-generated method stub

HeFenChef.diancan();

}

public HeFenChef getHeFenChef() {

return HeFenChef;

}

public void setHeFenChef(HeFenChef heFenChef) {

HeFenChef = heFenChef;

}

public HeFen() {

this.HeFenChef = new HeFenChef();

}

}

//具体实现者

public class HunTun implements BreakFast{

private HunTunChef HunTunChef;

@Override

public void cooking() {

// TODO Auto-generated method stub

HunTunChef.diancan();

}

public HunTunChef getHunTunChef() {

return HunTunChef;

}

public void setHunTunChef(HunTunChef hunTunChef) {

HunTunChef = hunTunChef;

}

public HunTun() {

this.HunTunChef = new HunTunChef();

}

}

//具体实现者

public class ChangFen implements BreakFast{

private ChangFenChef ChangFenChef;

@Override

public void cooking() {

// TODO Auto-generated method stub

ChangFenChef.diancan();

}

public ChangFenChef getChangFenChef() {

return ChangFenChef;

}

public void setChangFenChef(ChangFenChef changFenChef) {

ChangFenChef = changFenChef;

}

public ChangFen() {

this.ChangFenChef = new ChangFenChef();

}

}

// 调用者 - 顾客

public class Invoker {

private BreakFast breakFast;

public void diancan(){

breakFast.cooking();

}

public BreakFast getBreakFast() {

return breakFast;

}

public void setBreakFast(BreakFast breakFast) {

this.breakFast = breakFast;

}

public Invoker(BreakFast breakFast) {

super();

this.breakFast = breakFast;

}

}

public class Main {

public static void main(String[] args) {

Invoker invoker = null;

BreakFast breakFast = null;

breakFast = new HeFen();

invoker = new Invoker(breakFast);

invoker.diancan();

breakFast = new ChangFen();

invoker = new Invoker(breakFast);

invoker.diancan();

breakFast = new HunTun();

invoker = new Invoker(breakFast);

invoker.diancan();

}

}

运行结果:

点一份河粉

点一份河粉

点一份馄饨

命令模式的应用场景:

  • 当系统需要将请求调用者与请求接收者解耦时,命令模式使得调用者和接收者不直接交互
  • 当系统需要随机请求命令或者经常增加删除命令时,命令模式比较方便实现这些功能
  • 当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能
  • 当系统需要支持命令的撤销操作和恢复操作时候,可以将命令对象存储起来,采用备忘录模式来实现

3.3.4 责任链模式

责任链模式的定义与特点

责任链的定义

定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连城一条链;当有请求发生时,可将请求沿着这条链传递,知道有对象处理它为止。
    在责任链模式中,客户只需要将请求发送到责任链上即可,无需关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。

责任链的特点

优点:

  • 降低了对象之间的耦合度,该模式使得一个对象无需知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无需拥有对方的明确信息。
  • 增加了系统的可扩展性,可以根据需要增加新的请求处理类,满足开闭原则
  • 增强了给对象指派责任的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动他们的次序,也可动态地新增或者删除责任。
  • 责任链简化了对象之间的连接,每个对象只需要保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的if或者if…else语句。
  • 责任分担,每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点:

  • 不能保证每个请求一定被处理,由于一个请求没有明确的接收者,所以不能保证他一定会被处理,该请求可能一直传到连的末端都得不到处理
  • 对比较长的责任链,请求的处理可能涉及多个处理对象,系统性能会受到一定的影响
  • 职责链简历的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于责任链的错误设置而导致系统的出错,如可能会造成循环调用。

模式的结构与实现

责任链模式的结构:

  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后续链接
  • 具体处理角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给他的后继者
  • 客户类角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程

// 抽象处理角色 -

public abstract class Handler {

private Handler next;

public abstract void handleRequest(String request);

public Handler getNext() {

return next;

}

public void setNext(Handler next) {

this.next = next;

}

}

//具体处理角色

public class JuTiHandlerA extends Handler{

@Override

public void handleRequest(String request) {

// TODO Auto-generated method stub

if(request.equals("one")){

System.out.println(request +"具体处理者 A");

}else{

if(getNext() != null){

getNext().handleRequest(request);

}else{

System.out.println("没有处理该请求");

}

}

}

}

//具体处理角色

public class JuTiHandlerB extends Handler{

@Override

public void handleRequest(String request) {

// TODO Auto-generated method stub

if(request.equals("two")){

System.out.println(request +"具体处理者 B");

}else{

if(getNext() != null){

getNext().handleRequest(request);

}else{

System.out.println("没有处理该请求");

}

}

}

}

public class Main {

public static void main(String[] args) {

Handler handler = new JuTiHandlerA();

Handler handler2 = new JuTiHandlerB();

handler.setNext(handler2);

handler.handleRequest("one");

}

}

运行结果:

one具体处理者 A

责任链模式的应用场景

  • 有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动决定
  • 可以动态指定一组对象处理请求,或添加新的处理者
  • 在不明确指定请求处理者的情况下,向多个处理这种的一个提交请求

责任链模式的扩展

  • 纯的责任链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理;二:把责任推给下家处理
  • 不纯的责任链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象接收。

3.3.5 状态模式

状态模式的定义和特点

状态模式的定义

对有状态的对象,把复杂的“判断逻辑(if-else)”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

状态对象的优点

优点:

  • 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”
  • 减少对象之间的相互依赖,将不同的状态引入独立的对象中会使得状态转变的更加明确,且减少对象之间的相互依赖
  • 有利于程序的扩展,通过定义新的子类很容易增加新的状态和转变

状态模式的缺点

缺点:

  • 状态模式必然会增加系统的类和对象的个数
  • 状态模式的结构和实现都比较复杂,如果使用不当会造成程序结构的混乱。

状态模式的结构与实现

状态模式的结构

状态模式将把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。

结构:

  • 环境(Context)角色:也称为上下文,它定义了客户端感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理
  • 抽象(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为
  • 具体状态:实现抽象状态对应的行为

//抽象角色

public abstract class Stat {

public abstract void Handle(Context context);

}

// 环境角色

public class Context {

private Stat stat;

public void Handle(){

stat.Handle(this);

}

public Context() {

stat = new JuTiStat1();

}

public Stat getStat() {

return stat;

}

public void setStat(Stat stat) {

this.stat = stat;

}

}

//具体状态角色

public class JuTiStat1 extends Stat{

@Override

public void Handle(Context context) {

// TODO Auto-generated method stub

System.out.println("状态 ..... A");

context.setStat(new JuTiStat2());

}

}

//具体状态角色

public class JuTiStat2 extends Stat{

@Override

public void Handle(Context context) {

// TODO Auto-generated method stub

System.out.println("状态 ..... B");

context.setStat(new JuTiStat1());

}

}

public class Main {

public static void main(String[] args) {

Context context = new Context();

context.Handle();

context.Handle();

context.Handle();

context.Handle();

}

}

运行结果:

状态 ..... A

状态 ..... B

状态 ..... A

状态 ..... B

海贼王中路飞在打多弗朗明哥的时候,首先是普通状态,然后发怒开启二挡状态,被多弗朗明哥嘲笑速度快,但是力量低,于是开启三挡状态,又被嘲笑力量够了,但是速度差远了,路飞被逼急,于是开启四挡,最终打败了多弗朗明哥。现在我们通过代码实现这样的一个过程。

  • 1

//抽象角色 - 路飞的状态

public interface LuFeiState {

public void change();

}

//具体角色

class Ordinary implements LuFeiState{

@Override

public void change() {

System.out.println("路飞当前为普通状态战斗");

}

}

class SecondGear implements LuFeiState{

@Override

public void change() {

System.out.println("路飞开启三挡战斗");

}

}

class ThirdGear implements LuFeiState{

@Override

public void change() {

System.out.println("路飞开启三挡战斗");

}

}

class FourthGear implements LuFeiState{

@Override

public void change() {

System.out.println("路飞开启四挡战斗");

}

}

public class LuFei {

public final static int ORDINARY = 0;//普通状态

public final static int SECONDGEAR = 1;//二挡状态

public final static int THIRDGEAR = 2;//三挡状态

public final static int FOURTHGEAR = 3;//四挡状态

private LuFeiState state = new Ordinary();//由于路飞一开始是普通状态,所以我们初始化state为ORDINARY

public LuFeiState getState() {

return state;

}

public void setState(LuFeiState state) {

this.state = state;

}

public void change(){

state.change();

}

}

public class Main {

public static void main(String[] args) {

LuFei luFei = new LuFei();

luFei.setState(new SecondGear());

luFei.change();

luFei.setState(new ThirdGear());

luFei.change();

luFei.setState(new FourthGear());

luFei.change();

luFei.setState(new Ordinary());

luFei.change();

}

}

运行结果:

路飞开启三挡战斗

路飞开启三挡战斗

路飞开启四挡战斗

路飞当前为普通状态战斗

状态模式的应用场景

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态的改变它的行为,可以使用状态模式
  • 一个操作中含有庞大的分值结构,并且分值的结构决定对象的状态时。

3.3.6 观察者模式

观察者模式的定义和特点

观察者模式的定义

定义:多个对象存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新,这种模式有时又称作发布-订阅模式,模型-视图模式,它是对象行为模式。

观察者模式特点

优点:

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 目标与观察者之间建立了一套出发机制。

缺点:

  • 目标与观察者之间的依赖没有完全解除,而有可能出现循环引用
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

观察者模式的结构和实现

观察者模式的结构

实现观察者模式的时候需要注意目标对象和具体对象之间不能直接调用,否则使两者之间紧密耦合起来,这违反了面向对象的设计原则。
    结构:

  • 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  • 具体主题角色:也叫目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变的时候,通知所有注册过的观察者对象。
  • 抽象观察者(ObServer)角色:它是一个抽象类或者接口,它包含了一个更新自己的抽象方法,当接到具体的主题的更改通知时被调用。
  • 具体观察者角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

【例1】利用观察者模式设计一个程序,分析“人民币汇率”的升值或贬值对进口公司的进口产品成本或出口公司的出口产品收入以及公司的利润率的影响。

分析:当“人民币汇率”升值时,进口公司的进口产品成本降低且利润率提升,出口公司的出口产品收入降低且利润率降低;当“人民币汇率”贬值时,进口公司的进口产品成本提升且利润率降低,

出口公司的出口产品收入提升且利润率提升。

这里的汇率(Rate)类是抽象目标类,它包含了保存观察者(Company)的 List 和增加/删除观察者的方法,以及有关汇率改变的抽象方法 change(int number);而人民币汇率(RMBrate)类是具体目标,

它实现了父类的 change(int number) 方法,即当人民币汇率发生改变时通过相关公司;公司(Company)类是抽象观察者,它定义了一个有关汇率反应的抽象方法 response(int number);

进口公司(ImportCompany)类和出口公司(ExportCompany)类是具体观察者类,它们实现了父类的 response(int number) 方法,即当它们接收到汇率发生改变的通知时作为相应的反应

// 抽象目标类角色

public abstract class Rate {

List<Company> companys = new ArrayList<Company>();

public void add(Company company){

companys.add(company);

}

public void remove(Company company){

companys.remove(company);

}

//抽象通知接口

public abstract void change(int num);

}

//抽象观察者接口

public abstract class Company {

//抽象接口,更新自己状态

public abstract void response(int num);

}

//具体抽象目标接口

public class RMBRate extends Rate {

//,实现抽象接口,通知所有注册过的观察者

@Override

public void change(int num) {

// TODO Auto-generated method stub

for(Company company : companys){

company.response(num);

}

}

}

//具体观察者

public class ImportCompany extends Company {

@Override

public void response(int num) {

// TODO Auto-generated method stub

if(num > 0){

System.out.println("人民币汇率升值"+num+"个基点,降低了进口产品成本,提升了进口公司利润率。");

}else{

System.out.println("人民币汇率贬值"+(-num)+"个基点,提升了进口产品成本,降低了进口公司利润率。");

}

}

}

//具体观察者

public class ExportCompany extends Company {

@Override

public void response(int num) {

// TODO Auto-generated method stub

if(num>0){

System.out.println("人民币汇率升值"+num+"个基点,降低了出口产品收入,降低了出口公司的销售利润率。");

}else if(num<0){

System.out.println("人民币汇率贬值"+(-num)+"个基点,提升了出口产品收入,提升了出口公司的销售利润率。");

}

}

}

public class Main {

public static void main(String[] args) {

Rate rate = new RMBRate();

rate.add(new ImportCompany());

rate.add(new ExportCompany());

rate.change(1);

rate.change(-10);

}

}

运行结果:

人民币汇率升值1个基点,降低了进口产品成本,提升了进口公司利润率。

人民币汇率升值1个基点,降低了出口产品收入,降低了出口公司的销售利润率。

人民币汇率贬值10个基点,提升了进口产品成本,降低了进口公司利润率。

人民币汇率贬值10个基点,提升了出口产品收入,提升了出口公司的销售利润率。

观察者模式的应用场景

  • 对象存在一对多的关系,一个对象发生状态会影响其他对象
  • 当一个抽象模型有两个方面,其中一个方面依赖另一个方面,可将二者封装在独立的对象中以便各自独立的改变和复用

3.3.7 中介者模式

中介者模式的定义和特点

中介者模式的定义

定义:定义一个中介者对象来封装一系列的对象之间的交互,使原有对象之间耦合松散,可以独立的改变他们之间得交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

中介者模式的特点

优点:

  • 降低了对象之间的耦合性,使得对象易于独立的被复用
  • 将对象之间的一对多关联转变成一对一的关联,提高系统的灵活性,使得系统易于维护和扩展

缺点:

  • 当同时类太多时候,中介者职责将会很大,会变得复杂而且庞大,以至于系统难以维护

中介者模式的结构和实现

中介者模式的结构

中介者模式实现的关键是找出“中介者”。
    结构:

  • 抽象中介者角色:它是中介者的接口,提供了同事对象注册于转发同事对象信息的抽象方法。
  • 具体中介者角色:实现中介者接口,定义了一个List来管理同事对象,协调各个角色之间的交互关系,因此依赖于同事角色
  • 抽象同事类角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的通识类的公共功能
  • 具体同事类角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

// 抽象中介者角色

public abstract class Medium {

public abstract void regist(Customer customer);

public abstract void relay(String from,String msg);

}

public abstract class Customer {

protected Medium medium;

private String name;

public abstract void send(String msg);

public abstract void recive(String from ,String msg);

public Medium getMedium() {

return medium;

}

public void setMedium(Medium medium) {

this.medium = medium;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

public class EstateMedium extends Medium {

private List<Customer> customers = new ArrayList<Customer>();

@Override

public void regist(Customer customer) {

// TODO Auto-generated method stub

if(!customers.contains(customer)){

customers.add(customer);

customer.setMedium(this);

}

}

@Override

public void relay(String from, String msg) {

// TODO Auto-generated method stub

for(Customer customer : customers){

String name = customer.getName();

if(!name.equals(from)){

customer.recive(from,msg);

}

}

}

}

public class Seller extends Customer{

@Override

public void send(String msg) {

// TODO Auto-generated method stub

System.out.println("我(卖方)说:"+msg);

}

@Override

public void recive(String from ,String msg) {

// TODO Auto-generated method stub

System.out.println(from +"说:"+msg);

}

}

public class Buyer extends Customer{

@Override

public void send(String msg) {

// TODO Auto-generated method stub

System.out.println("我(买方)说:"+msg);

}

@Override

public void recive(String from ,String msg) {

// TODO Auto-generated method stub

System.out.println(from +"说:"+msg);

}

}

public class Main {

public static void main(String[] args) {

Medium medium = new EstateMedium();

Customer seller = new Seller();

Customer buyer = new Buyer();

medium.regist(seller);

medium.regist(buyer);

seller.send("你好");

buyer.send("你好");

seller.recive("seller", "哈哈");

buyer.recive("buyer", "么么哒");

}

}

运行结果:

我(卖方)说:你好

我(买方)说:你好

seller说:哈哈

buyer说:么么哒

模式的应用场景

  • 当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时
  • 当创建一个运行于多个类之间的对象,又不想生成新的子类时

3.3.8 迭代器模式

迭代器模式的定义和特点

迭代器模式的定义

定义:提供一个对象来顺序访问集合对象中的一系列数据,而不暴露集合内部对象的表示,迭代器模式是一种对象行为模式

迭代器模式的特点

优点:

  • 访问一个聚合对象的内容而不暴露内部的表示
  • 遍历任务交由迭代器完成,简化了聚合类
  • 它支持以不同的方式遍历一个聚合,甚至可以自定义一个迭代器的子类以支持新的遍历
  • 增加新的聚合类和迭代器都很方便,无需修改原有代码
  • 封装性良好,为遍历不同的聚合类提供一个统一的接口

缺点:

  • 增加了类的个数,一定程度上增加了系统的复杂性

迭代器模式的结构和实现

迭代器模式的结构

迭代器模式是通过将聚合类的对象遍历行为分离出来,抽象成迭代器类来实现,其目的是在不暴露聚合对象的内部结构下,让外部代码透明的访问聚合的内部数据。

结构:

  • 抽象聚合角色:定义存储、添加、删除聚合对象以创建迭代器对象的接口
  • 具体聚合角色:实现抽象聚合类,返回一个具体迭代器的实例
  • 抽象迭代器角色:定义访问和遍历聚合元素的接口,通常包含hasNext()、first()、next()等方法
  • 具体迭代器角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置

// 抽象聚合角色

public abstract class Aggregate {

public abstract void add(Object obj);

public abstract void remove(Object obj);

public abstract Iterator getIterator();

}

// 具体聚合角色

public class ConcreteAggregate extends Aggregate {

List<Object> list = new ArrayList<Object>();

@Override

public void add(Object obj) {

// TODO Auto-generated method stub

list.add(obj);

}

@Override

public void remove(Object obj) {

// TODO Auto-generated method stub

list.remove(obj);

}

@Override

public Iterator getIterator() {

// TODO Auto-generated method stub

return new ConcreteIterator(list);

}

}

// 抽象迭代器角色

public abstract class Iterator {

public abstract Object first();

public abstract Object next();

public abstract boolean hasNext();

}

// 具体迭代器角色

public class ConcreteIterator extends Iterator {

private List<Object> list = null;

private int index = -1;

@Override

public Object first() {

// TODO Auto-generated method stub

index = 0;

Object obj = list.get(index);

return obj;

}

@Override

public Object next() {

// TODO Auto-generated method stub

Object obj = null;

if(this.hasNext()){

obj = list.get(++index);

}

return obj;

}

@Override

public boolean hasNext() {

// TODO Auto-generated method stub

if(index < list.size() - 1){

return true;

}

return false;

}

public ConcreteIterator(List<Object> list) {

super();

this.list = list;

}

public List<Object> getList() {

return list;

}

public void setList(List<Object> list) {

this.list = list;

}

}

public class Main {

public static void main(String[] args) {

Aggregate aggregate = new ConcreteAggregate();

aggregate.add("啦啦啦");

aggregate.add("么么哒");

aggregate.add("哈哈哈");

Iterator iterator = aggregate.getIterator();

while(iterator.hasNext()){

System.out.println(iterator.next());

}

System.out.println("first:"+iterator.first());

}

}

运行结果:

啦啦啦

么么哒

哈哈哈

first:啦啦啦

迭代器的应用场景

  • 当需要为聚合对象提供多种遍历方式时
  • 当需要为遍历不同的聚合结构提供一个统一的接口时
  • 当访问一个聚合对象的内容而无需暴露其内部的细节表示时

3.3.9 访问者模式

访问者模式的定义和特点

访问者模式的定义

定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可添加这些元素的新操作,为数据结构中每个元素提供多种访问方式。它将对数据的操作和数据的结构进行分离,是行为类模式中最复杂的一种。

访问者的特点

优点:

  • 扩展性好:能够在不修改元素结构的情况下,对对象结构的元素添加新的功能
  • 复用性好:可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度
  • 灵活性好:访问者模式将数据结构与作用结构上的操作解耦,使得操作集合可相对自由的演化而不影响系统的数据结构
  • 符合一致性原则:访问者模式把相关的行为封装在一起,构成一个访问者,使得每个访问者的功能都比较单一

缺点:

  • 增加新的元素类很困难,在访问者模式中,每增加一个新的元素类,都要在每一个具体的访问类中增加相应的具体操作,这违背了“开闭原则”
  • 破快封装,访问者模式中具体元素对访问者公布细节,着破坏了对象的封装性
  • 违反了依赖倒置原则,访问者模式中依赖了具体类,而没有依赖抽象类

访问者模式的结构和实现

访问者模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类

访问者模式的结构

  • 抽象访问者角色:定义一个访问具体元素的接口,为每个具体的元素类对应一个访问操作visit(),该类中的参数类型标识了被访问的具体元素。
  • 具体访问着角色:实现抽象访问者角色中的定义的接口,确定访问者访问一个元素时该做什么
  • 抽象元素角色:声明一个包含接受accept()的接口,接收访问者对象作为accept()方法的参数
  • 具体元素角色:实现抽象元素角色中声明的accept()操作,其方法体通常都是visitor.visit(this),另外具体元素中还有可能包含本身业务逻辑的相关操作。
  • 对象结构角色:是一个包含元素角色的容器,提供让访问者遍历元素容器中的所有元素的方法,通常通过List、Set、Map等聚合类实现。

// 抽象访问者角色

public abstract class Visitor {

public abstract void visit(ElementA a );

public abstract void visit(ElementB b );

}

// 抽象元素角色

public abstract class Element {

public abstract void accept(Visitor visitor);

}

public class ElementA extends Element{

@Override

public void accept(Visitor visitor) {

// TODO Auto-generated method stub

visitor.visit(this);

}

public String operationA(){

return "具体元素A";

}

}

public class ElementB extends Element{

@Override

public void accept(Visitor visitor) {

// TODO Auto-generated method stub

visitor.visit(this);

}

public String operationB(){

return "具体元素B";

}

}

public class VisitorA extends Visitor{

@Override

public void visit(ElementA a) {

// TODO Auto-generated method stub

System.out.println("具体访问者A ->"+a.operationA());

}

@Override

public void visit(ElementB b) {

// TODO Auto-generated method stub

System.out.println("具体访问者A ->"+b.operationB());

}

}

public class VisitorB extends Visitor{

@Override

public void visit(ElementA a) {

// TODO Auto-generated method stub

System.out.println("具体访问者B ->"+a.operationA());

}

@Override

public void visit(ElementB b) {

// TODO Auto-generated method stub

System.out.println("具体访问者B ->"+b.operationB());

}

}

public class ObjectStructure {

private List<Element> list = new ArrayList<Element>();

public void accept(Visitor visitor){

Iterator iteartor = list.iterator();

while(iteartor.hasNext()){

Element element = (Element) iteartor.next();

element.accept(visitor);

}

}

public void add(Element element){

list.add(element);

}

public void remove(Element element){

list.remove(element);

}

}

public class Main {

public static void main(String[] args) {

ObjectStructure os = new ObjectStructure();

os.add(new ElementA());

os.add(new ElementB());

Visitor visitor = new VisitorA();

os.accept(visitor);

}

}

运行结果:

具体访问者A ->具体元素A

具体访问者A ->具体元素B

  • 1
  • 2
  • 3

分析:艺术公司利用“铜”可以设计出铜像,利用“纸”可以画出图画;造币公司利用“铜”可以印出铜币,利用“纸”可以印出纸币。

对“铜”和“纸”这两种元素,两个公司的处理方法不同,所以该实例用访问者模式来实现比较适合。

抽象访问者:Company

具体访问者:ArtCompany,RMBCompany

抽象元素角色:Material 材料类

具体元素角色:Paper、Cuprum

对象结构角色:SetMaterial

访问者模式应用场景

  • 对象结构相对稳定,但其操作算法经常变化的程序
  • 对象结构中的对象需要提供不同且不想关的操作,而且要避让这些操作的变化影响对象的结构
  • 对象结构包含很多类型的对象,希望对象实施一些依赖于具体类型的操作

3.3.10 备忘录模式

备忘录模式的定义和特点

备忘录模式的定义

在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时候能将该状态恢复到保存状态,又叫快照模式

备忘录模式的特点

优点:

  • 提供了一种可以恢复状态的机制,当前用户需要时能够比较方便地将数据恢复到某个历史状态
  • 实现了内部状态的封装,除了创建它的发起人之外,其他对象都不能访问这些状态信息
  • 简化了发起人类,发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

缺点:

  • 资源消耗大,如果要保存的内部状态过多或者特别频繁,将会占用比较大的内存资源

模式的结构和实现

模式的结构

  • 发起人角色:记录当前时刻的内部状态信息,提供备忘录和恢复备忘录的功能,实现其他业务的功能,它可以访问备忘录里面所有的信息
  • 备忘录角色:负责储存发起人角色的内部状态,在需要的时候提供这些内部状态给发起人
  • 管理者角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的功能进行修改与访问

//备忘录角色

public class Memento {

private String state;

public String getState() {

return state;

}

public void setState(String state) {

this.state = state;

}

public Memento(String state) {

super();

this.state = state;

}

}// 角色管理类

public class Manager {

private Memento memento;

public Memento getMemento() {

return memento;

}

public void setMemento(Memento memento) {

this.memento = memento;

}

public Manager(Memento memento) {

super();

this.memento = memento;

}

public Manager() {

super();

}

}

//发起人

public class SendPeople {

private String state;

public Memento createMemento(){

Memento m = new Memento(state);

return m;

}

public void restoreMemento(Memento memento){

this.setState(memento.getState());

}

public String getState() {

return state;

}

public void setState(String state) {

this.state = state;

}

}

public class Main {

public static void main(String[] args) {

SendPeople sendp = new SendPeople();

Manager manager = new Manager();

sendp.setState("890");

System.out.println("初始状态 ="+sendp.getState());

manager.setMemento(sendp.createMemento());

sendp.setState("1111");

System.out.println("新的状态="+sendp.getState());

sendp.restoreMemento(manager.getMemento()); // 恢复状态

System.out.println("恢复状态="+sendp.getState());

}

}

运行结果:

初始状态 =890

新的状态=1111

恢复状态=890

备忘录模式的应用场景

  • 需要保存与恢复数据的场景,比如玩游戏中间存档
  • 需要提供一个可以回滚的操作场景,例如数据库事务操作

3.3.11 解释器模式

解释器模式的定义和特点

解释器模式的定义

给分析对象应以一个语言,并定义改语言的文法表示,再设计一个解释器来解析语言中的句子,也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
    这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。

解释器模式的特点

解释器模式是一种行为型模式。
    优点:

  • 扩展性好,由于解释器语言中使用类来标识语言的文法规则,因此可以通过继承机制来扩展或改变文法
  • 容易实现,在语法树中的每个表达式节点类都是相似的,所以实现其文法较容易

缺点:

  • 执行效率低,解释器模式中通常使用大量的循环和递归调用,当要解释的句子较为复杂的时候,其运行速度很慢,且代码的调试过程比较麻烦
  • 会引起类膨胀,解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护
  • 可应用的场景比较少,在软件开发中,需要定义语言的文法实例非常少,所以这种模式很少被使用

解释器的结构

  • 文法:用于描述语言的语法结构的形成规则
  • 句子:句子是语言的基本单位,是语言集中的一个元素,能有“语法”推导而出
  • 语法树:是句子结构的一种表示,表示了句子的推导结果,有利于理解句子的语法结构的层次

<expression> ::= <city>的<person>

<city> ::= 韶关|广州

<person> ::= 老人|妇女|儿童

注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。

// 抽象表达式

public abstract class Expression {

public abstract boolean intercepter(String info);

}

public class TerminalExpression extends Expression{

private Set<String> list = new HashSet<String>();

@Override

public boolean intercepter(String info) {

// TODO Auto-generated method stub

if(list.contains(info)){

return true;

}

return false;

}

public TerminalExpression(String[] data) {

for(int i = 0 ; i < data.length ; i++){

list.add(data[i]);

}

}

}

public class AndExpression extends Expression {

private Expression city;

private Expression persion;

@Override

public boolean intercepter(String info) {

// TODO Auto-generated method stub

String s[] = info.split("的");

return city.intercepter(s[0]) && persion.intercepter(s[1]);

}

public AndExpression(Expression city, Expression persion) {

super();

this.city = city;

this.persion = persion;

}

public Expression getCity() {

return city;

}

public void setCity(Expression city) {

this.city = city;

}

public Expression getPersion() {

return persion;

}

public void setPersion(Expression persion) {

this.persion = persion;

}

}

public class Context {

private String[] citys={"韶关","广州"};

private String[] persons={"老人","妇女","儿童"};

private Expression expression;

public Context() {

super();

Expression city = new TerminalExpression(citys);

Expression person = new TerminalExpression(persons);

expression = new AndExpression(city, person);

}

public void freeRide(String info){

boolean ok = expression.intercepter(info);

if(ok) System.out.println(info + ",本次免费乘车");

else System.out.println(info +",不是会员,赶下去");

}

public String[] getCitys() {

return citys;

}

public void setCitys(String[] citys) {

this.citys = citys;

}

public String[] getpPersons() {

return persons;

}

public void setPersons(String[] persion) {

this.persons = persion;

}

public Expression getExpression() {

return expression;

}

public void setExpression(Expression expression) {

this.expression = expression;

}

}

public class Main {

public static void main(String[] args) {

Context bus=new Context();

bus.freeRide("韶关的老人");

bus.freeRide("韶关的年轻人");

bus.freeRide("广州的妇女");

bus.freeRide("广州的儿童");

bus.freeRide("山东的儿童");

}

}

运行结果:

韶关的老人,本次免费乘车

韶关的年轻人,不是会员,赶下去

广州的妇女,本次免费乘车

广州的儿童,本次免费乘车

山东的儿童,不是会员,赶下去

23种设计模式(二)相关推荐

  1. 23种设计模式 UML 类图及对应示例代码 (二)

    23种设计模式 UML 类图及对应示例代码 (二) 11.DoFactory.GangOfFour.Flyweight.Structural Flyweight:运用共享技术有效的支持大量细粒度的对象 ...

  2. 23种设计模式-个人笔记(二)

    目录 五.23 种设计模式 1.单例模式 1.1.单例模式的定义与特点 1.2.单例模式的优点和缺点 1.3.单例模式的应用场景 1.4.单例模式的结构与实现 1.5.八种方式详解 1.6.单例模式在 ...

  3. 设计模式(二)23种设计模式

    设计模式(二)23种设计模式 文章目录 设计模式(二)23种设计模式 组件协作模式 策略模式 观察者模式 单一职责模式 Decorator模式 Bridge模式 对象创建模式 Factory Meth ...

  4. 23种设计模式(二十三)访问者模式(阁瑞钛伦特软件-九耶实训)

    常说的设计模式是23种设计模式,分为3大类: 创建型模式5种:工厂方法.抽象工厂.单例.建造者.原型 结构型模式7种:适配器.代理.桥接.装饰者.外观.享元.组合 行为型模式11种:模板方法.解释器. ...

  5. 23种设计模式(二十二)状态模式(阁瑞钛伦特软件-九耶实训)

    常说的设计模式是23种设计模式,分为3大类: 创建型模式5种:工厂方法.抽象工厂.单例.建造者.原型 结构型模式7种:适配器.代理.桥接.装饰者.外观.享元.组合 行为型模式11种:模板方法.解释器. ...

  6. Java开发中的23种设计模式详解(转)

    设计模式(Design Patterns) --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  7. 【设计模式】Java 23种设计模式对比总结

    一.设计模式的分类 创建型模式,共五种(1-5):工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种(6-12):适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组 ...

  8. 【java】java开发中的23种设计模式详解

    设计模式(Design Patterns) --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  9. java 23种设计模式及具体例子 收藏有时间慢慢看

    设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代 码可靠性. 毫无疑问,设计模式 ...

  10. JAVA设计模式总结之23种设计模式

    一.什么是设计模式                                                                                           ...

最新文章

  1. 常惠琢 201771010102《面向对象程序设计(java)》第七周学习总结
  2. Ubuntu下安装Stanford CoreNLP
  3. 二叉树非递归遍历的经典求解
  4. 机器学习优化方法总结比较(SGD,Adagrad,Adadelta,Adam,Adamax,Nadam)
  5. 局域网无法访问本地apache
  6. 轮询锁使用时遇到的问题与解决方案!
  7. Numbers on the Chessboard(CF-1027B)
  8. 兼容多浏览器的CSS背景透明
  9. Android开发笔记(七十八)异常容错处理
  10. linux系统root用户忘记密码的重置方法
  11. JVM Troubleshooting
  12. html文件是fla,FLASH 全屏、按ESC 退出全屏模式Fla及HTML源文件
  13. 周杰伦 jay《青花瓷》mp3 下载/试听/MV/在线播放
  14. 陆奇加入拼多多,担任技术委员会主席!
  15. 哈佛架构 VS 冯·诺依曼架构
  16. java中j=j++和j=++j的理解
  17. 日活四千万的汤姆猫游戏家族,用AWS云服务打造“无感”用户体验
  18. 我们是如何将一个项目做烂的
  19. Ubuntu18.04屏幕分辨率问题
  20. 综述:目标检测2001-2021

热门文章

  1. Blocks Programming
  2. 计算机常用文件夹怎么关,win7系统隐藏最近使用的文件和常用文件夹的处理步骤...
  3. f2pool鱼池服务器不稳定,细数室外鱼池最常见问题及处理办法,很多人都不知道...
  4. MATLAB:图像裁切(imcrop函数)
  5. 获取手机串号 版本 品牌
  6. Day526.数据库备份与恢复 -mysql
  7. 机械臂速成小指南(十一):坐标系的标准命名
  8. 思维导图模板分享及绘制思维导图方法介绍
  9. DEDECMS织梦内容管理系统添加新文章白屏
  10. mysql_error_trace.inc 后台地址_警惕:织梦dedecm漏洞data/mysql_error_trace.inc暴露后台信息...