java 23种设计模式详解

1. 软件设计模式意义

文章链接:http://c.biancheng.net/design_pattern/
    设计模式的本质是对面向对象设计原则运用,是对类的封装、继承和多态以及类的关联关系和组合关系的充分理解。优点:

  • 可以提高程序员的思维能力、编程能力和设计能力
  • 使程序更加标准化、代码编写更加工程化,使软件开发效率大大提升,从而缩短开发周期
  • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

1.1 设计模式的基本要素

    软件设计模式可以使人们更加简洁方便的复用成功的设计和体系结构。
最关键的包括如下4个主要部分

  • 模式名称:有助于我们理解和记忆该模式。
  • 问题:描述了该模式的应用环境,解释了设计问题的前因后果,以及必须满足一些条件。
  • 解决方案:模式就像是一个模板,可应用于不同的场景,所以解决方案并不描述一个特定的设计或者实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合来解决这个问题。
  • 效果:描述了模式的优缺点。

1.2 GoF 23种设计模式的分类和功能

    设计模式有两种分类方法:根据目的来分根据模式的作用范围来分

1.2.1 根据目的来分

根据模式是用来完成什么工作目的的,这种凡是分为创建型模式、结构型模式和行为型模式3种。

  • 创建型模式:用于描述“怎样创建对象”,主要特点就是“将对象的创建和使用分离”。例如:单例、原型、工厂方法、抽象工厂、建造者5中模式创建模式
  • 结构型模式:用于描述如何将类和对象按照某种布局组成更大的结构。例如:代理、适配器、桥接、装饰、外观、享元、组合等7种结构型模式。
  • 行为型模式:用于描述类或者对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。例如:模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等11中行为型模式。
1.2.2 根据作用范围来分

根据模式主要用于类上还是对象上来分,可以分为类模式和对象模式。

  • 类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译的时刻便确定下来。例如:工厂方法、适配器、模板方法、解释器等属于该模式。
  • 对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以改变的,更具动态性。除了类模式的4中,其他模式都是对象模式。
范围\目的 创建型模式 结构型模式 行为型模式
类模式 工厂方法 (类)适配器 模板方法、解释器
对象模式 单例
原型
抽象工厂
建造者
代理
(对象)适配器
桥接
装饰
外观
享元
组合
策略
命令
职责链
状态
观察者
中介者
迭代器
访问者
备忘录
解释器

1.3 23种设计模式的功能

  1. 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该类的实例,其拓展是有限多例模式。
  2. 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制克隆出多个和原型类似的新实例。
  3. 工厂方法(Factory Method)模式:定义一个创建产品的接口,由子类决定生产用什么产品。
  4. 抽象工厂(Abstract Factory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列的相关产品。
  5. 建造者(Builder)模式:将一个复杂的对象分解成多个简单的部分,然后根据不同的需要分别创建他们,最后构建成复杂的对象。
  6. 代理(Proxy)模式:为某个对象提供一种代理以控制对该对象的访问。即客户端通过代理间接的访问该对象,从而限制、增强或修改对象的一些特性。
  7. 适配器(Adapter)模式:将一个类的接口转换成客户希望的另一个类的接口,使得原本由于接口不兼容而不能一起工作的哪些类能一起工作。
  8. 桥接(Bridge)模式:将抽象与实现分离,使他们可以独立变化。他是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
  9. 装饰(Decorator)模式:动态给对象增加一些职责,即增加其额外的功能。
  10. 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
  11. 享元(Flyweight)模式:运用共享技术来有效的支持大量的细粒度对象的复用。
  12. 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
  13. 模板方法(Templete Method)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类在不改变算法结构的情况下重新定义该算法的某些特定步骤。
  14. 策略(Strategy)模式:定义了一些列算法,并将每个算法封装起来,使他们可以相互替换,且算法的改变不会影响使用算法的客户。
  15. 命令(Commond)模式:将一个请求封装成一个对象,使发出请求的责任和执行请求的责任分开。
  16. 职责链(Chain Of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方法除去了对象之间的耦合。
  17. 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
  18. 观察者(Observer)模式:多个对象间存在一对多的关系,当一个对象发生改变时,把这种改变通知其他各个对象,从而影响其他对象的行为。
  19. 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象之间的耦合度,使原有对象不比相互了解。
  20. 迭代器(Iterator)模式:提供一个方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
  21. 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者访问。
  22. 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
  23. 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

1.4 类之间的关系

    在软件系统中,类不是孤立存在的,类与类之间存在各种关系。

  • 依赖关系:依赖关系是一种使用关系,它是对象之间耦合度最弱的关联方式,是临时性的关联关系。在代码中,某个类的方法通过局部变量、方法的参数或者静态方法的调用来访问另一个类中的某些方法来完成一些职责。
//人打电话
public class Person{
    private String name;
    
    //打电话
    public void call(MobilePhone mp){
        //通过方法参数来访问另一个类
        mp.call("打电话");
    }
}

//电话
public class MobilePhone{
    public void call(String message){
        //打电话一系列操作,不详细介绍
        ...
    }
}
  • 关联关系:关联关系是对象之间的一种引用关系,用于标识一类对象跟另一类对象之间的联系。如老师和学生,师傅和徒弟…。关联关系也可以是双向的,可以是单向的。

public class Teacher{
    String name;
    
    //一个老师可以有多个学生
    List students;
}


public class Student{
    String name;
    //一个学生可以有多个老师,语文、数学...老师
    List teachers;
}
  • 聚合关系:聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体而单独存在。例如:学校和老师之间的关系
public class University{
    String name ;
    //一个大学有多个老师
    List teachers;
}

//成员变量,老师
public class Teacher{
    String name;
}
  • 组合关系:组合关系也是一种关联关系,标识类之间的整体与部分的关系,但是它是一种更强烈的关系。在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也不存在了。例如:头和嘴的关系,没了头嘴也不存在了.
//头和嘴关系,头
public class Head{
    Mouth mouth;
    Eye eye;
}

public class Mouth{
    //嘴的属性...
}
  • 泛化关系:泛化关系是对象之间耦合度最大的一种,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承类的关系。例如:学生和老师都是人…
//人
public class Person{
    Integer age;
    String sex;
    String name;
}

public class Student extends Person{
    //学生编号
    Integer studentNo;
    
    //学习
    public void study(){
        //...学习
    }
}

public class Teacher extends Person{
    //老师编号
    Integer teacherNo;
    
    //教学
    public void teach(){
        //教学..
    }
}
  • 实现关系:接口与实现类之间的关系,这种关系中,类实现了接口,类中的操作实现了接口中所有生命的抽象操作。例如:交通工具 move,汽车,火车,轮船之间的关系
//交通工具
public interface Vehicle {
	
	public void move();
}

//汽车实现交通工具接口类
public class Car implements Vehicle{

	@Override
	public void move() {
		// TODO Auto-generated method stub
	}
	
} 

//火车实现交通工具接口类
public class Train implements Vehicle{

	@Override
	public void move() {
		// TODO Auto-generated method stub
	}
	
} 

2.面向对象的7大原则

    在软件开发过程中,为了提高系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序要都需要遵循7条原则来开发,从而提高开发效率节约研发成本和维护成本。

2.1 开闭原则

    开闭原则:软件开发过程中,软件实体应当对扩展开放,对修改关闭。也就是当应用需求改变的时候,在不修改软件实体的源码或者二进制码的情况下,可以扩展模块的功能,使其满足新的需求。

// -- windows主题展示,抽象类,将接口抽象画
public abstract class Subject {
	public abstract void display();
}


//具体的主题是子类
public class SubjectOne extends Subject {
	public void display(){
		System.out.println("第一个主题");
	}
}

//具体的主题是子类
public class SubjectTwo extends Subject {
	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("第二个主题");
	}
	
}

2.2 里氏替换原则

    主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,以及其中蕴含的原理。它是继承复用的基础,反应了基类与子类之间的关系,是对开闭原则的补充,是对抽象化的具体步骤的规范。
里氏替换原则的作用:

  • 是实现开闭原则的重要方式之一
  • 克服了父类继承当中重写父类造成的可复用性变差的缺点
  • 它是动作正确性的保证。即已有的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
        里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但是不能改变父类的功能。也就是说:子类继承父类的时候,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
//动物
public class Animal {
    //行走速度
	private double runSpeed;
	
	//distance公里所需时间
	public double getRunTime(double distance){
		
		return distance/ runSpeed;
	}

	public void setRunSpeed(double runSpeed) {
		this.runSpeed = runSpeed;
	}
}

//鸟类继承动物,但是鸟类是飞行,鸟类扩展了动物的特性,相当于子类的新特性,而且不会影响基类的操作
public class Bird extends Animal {
	//鸟类特性 fly速度
	private double flySpeed;
	//鸟类飞行distance公里所需时间
	public double getFlyTime(double distance){
		return distance / flySpeed;
	}

	public void setSpeed(double flySpeed) {
		this.flySpeed = flySpeed;
	}
}

//燕子继承鸟类
public class Swallow extends Bird {
	
}


//几维鸟继承动物,几维鸟是鸟但是不会飞
public class BrownKiwi extends Animal {

}

public class Main {
	public static void main(String[] args) {
		Bird swallow = new Swallow();
		swallow.setSpeed(120);
		
		Animal kiwi = new BrownKiwi();
		kiwi.setRunSpeed(10);
		
		System.out.println("如果飞行或者行走300公里");
		System.out.println("燕子飞行300公里用时"+swallow.getFlyTime(300)+"小时");
		System.out.println("几维鸟行走300公里用时"+kiwi.getRunTime(300)+"小时");
	}
}

2.3 依赖倒置原则

    高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。其核心思想就是:要面向接口编程,不要面向实现编程。这里的抽象代表接口或者抽象类,而细节指具体的实现。
    依赖倒置原则是实现开闭原则的重要途径之一,降低了客户与实现模块的耦合度。
    依赖倒置原则的作用:

  • 降低类之间的耦合度
  • 提高系统的稳定性
  • 减少并行开发引起的风险
  • 提高代码的可读性和可维护性

    依赖倒置原则的实现方法:

  • 每个类尽量提供接口或者抽象类,或者两者皆备
  • 变量的声明类型应尽量是接口或者抽象类
  • 任何类不应该从具体派生类
  • 使用继承时应尽量遵循里氏替换原则
//顾客购物场景
//商店卖东西
//无论顾客选择那家商店,商店(Store)都有一个sell()接口,顾客(Customer)都有个shopping()购物接口。

//商店卖东西接口类
public Interface Store{
    //卖东西
    public String sell();
}

public class Customer{
    private String name;
    public String shopping(Store shop){
        //购物
        return "顾客购买东西"+shop.sell();
    }
    
    public Customer(String name){
        this.name = name;
    }
} 

//鞋店也是商店,卖鞋,实现了商店接口
public class ShoeStore implements Store{

    public String sell(){
        
        return "耐克,艾迪达斯,鸿星尔克...各种各样的鞋子";
    }
}

//衣服店也是商店,卖衣服,实现了商店接口
public class ClothesStore{
    
    public String sell(){
        
        return "卫衣,毛衣,衬衫,短袖...各种衣服";
    }
}

public class Test{
    
    public static void main(String args[]){
        //顾客
        Customer custom = new Customer("张三");
        //购买鞋子
        custom.shopping(new ShoeStore());
        //购买衣服
        custom.shopping(new ClothesStore());
    }
}

//运行结果:
//张三:
//顾客购买东西:售卖:耐克,艾迪达斯/,鸿星尔克...各种各样的鞋子
//顾客购买东西:卫衣,毛衣,衬衫,短袖...各种衣服

2.4 单一职责原则

    单一职责原则规定,一个类有且仅有一个引起变化的原因,否则这个类就应该被拆分。对象不应该承担太多职责,如果一个对象承担太多职责则会有以下两个缺点:

  1. 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力
  2. 当客户端需要这个对象的一个职责时,不得不将其他不需要的职责包含进来,从而造成冗余代码或者代码的浪费。

    单一职责的有点:

  • 降低类的复杂度,一个类只负责一个职责,其逻辑肯定要比负责多个类的职责简单的多。
  • 提高类的可读性。复杂性降低,自然可读性会提高
  • 提高系统的可维护性。可读性提高,自然维护就容易很多了。
  • 变更引起的风险减低。变更是必然的,如果单一职责的原则遵守好,当修改一个功能的时候,可以显著降低对其他功能的影响。
/**
* 分析:大学学生工作主要包括学生生活辅导和学生学业指导两个方面的工作,其中生活辅导主要包括班委建设、出勤统计、心理辅导、费用催缴、班级管理等工作,学业指导主要包括专业引导、学习辅导、科研指导、学习总结等工作。如果将这些工作交给一位老师负责显然不合理,正确的做 法是生活辅导由辅导员负责,学业指导由学业导师负责。
* 总的来说:学生工作分为两块,生活辅导员,学业辅导员
**/

//学生工作
public class StudentWork{
    
    //生活辅导员
    public LifeTeacher lifeTeacher;
    
    //学业辅导员
    public TeachTeacher teachTeacher;
}

//生活辅导主要包括班委建设、出勤统计、心理辅导、费用催缴、班级管理等工作
public class LifeTeacher{
    
    public void 班委建设(){
        //..班委极限社相关内容
    }
    public void 出勤统计(){
        //..出勤统计相关内容
    }
    public void 心理辅导(){
        //..心理辅导相关内容
    }
    public void 费用催缴(){
        //..费用催缴相关内容
    }
    public void 班级管理(){
        //..班级管理相关内容
    }
}

//学业辅导员主要包括专业引导、学习辅导、科研指导、学习总结等工作
public class TeachTeacher {
    
    public void 专业引导(){
        //..专业引导相关内容
    }
    public void 学习辅导(){
        //..学习辅导相关内容
    }
    public void 科研指导(){
        //..科研指导相关内容
    }
    public void 学习总结(){
        //..学习总结相关内容
    }
}

2.5 接口隔离原则

    接口隔离原则就是将庞大臃肿的接口拆分成更小或者更具体的接口,让接口中只包含客户感兴趣的方法。
    要为各个类建立他们专用的接口,而不要试图去建立一个很庞大的接口供所有的依赖它的类去调用。
    接口隔离原则和单一职责原则有相同点,都是为了提高类的内聚性、降低他们之间的耦合度,体现了封装的思想。但是两者也有不同:

  • 单一职责注重的是职责,接口隔离注重的是对接口的依赖隔离
  • 单一职责主要是约束类,针对的是程序中的实现和细节;接口隔离主要是约束接口,主要针对抽象和程序整体框架的构建。

    接口隔离原则的优点

  • 将庞大臃肿的接口拆分成多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性
  • 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合度
  • 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果接口定义的粒度过小,则会造成接口的数量过多,使得设计复杂化;如果接口定义过大,灵活性就会降低,无法提供定制服务,给整体项目带来无法预料的风险
  • 使用多个专门的接口还能够体现对象的层次,因为可以实现对接口的继承,实现对总接口的定义。
  • 能减少项目中代码的冗余,过大的大接口里面放置了好多无用的方法,挡视线这个接口的时候被迫设计冗余的代码。

    接口隔离原则的实现方法:

  • 接口尽量小,但是要有限度。一个接口只服务于一个子模块或者业务逻辑
  • 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。
  • 了解环境,拒绝盲从。每个项目或者产品都会选定的环境因素,环境不同,接口拆分的标准就会不同,深入了解业务逻辑
  • 提高内聚,减少对外交互,使得接口用最少的方法完成最多的事情
//学生成绩管理程序一般包含插入成绩、删除成绩、修改成绩、计算总分、计算均分、打印成绩信息、査询成绩信息等功能,如果将这些功能全部放到一个接口中显然不太合理,正确的做法是将它们分别放在输入模块、统计模块和打印模块等 3 个模块中
//1.输入模块:插入成绩,修改成绩,删除成绩
//2.统计模块:计算总分,计算平均分
//3.打印模块:打印成绩信息,查询成绩信息

// 插入模块
public interface InputMoudle{
    //插入成绩
    public void insert();
    
    //删除成绩
    public void delete();
    
    //修改成绩
    public void update();
}

// 统计模块
public interface CountMoudle{
    
    //计算总分
    public Integer countTotalScore();
    
    //计算平均分
    public double countAvgScore();
}

// 打印模块
public interface PrintMoudle{
    
    //打印成绩信息
    public void print();
    
    //查询成绩信息
    public void search();
}

// 实现类
public class StuScoreList implements InputMoudle,CountMoudle,PrintMoudle{
    
    private StuScoreList{}
    
    public static InputMoudle getInputMoudle(){
        return (InputMoudle)new StuScoreList();
    }
    
    public static CountMoudle getCountMoudle(){
        return (CountMoudle)new StuScoreList();
    }
    
    public static PrintMoudle getPrintMoudle(){
        return (PrintMoudle)new StuScoreList();
    }
    
    public void insert()
    {
        System.out.println("输入模块的insert()方法被调用!");
    }
    public void delete()
    {
        System.out.println("输入模块的delete()方法被调用!");
    }
    public void modify()
    {
        System.out.println("输入模块的modify()方法被调用!");
    }
    public void countTotalScore()
    {
        System.out.println("统计模块的countTotalScore()方法被调用!");
    }
    public void countAverage()
    {
        System.out.println("统计模块的countAverage()方法被调用!");
    }
    public void printStuInfo()
    {
        System.out.println("打印模块的printStuInfo()方法被调用!");
    }
    public void queryStuInfo()
    {
        System.out.println("打印模块的queryStuInfo()方法被调用!");
    }
}

public class ISPtest
{
    public static void main(String[] args)
    {
        InputModule input =StuScoreList.getInputModule();
        CountModule count =StuScoreList.getCountModule();
        PrintModule print =StuScoreList.getPrintModule();
        input.insert();
        count.countTotalScore();
        print.printStuInfo();
        //print.delete();
    }
}

2.6米迪特法则

    米迪特法则的定义是:如果两个软件实体无需直接通信,那么就不应当发生直接的交互调用,可以通过第三方转发该调用。其目的是为了降低类之间的耦合度,提高模块相对独立。

    米迪特法则的有点:

  • 降低了类的耦合度,提高了模块的相对独立性
  • 由于亲和度降低,从而提高了类的可复用率和系统的扩展性。

米迪特法则的缺点:过度使用米迪特法则会使得系统产生大量的中介类,从而增加系统的复杂度,使得模块之间的通信效率降低。所以在采用米迪特法则的时候需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。

米迪特法则的实现方法:

  • 从依赖者的角度来说,只依赖该依赖的对象。
  • 从被依赖的角度来说,只暴露该暴露的方法。

所以在运用米迪特法则的时候需要注意6点:

  • 在累的划分上,应该创建弱耦合的类,类与类之间的耦合越弱,就越有利于实现可复用的目标。
  • 在类的结构设计上,尽量减低类成员的访问权限
  • 在类的设计上,优先考虑将一个类设置成不变类
  • 在对其他类的引用上,将引用其他对象的次数降到最低
  • 不暴露类的属性成员,而应该提供类的访问器(set 和 get方法)
  • 谨慎使用序列化功能
//明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则


//明星
public class Star{
    
    private String name ;
    
    private String getName(){
        
        return this.name;
    }
}

//粉丝
public class Fans{
    
    private String name;
    
    public String getName{
        //获取粉丝名称
        return this.name;
    }
}

//公司
public class Company{
    
    private String name;
    
    public String getName{
        //获取公司名称
        return this.name;
    }
}

//经纪人
public class Agent{
    
    private Star myStar;
    
    private Fans myFans;
    
    private Company myCompany;
    
    public void setStar(Star myStar){
        this.myStar = mystar;
    }
    
    public void setFans(Fans myFans){
        this.myFans = myFans;
    }
    
    public void setCompany(Company myCompany){
        this.myCompany = myCompany;
    }
    
    public void meeting(){
        System.out.println(myFans.getName +"与明星"+myStar.getName()+"见面了");
    }
    
    public void business(){
        System.out.println(myCompany.getName +"与明星"+myStar.getName()+"洽谈业务了");
    }
}

public class LoDtest
{
    public static void main(String[] args)
    {
        Agent agent=new Agent();
        agent.setStar(new Star("林心如"));
        agent.setFans(new Fans("粉丝韩丞"));
        agent.setCompany(new Company("中国传媒有限公司"));
        agent.meeting();
        agent.business();
    }
}

// 粉丝韩丞与明星林心如见面了。
// 中国传媒有限公司与明星林心如洽淡业务。

2.7 合成复用原则

    合成复用原则要求在软件复用时,要尽量先使用组合或者聚合关系来关联实现,其次才考虑使用继承关系来实现。如果要使用继承关系,则必须严格遵循里氏替换原则。
    合成复用原则的重要性,通常复用分为继承复用和合成复用两种,继承复用虽然简单和易实现的有点,但是也有缺点:

  • 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
  • 子类与父类耦合度高。父类的实现的改变都会导致子类的实现发生变化,这不利于扩展和维护。
  • 限制了复用的灵活性。从父类继承而来的实现是静态的,在编译的时候已经定义了,所以在运行时不可能发生变化。

    采用组合或者聚合复用时,可以将已有的对象纳入新的对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,有以下优点:

  • 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
  • 新旧类之间的耦合度低。这种复用所需要的依赖少,新对象存取成分对象的唯一方法就是通过成分对象的接口。
  • 复用的灵活性高。这种复用可以在运行动态进行,新对象可以动态的引用成分对象类型相同的对象。

    合成复用原则的实现方法:合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员来实现,新对象可以调用已有对象的功能,从而达到复用。

//分析:汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多

//汽车:--移动move
//    : --颜色Color:
//        --白色
//        --黑色
//        --红色
//汽车对象将颜色对象纳入到汽车里面,作为汽车的对象来使用
public class Color{
    private String color;

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}
}

public class Car{
    private Color color;
	
	public void move(){
		System.out.println("汽车");
	}
	
	public Car(Color color){
		this.color = color;
	}

	public Color getColor() {
		return color;
	}

	public void setColor(Color color) {
		this.color = color;
	}
}

public class GasoLineCar extends Car{

	public GasoLineCar(Color color) {
		super(color);
		// TODO Auto-generated constructor stub
	}

}

public class White extends Color{

}  

public class Main {
	public static void main(String[] args) {
		Color color = new White();
		color.setColor("白颜色");
		Car car = new GasoLineCar(color);
		System.out.println(car.getColor().getColor());
		car.move();
	}
}
// 白颜色
// 汽车

3 创建模式的特点和分类

3.1 创建模式的特点

    创建模式主要是"将对象的创建和使用分开",这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由工厂来完成。就像我们去商场购买商品的时候,不需要知道商品是如何生产出来的一样,因为它由专门的公司生产。
    创建模式分类:

  • 单例模式
  • 原型模式
  • 工厂方法模式
  • 抽象工厂模式
  • 建造者模式

    工厂方法模式是类模式,其他四种都是对象模式

3.1.1 单例模式

    在有些系统中,为了节省内存资源、保证数据内容一致,对某些类要求只能创建一个实例,这就是单例模式。

    单例模式的定义:
    一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如:windows系统之只能打开一个任务管理器,这样可以避免打开多个而浪费资源,或者各个窗口显示的内容不一致导致错误。
    单例模式的特点:

  • 单例实例只有一个实例对象
  • 该单例对象必须有该类自行创建
  • 单例提供对外访问的一个全局访问点

    单例模式的实现:

  • 懒汉式单例
  • 饿汉式单例
  • 登记式单例

    懒汉式单例
该模式的特点就是类加载的时候没有生成单例,只有当第一次调用getInstance()方法的时候才会生成单例。

public class LazySingleton {

	//volatile 保证instance在所有的线程中同步
	private static volatile LazySingleton instance = null;
	
	//private 避免类在外部被实例化
	private LazySingleton(){
		System.out.println("创建一个懒汉单例模式..");
	}
	
	// synchronized 同步操作
	public static synchronized LazySingleton getInstance(){
		System.out.println("模式instance="+instance);
		if(null == instance){
			System.out.println("开始创建..");
			instance = new LazySingleton();
			System.out.println("创建成功instance"+instance);
		}
		return instance;
	}
}

public class Client {
	public static void main(String[] args) {
		for(int i = 0 ; i < 3 ; i++){
			System.out.println(i+".================================");
			LazySingleton instance = LazySingleton.getInstance();
		}
		
		
	}
}
运行结果:
0.================================
模式instance=null
开始创建..
创建一个懒汉单例模式..
创建成功instancecom.singleton.LazySingleton@6d06d69c
1.================================
模式instance=com.singleton.LazySingleton@6d06d69c
2.================================
模式instance=com.singleton.LazySingleton@6d06d69c

    注意:如果编写多线程程序,不要删除上列代码的关键字volatile 和 synchronized ,否则会引起线程非安全问题。如果不删除这两个关键字就能保证多线程安全,但是每次访问都需要同步,会影响性能,而且消耗更多资源,这就是懒汉式单例的缺点。

    饿汉单例式:该模式的特点是类一旦加载就会创建一个单例,保证在调用getInstance方法之前单例已经存在。

public class HungrySingleton {
	
	// 类加载的时候就会创建一个单例模式
	private static final HungrySingleton instance = new HungrySingleton();
	
	// 保证实例不会被外部创建
	private HungrySingleton(){
		System.out.println("开始创建单例..");
	}
	
	public static HungrySingleton getInstance(){
		System.out.println("获取单例..instance="+instance);
		return instance;
	}
	
}

public class Client {
	public static void main(String[] args) {
		for(int i = 0 ; i < 3 ; i++){
			System.out.println(i+".================================");
			HungrySingleton instance = HungrySingleton.getInstance();
		}
		
		
	}
}
运行结果:
0.================================
开始创建单例..
获取单例..instance=com.singleton.HungrySingleton@6d06d69c
1.================================
获取单例..instance=com.singleton.HungrySingleton@6d06d69c
2.================================
获取单例..instance=com.singleton.HungrySingleton@6d06d69c

    饿汉式单例在类创建的时候同时也创建了一个静态对象供系统使用,以后不在改变,所以是线程安全的,可以直接用于多线程而不会出现问题。

    单例模式的应用场景:

  • 在应用场景中,某个类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象的访问速度。如Web中配置的对象、数据库连接池等。
  • 当某个类需要频繁的实例化,而创建的对象需要频繁被销毁的时候,如线程的线程池、网络连接池等。
3.1.2 原型模式

    用一个已经创建好的对象作为原型,通过复制该对象原型来创建和原型相同或者相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无需知道对象创建的细节。例如:windows操作系统的安装比较繁琐,如果复制的话就简单多了。

    原型模式的结构和实现
    由于java提供了对象的clone()方法,所以用java的实现原型模式很简单。

  • 模式的结构:原型模式包含如下几个角色
  1. 抽象类原型:规定的具体的原型对象必须实现的接口
  2. 具体原型类:实现类抽象类原型的clone()方法,它是可被复制的对象。
  3. 访问类:使用具体原型类中的clone()方法来复制新的对象。
  • 原型模式的实现
        原型模式的克隆分为浅克隆深克隆,java中的Object类提供了浅克隆的clone()方法,具体原型类只要实现Cloneable接口就可以实现对象的浅克隆,这里的Cloneable就是抽象原型类。
  1. 浅克隆:当被克隆的类中有引用对象(String、Integer等)基本类型除外时,克隆出来的类中的引用变量存储的还是之前对象的内存地址。
//浅克隆的例子
//用原型模式生成“三好学生”奖状。
//分析:同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,属于相似对象的复制,同样可以用原型模式创建,然后再做简单修改就可以了。

public class Citation implements Cloneable{
	private String name;
	private String info;
	private String college;
	//对象的引用
	private Address address;
	public Citation(String name, String info, String college) {
		super();
		this.name = name;
		this.info = info;
		this.college = college;
		System.out.println("奖状创建成功 name="+name);
	}
	
	
	public Citation(String name, String info, String college, Address address) {
		super();
		this.name = name;
		this.info = info;
		this.college = college;
		this.address = address;
		System.out.println("奖状创建成功 name="+name+",address="+address.getName());
	}


	public void display(){
		System.out.println(name+info+college+address.getName());
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getInfo() {
		return info;
	}
	public void setInfo(String info) {
		this.info = info;
	}
	public String getCollege() {
		return college;
	}
	public void setCollege(String college) {
		this.college = college;
	}

	public Address getAddress() {
		return address;
	}


	public void setAddress(Address address) {
		this.address = address;
	}


	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		System.out.println("奖状拷贝成功name="+name+",address="+address.getName());
		return (Citation)super.clone();
	}

public class Address implements Serializable{
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
}

public class Main {
	
	public static void main(String[] args) throws CloneNotSupportedException {
		Address address = new Address();
		address.setName("北京");
		Citation citation = new Citation("张三", "这一学期成绩优异,特此颁布三好学生奖状", "北京大学",address);
		citation.display();
		Citation citation2 = (Citation)citation.clone();
		citation2.setName("李四");
		//克隆完成之后,重新设置address为shanghai,发现张三的地址也变了,也就是指向的元素地址是指向的原来张三的地址,也就是引用对象还是用的原来的地址
		citation2.getAddress().setName("shanghai");
		citation2.display();
		citation.display();
	}
}
//运行结果
奖状创建成功 name=张三,address=北京
张三这一学期成绩优异,特此颁布三好学生奖状北京大学北京
奖状拷贝成功name=张三,address=北京
李四这一学期成绩优异,特此颁布三好学生奖状北京大学shanghai
张三这一学期成绩优异,特此颁布三好学生奖状北京大学shanghai


  1. 深克隆:当被克隆的类中有引用对象(String、Integer等)基本类型除外时,克隆出来的类中的引用对象也需要单独的去克隆,也就是引用对象内部也需要单独实现Cloneable类,实现clone()方法。注意:用final关键字修饰的变量不能被克隆。而且深度克隆还可以通过序列化和反序列化来实现。

深度克隆-:普通克隆

//普通深度克隆列子
public class Person implements Cloneable{
	
	private String name;
	
	private String age;
	
	//兴趣爱好,对象的引用
	private Interest interest;
	
	//构造方法
	public Person(String name, String age, Interest interest) {
		super();
		this.name = name;
		this.age = age;
		this.interest = interest;
	}
		

	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		Person person = (Person)super.clone();
		person.interest = (Interest)this.interest.clone();
		return person;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getAge() {
		return age;
	}

	public void setAge(String age) {
		this.age = age;
	}

	public Interest getInterest() {
		return interest;
	}

	public void setInterest(Interest interest) {
		this.interest = interest;
	}
	
	public void display(){
		System.out.println(name +","+age+",兴趣爱好 (instrest)"+interest.getInterest_name());
	}
	
}
//引用对象需要实现Cloneable接口
public class Interest implements Cloneable{
	
	private String interest_name;
	
	
	public Interest(String interest_name) {
		super();
		this.interest_name = interest_name;
	}

	//引用对象内部也要重写clone方法
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		Interest interest = (Interest)super.clone();
		return interest;
	}

	public String getInterest_name() {
		return interest_name;
	}

	public void setInterest_name(String interest_name) {
		this.interest_name = interest_name;
	}
}

public class Main {
	
	public static void main(String[] args) throws CloneNotSupportedException {
		Interest interest = new Interest("喝咖啡");
		Person p1 = new Person("张三", "18", interest);
		p1.display();
		
		Person p2 = (Person)p1.clone();
		p2.setName("李四");
		p2.getInterest().setInterest_name("逛街");
		p2.display();
		p1.display();
	}
}

//运行结果
张三,18,兴趣爱好 (instrest)喝咖啡
李四,18,兴趣爱好 (instrest)逛街
张三,18,兴趣爱好 (instrest)喝咖啡
//从运行结果中分析,李四克隆了张三的属性,但是兴趣爱好也克隆了,此时李四跟张三的兴趣指向的是不同的地址,修改李四的兴趣爱好(interset)的时候不会印象张三的兴趣爱好(interset)

深度克隆-:序列化克隆

//使用序列化克隆的时候,被克隆的类必须实现序列化接口类Serializable,否则就会出现如下错误
Exception in thread "main" java.io.NotSerializableException: com.yuanxing.Person
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.writeObject(Unknown Source)
	at com.yuanxing.Main.main(Main.java:39)
	
public class Person implements Serializable{
	
	private String name;
	
	private String age;
	
	//兴趣爱好,对象的引用
	private Interest interest;
	
	//构造方法
	public Person(String name, String age, Interest interest) {
		super();
		this.name = name;
		this.age = age;
		this.interest = interest;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getAge() {
		return age;
	}

	public void setAge(String age) {
		this.age = age;
	}

	public Interest getInterest() {
		return interest;
	}

	public void setInterest(Interest interest) {
		this.interest = interest;
	}
	
	public void display(){
		System.out.println(name +","+age+",兴趣爱好 (instrest)"+interest.getInterest_name());
	}
	
}
public class Interest implements Serializable{
	
	private String interest_name;
	
	
	public Interest(String interest_name) {
		super();
		this.interest_name = interest_name;
	}
	public String getInterest_name() {
		return interest_name;
	}

	public void setInterest_name(String interest_name) {
		this.interest_name = interest_name;
	}
}

public class Main {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		
		Interest interest = new Interest("打篮球");
		Person p1 = new Person("张三", "18", interest);
		p1.display();
		//使用序列化和反序列化实现深克隆
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(out);
		oos.writeObject(p1);
		byte[] bytes = out.toByteArray();
		
		ByteArrayInputStream in = new ByteArrayInputStream(bytes);
		ObjectInputStream ois = new ObjectInputStream(in);
		
		Person p2 = (Person)ois.readObject();
		
		p2.getInterest().setInterest_name("看电影");
		p2.setName("王麻子");
		p2.display();
		p1.display();
	}
}

//运行结果
张三,18,兴趣爱好 (instrest)打篮球
王麻子,18,兴趣爱好 (instrest)看电影
张三,18,兴趣爱好 (instrest)打篮球

3.1.3 工厂模式

    定义一个创建产品对象的工厂接口,将产品创建的实际创建工作推迟到子工厂类当中。这种模式满足典型的“创建对象与使用对象分离”的特点。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫做“简单工厂模式”,它不属于23种设计模式,缺点是增加产品时会违背“开闭原则”。
    工厂模式的主要优点:

  • 用户只需要知道具体工厂的名称就可以得到所需要的产品,无需知道产品的具体创建过程。
  • 在系统增加新产品时候,只需要添加具体的品类和具体的工厂类,无需对原工厂进行修改,满足开闭原则

    工厂模式的缺点:每增加一个产品就需要增加一个具体的产品类和具体工厂类,增加了系统的复杂度。

    工厂模式的模式结构:

  • 抽象工厂:提供了创建产品的接口,调用者通过访问具体的工厂创建产品。
  • 具体工厂:主要实现了抽象工厂,来完成创建具体产品的操作
  • 抽象产品:定义了产品的范围,描述了产品的主要特性和功能。
  • 具体产品:实现了抽象产品角色所定义的接口,有具体的工厂来创建,同具体的工厂一一对应。
//例子:汽车和工厂,卡车和公共汽车都属于汽车,汽车属于抽象产品,卡车和公共汽车属于具体产品
//工厂属于抽象工厂,卡车工厂和公共汽车工厂属于具体工厂
public interface Car {
	
	public void show();
}

public interface CarFactory {
	
	public Car createCar();
}

public class Truck implements Car{
	@Override
	public void show() {
		// TODO Auto-generated method stub
		System.out.println("我是一辆卡车");
	}
}

public class Bus implements Car{
	@Override
	public void show() {
		// TODO Auto-generated method stub
		System.out.println("我是一辆公共汽车。|-|。");
	}
}

public class TruckFactory implements CarFactory{
	@Override
	public Car createCar() {
		// TODO Auto-generated method stub
		System.out.println("进入卡车工厂,开始创建卡车");
		return new Truck();
	}
}

public class BusFactory implements CarFactory{
	@Override
	public Car createCar() {
		// TODO Auto-generated method stub
		System.out.println("进入公共汽车工厂,开始创建公共汽车");
		return new Bus();
	}
}


public class Main {
	public static void main(String[] args) {
		Car car ;
		CarFactory factory;
		factory = new BusFactory();
		car = factory.createCar();
		car.show();
		
		factory = new TruckFactory();
		car = factory.createCar();
		car.show();
	}
}

//运行结果
进入公共汽车工厂,开始创建公共汽车
我是一辆公共汽车。|-|。
进入卡车工厂,开始创建卡车
我是一辆卡车

3.1.4 抽象工厂

    是一种访问类提供一个创建一组相关或者相互依赖对象的接口,且访问类无需指定所需要产品的具体类就能得到同族的不同等级的产品的模式结构。
    抽象工厂是工厂方法模式的升级版,工厂方法只生产一个等级的产品,而抽象工厂模式可以生产多个等级的产品。
    抽象工厂模式一般满足以下条件

  • 系统中有多个产品族,每个具体的工厂创建统一族但是不同等级的产品。
  • 系统一次可能消费其中某一族的产品,即同族的产品一起使用。

    产品登记结构与产品族

  • 产品登记结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品的等级结构,抽象电视机是父类,而具体品牌的电视机是子类。
  • 产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同的产品等级结构中的一组产品。如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机的产品等级结构中,海尔电冰箱位于电冰箱的产品结果中,海尔电视机、海尔电冰箱构成了一个产品族。

    抽象工厂模式拒了具有工厂模式的特点外, 其主要有以下优点:

  • 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
  • 当增加一个新的产品族时不需要修改原代码,满足开闭原则。

    抽象工厂的缺点:当产品族中需要增加一个新的产品时,所有的共产类都需要进行修改。

抽象工厂模式的结构与实现
    抽象工厂同工厂方法模式一样,也具有四个要素:抽象工厂、具体工厂、抽象产品、具体产品4个要素组成。但是抽象工厂中的方法个数不同,抽象产品的个数也不同。
    模式的结构:

  • 抽象工厂:提供了产品的创建接口,包含多个创建产品的方法newProduct(),可以创建多个不同等级的产品。
  • 具体工厂:主要是实现抽象工厂的多个抽象方法,完成具体的产品创建
  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品:实现了抽象产品角色所定义的所有产品,由具体的工厂来创建,它同具体工厂之间是一对多的关系,一个工厂可以生产多个不同等级的产品。
例子:每个网站都有不同的皮肤,每个皮肤包含按钮(button),输入框(textFiled),边框(comboBox)..等

//抽象产品-按钮类
public interface Button {
	
	public void display();
}

//具体产品 - Spring按钮
public class SpringButton implements Button {

	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("浅绿色Springbutton");
	}

}

//具体产品 - SummerButton
public class SummerButton implements Button {

	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("绿色按钮SummerButton");
	}

}

//输入框
public interface TextFiled {
	
	public void display();
}

//具体产品 - SpringTextFiled
public class SpringTextFiled implements TextFiled {

	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("浅绿色textFiled");
	}

}

//具体产品 - SummerTextFiled
public class SummerTextFiled implements TextFiled {

	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("绿色的textFiled");
	}

}

//抽象产品 - ComboBox
public interface ComboBox {
	
	public void display();
}

//具体产品 - SpringComboBox
public class SpringComboBox implements ComboBox {

	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("浅绿色Combobox");
	}

}

//具体产品- SummerComboBox
public class SummerComboBox implements ComboBox {

	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("绿色Combobox");
	}

}


//抽象工厂 
public interface SkinFactory {
	
	public Button createButton();
	
	public TextFiled createTextFiled();
	
	public ComboBox createComboBox();
}

//具体工厂 - 创建SpringSkin
public class SpringSkinFactory implements SkinFactory {

	@Override
	public Button createButton() {
		// TODO Auto-generated method stub
		return new SpringButton();
	}

	@Override
	public TextFiled createTextFiled() {
		// TODO Auto-generated method stub
		return new SpringTextFiled();
	}

	@Override
	public ComboBox createComboBox() {
		// TODO Auto-generated method stub
		return new SpringComboBox();
	}

}

//具体工厂 - 创建SummerSkin
public class SummerSkinFactory implements SkinFactory {

	@Override
	public Button createButton() {
		// TODO Auto-generated method stub
		return new SpringButton();
	}

	@Override
	public TextFiled createTextFiled() {
		// TODO Auto-generated method stub
		return new SpringTextFiled();
	}

	@Override
	public ComboBox createComboBox() {
		// TODO Auto-generated method stub
		return new SpringComboBox();
	}

}

public class Main {
	public static void main(String[] args) {
		SkinFactory factory ;
		Button bt;
		TextFiled tf;
		ComboBox cb;
		
		//根据工厂的不同,实例化不同的具体工厂,用工厂来创建统一族的不同等级的产品
		factory = new SpringSkinFactory();
		bt = factory.createButton();
		tf = factory.createTextFiled();
		cb = factory.createComboBox();
		
		bt.display();
		tf.display();
		cb.display();
	}
}

//运行结果:
//浅绿色Springbutton
//浅绿色textFiled
//浅绿色Combobox

3.1.5 建造者(Builder)模式

    建造者模式指将一个复杂的对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示。它是将一个复杂的对象分解成多个简单的对象,然后一步一步构建而成。即:产品的组成是不变的,但是每一部分都可以灵活选择。
    建造者模式的优点:

  • 各个具体的建造者相互独立,有利于系统的扩展
  • 客户端不必知道产品的内部组成细节,便于控制细节风险

    建造者模式的缺点:

  • 产品的组成部分必须相同,这限制了其使用范围
  • 如果产品的内部变化复杂,该模式会增加许多建造者类。

    建造者模式跟工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法更注重零部件的创建过程,但是两者可以结合使用。
    建造者模式的结构和实现
    建造者模式的结构:建造者模式由产品角色、抽象建造者、具体建造者、指挥者等4个要素组成。

  • 产品角色:它是包含多个组件的复杂对象,由具体的建造者来创建各个组件。
  • 抽象建造者:它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法getResult();
  • 具体建造者:实现了抽象建造者(Builder)的接口,完成复杂产品组件的的具体创建方法。
  • 指挥者:它调用抽象建造者中的部件构造与装配方法完成复杂对象的创建,在只会中不涉及具体产品的信息。
用建造者(Builder)模式描述客厅装修。
分析:客厅装修是一个复杂的过程,它包含墙体的装修、电视机的选择、沙发的购买与布局等。客户把装修要求告诉项目经理,项目经理指挥装修工人一步步装修,最后完成整个客厅的装修与布局,所以本实例用建造者模式实现比较适合。
这里客厅是产品,包括墙、电视和沙发等组成部分。具体装修工人是具体建造者,他们负责装修与墙、电视和沙发的布局。项目经理是指挥者,他负责指挥装修工人进行装修。
// 产品角色
public class Parlour {
	private String wall;
	private String tv;
	private String sofa;
	public void show(){
		System.out.println("展示客厅的装修结果....");
		System.out.println(wall);
		System.out.println(tv);
		System.out.println(sofa);
	}
	public String getWall() {
		return wall;
	}
	public void setWall(String wall) {
		this.wall = wall;
	}
	public String getTv() {
		return tv;
	}
	public void setTv(String tv) {
		this.tv = tv;
	}
	public String getSofa() {
		return sofa;
	}
	public void setSofa(String sofa) {
		this.sofa = sofa;
	}
	
}


// 抽象的建造者
public abstract class Builder {
	
	public Parlour parlour = new Parlour();
	
	//构建组件
	public abstract void buildWall();
	
	//构建组件
	public abstract void buildTv();
	
	//构建组件
	public abstract void buildSofa();
	
	// 获取结果
	public Parlour getResult(){
		return parlour;
	}
}
// 具体建造者
public class ConcreateDecoratorFirst extends Builder {
	
	private String des = "建造者First,开始构建";
	@Override
	public void buildWall() {
		// TODO Auto-generated method stub
		System.out.println(des +"wall");
		parlour.setWall("wall");
	}

	@Override
	public void buildTv() {
		// TODO Auto-generated method stub
		System.out.println(des +"tv");
		parlour.setTv("tv");
	}

	@Override
	public void buildSofa() {
		// TODO Auto-generated method stub
		System.out.println(des +"sofa");
		parlour.setSofa("sofa");
	}

}
//具体建造者
public class ConcreateDecoratorSecond extends Builder {
	
	private String des = "建造者second,开始构建";
	@Override
	public void buildWall() {
		// TODO Auto-generated method stub
		System.out.println(des +"wall");
		parlour.setWall("wall");
	}

	@Override
	public void buildTv() {
		// TODO Auto-generated method stub
		System.out.println(des +"tv");
		parlour.setTv("tv");
	}

	@Override
	public void buildSofa() {
		// TODO Auto-generated method stub
		System.out.println(des +"sofa");
		parlour.setSofa("sofa");
	}

}

//指挥者
public class Manager {
	private Builder builder;

	public Manager(Builder builder) {
		this.builder = builder;
	}
	
	public Parlour decorate(){
		builder.buildSofa();
		builder.buildTv();
		builder.buildWall();
		return builder.getResult();
	}

	public Builder getBuilder() {
		return builder;
	}

	public void setBuilder(Builder builder) {
		this.builder = builder;
	}
	
}

public class Main {
	public static void main(String[] args) {
		
		Builder builder = new ConcreateDecoratorFirst();
		Manager manager = new Manager(builder);
		Parlour parlour = manager.decorate();
		parlour.show();
	}
}

//运行结果:
建造者First,开始构建sofa
建造者First,开始构建tv
建造者First,开始构建wall
展示客厅的装修结果....
wall
tv
sofa

    建造者模式创建的复杂对象,其产品的各个组成部分经常面领着剧烈的变化,但将他们组合在一起的算法相对稳定:

  • 创建比较发复杂的对象,由多个部件组成,各部件面领着复杂的变化,但各个部件的建造顺序是稳定的。
  • 创建复杂对象的算法独立于该对象的组成部分以及他们的装配方式,即产品的构建和最终的表示是相对独立的。

3.2结构型模式概述

    结构型模式描述如何将类或者对象按照某种布局组成更大的结构。它分为类结构模型和对象结构模型,类结构模型采用继承机制来组织接口和类,对象结构模型采用组合或聚合来组合对象。
    由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构模型比类结构模型具有更大的灵活性。
    结构型模式分为7种:

  • 代理模式
  • 适配器模式
  • 桥接模式
  • 装饰模式
  • 外观模式
  • 享元模式
  • 组合模式

3.2.1 代理模式

    由于某些原因需要给对象提供一个代理以控制该对象的访问权限。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象的中介。例如:12306代售点、找工作的中介等。。

    代理模式的优点:

  • 代理模式在客户端与目标对象之间起到一个中介的作用和保护目标对象的作用
  • 代理模式可以扩展目标对象的功能
  • 代理模式将客户端和目标对象分离,在一定程度上降低了系统的耦合度。

    代理模式的缺点:

  • 客户端和目标对象多了一个代理,会造成请求速度变慢
  • 增加了系统的复杂度

    代理模式的结构和实现:代理模式结构比较简单,主要是通过定义一个继承抽象主题的代理包含真实主题,从而实现对真实主题的访问。
模式结构

  • 抽象类:通过接口或者抽象类声明真实主题和代理对象的业务方法
  • 真实主题:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,他可以访问、控制或者扩展真实主题的功能。
例子:奔驰中国大区代理
真实主题:奔驰
代理:中国大区总代理,可以对奔驰进行加价,漏油等操作
// 抽象主题:卖车
public interface Car {
	public void sell();
}


//真实主题:卖奔驰
public class BenZ implements Car{
	
	public void sell(){
		System.out.println("访问卖奔驰车的方法...");
	}
}

//中国大区代理
public class CNProxy implements Car{
	
	private BenZ benz;
	@Override
	public void sell() {
		// TODO Auto-generated method stub
		if(benz == null){
			benz = new BenZ();
		}
		addMoney();
		//利用代理类,卖奔驰
		benz.sell();
		afterSell();
		
	}
	
	//扩展
	public void addMoney(){
		System.out.println("加价1000$");
	}
	
	//扩展
	public void afterSell(){
		System.out.println("卖出去之后开始漏油...");
	}
	
}

public class Main {
	public static void main(String[] args) {
		CNProxy proxy = new CNProxy();
		proxy.sell();
	}
}

//运行结果
先加价1000$
访问卖奔驰车的方法...
卖出去之后开始漏油...

代理模式应用场景

  • 远程代理:这种方式通常是为了隐藏目标对象在于不同的地址空间的事实。例如:申请某些网盘空间时,会在用户的文件系统中建议一个虚拟的硬盘,用户访问虚拟硬盘时实际上访问的是网盘空间。
  • 虚拟代理:这种方式通常用于要创建的目标对象开销很大。例如:下载一部电影需要很长时间,因某种计算比较发咋而无法短时间完成,这时可以用小比例的虚拟代理替换真实对象,消除用户对服务器的缓慢感觉。
  • 安全代理,这种方式通常用于控制不同类型客户对真实对象的访问权限设置。
  • 智能指引,主要用于调用目标对象时,代理附加一些额外的功能。例如:增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
  • 延迟加载,为了提高系统的性能,延迟目标的加载。

代理模式的扩展:SpringAOP原理其实就是用的动态代理模式。

3.2.2 适配器模式

    适配器模式,将一个类的接口转换成客户希望的另一个类的接口,使得原本由于接口不兼容而不能一起工作的那些类能在一起工作。适配器模式分为类结构模式和对象结构模式两种,前者的耦合度比后者的高,且要求程序员了解现有组建中的相关的内部结构,所以应用相对较少。
    适配器模式的有点:

  • 客户通过适配器可以透明的调用目标接口
  • 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类
  • 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致问题

    适配器模式的缺点:

  • 对类适配器来说,更换适配器的实现过程比较复杂。
模式的结构和实现

    结构:

  • 目标接口:当前系统业务所期待的接口,它可以是抽象类或者接口
  • 适配者类:它是被访问和适配的现存组件库中的组件接口
  • 适配器类:它是一个转换器,通过继承或者引用适配者对象,把适配者接口转换成目标接口,让客户按照目标接口的格式访问适配者
新能源汽车的发动机有电能发动机(Electric Motor)和光能发动机(Optical Motor)等,各种发动机的驱动方法不同,例如,电能发动机的驱动方法 electricDrive() 是用电能驱动,而光能发动机的驱动方法 opticalDrive() 是用光能驱动,它们是适配器模式中被访问的适配者。

客户端希望用统一的发动机驱动方法 drive() 访问这两种发动机,所以必须定义一个统一的目标接口 Motor,然后再定义电能适配器(Electric Adapter)和光能适配器(Optical Adapter)去适配这两种发动机

分析:用统一的接口去实现两种方式的切换
目标接口(统一接口):Motor
适配者:电动发动机,光力发动机
适配器:电力适配器,光力适配器
//目标接口:target
public interface Motor {
	
	public void drive();
}


//适配者 :adaptee
public class ElectricMotor{

	public void electricDrive() {
		// TODO Auto-generated method stub
		System.out.println("电力发动机...");
	}

}

//适配者:adaptee
public class OpticalMotor {
	
	public void opticalDrive(){
		System.out.println("光力发动机发动...");
	}
}

//适配器 :adapter电能适配器
public class ElectricAdapter implements Motor{
	
	private ElectricMotor eMotor;
	
	public ElectricAdapter(ElectricMotor eMotor) {
		this.eMotor = eMotor;
	}

	
	@Override
	public void drive() {
		// TODO Auto-generated method stub
		eMotor.electricDrive();
	}


	public ElectricMotor geteMotor() {
		return eMotor;
	}

	public void seteMotor(ElectricMotor eMotor) {
		this.eMotor = eMotor;
	}
	
}

//适配器 :adapter光能适配器
public class OpticalAdapter implements Motor {
	
	private OpticalMotor oMotor;

	public OpticalAdapter(OpticalMotor oMotor) {
		this.oMotor = oMotor;
	}

	@Override
	public void drive() {
		// TODO Auto-generated method stub
		oMotor.opticalDrive();
	}

	public OpticalMotor getoMotor() {
		return oMotor;
	}

	public void setoMotor(OpticalMotor oMotor) {
		this.oMotor = oMotor;
	}

}

public class Main {
	public static void main(String[] args) {
		System.out.println("适配器模式开始测试...");
		OpticalMotor opMotor = new OpticalMotor();
		Motor motor = new OpticalAdapter(opMotor);
		motor.drive();
	}
}
运行结果:
适配器模式开始测试...
光力发动机发动...

3.2.3 桥接模式

    桥接模式:将抽象与实现分离,使他们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
    桥接模式的有点:

  • 由于抽象和实现分离,扩展性能强
  • 实现细节对客户透明

    桥接模式缺点:

  • 由于聚合关系建立的抽象层,要求开发者针对抽象进行设计与编程,这增加了系统的理解与设计难度。

桥接模式的结构和实现

可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系

    桥接模式的结构:

  • 抽象化角色:定义抽象类,并包含一个对实现化对象的引用
  • 扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • 实现化角色:定义实现化角色的接口,供扩展抽象化角色的调用
  • 具体实现化角色:给出实现化角色接口的具体实现
分析:女士皮包有很多种,可以按用途分、按皮质分、按品牌分、按颜色分、按大小分等,存在多个维度的变化,所以采用桥接模式来实现女士皮包的选购比较合适
颜色类:Color是一个维度,定义为实现化角色,它有两个具体实现化角色:黄色和红色,通过 getColor() 方法可以选择颜色
包类:是另一个维度,定义为抽象化角色,它有两个扩展抽象化角色:挎包和钱包,它包含了颜色类对象,通过 getName() 方法可以选择相关颜色的挎包和钱包
//实现化角色:color
public interface Color {
	public String getColor();
}

//具体实现化角色:red
public class RedColor implements Color {

	@Override
	public String getColor() {
		// TODO Auto-generated method stub
		String color = "具体实现化角色:red";
		return color;
	}

}

//具体实现化角色:yellow
public class YellowColor implements Color {

	@Override
	public String getColor() {
		// TODO Auto-generated method stub
		String color = "具体实现化角色:yellow";
		return color;
	}

}

public abstract class Bag {
	private Color color;
	
	public abstract String getName();

	public void setColor(Color color) {
		this.color = color;
	}

	public Color getColor() {
		return color;
	}
	
}

//扩展抽象化角色:bag
public class HandBag extends Bag {

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		String bag = "手包";
		return bag;
	}

}

//扩展抽象化角色:bag
public class WalletBag extends Bag {

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		String bag = "钱包";
		return bag;
	}

}

public class Main {
	public static void main(String[] args) {
		Color color = new RedColor();
		Bag bag = new HandBag();
		bag.setColor(color);
		System.out.println(bag.getName());
		System.out.println(bag.getColor().getColor());
	}
}
运行结果:

手包
具体实现化角色:red

3.2.4 装饰模式

    在不改变现有对象的结构下,动态的给对象增加一些职责。例如:房子的装修,相片增加相框等。
    装饰模式的有点:

  • 采用装饰模式来扩展对象的功能比继承更加灵活
  • 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。

    装饰模式的缺点:

  • 增加了许多子类,如果过度使用会使的程序变得更加复杂
装饰模式的结构和实现

    通常情况下,扩展一个类的功能会使用继承的方式来实现。但是继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(装饰对象)来包裹真实对象,并且保持真实对象的结构不改变的前提下,为其提供额外的功能,这就是装饰模式的目标。

    模式的结构:

  • 抽象构件角色:定义一个抽象接口以规范准备接受附加对象的责任。
  • 具体构建角色:实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰角色:继承抽象构件,并包含具体构件的实例可以通过其子类扩展具体构件的功能。
  • 具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加责任。
分析:在《恶魔战士》中,游戏角色“莫莉卡·安斯兰”的原身是一个可爱少女,但当她变身时,会变成头顶及背部延伸出蝙蝠状飞翼的女妖,当然她还可以变为穿着漂亮外衣的少女。这些都可用装饰模式来实现,在本实例中的“莫莉卡”原身有 setImage(String t) 方法决定其显示方式,而其 变身“蝙蝠状女妖”和“着装少女”可以用 setChanger() 方法来改变其外观,原身与变身后的效果用 display() 方法来

抽象构件角色:莫莉卡·安斯兰
具体构件角色:原身
抽象装饰角色:变形
具体装饰角色:女妖
//抽象构件角色:morrigan
public interface Morrigan {
	
	public void display();
}

//具体装饰角色:原身
public class Original implements Morrigan{
	
	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("莫丽卡原身...");
	}

	public Original() {
		System.out.println("莫丽卡初始化原身...");
	}
	
}

// 抽象装饰角色:change
public class Change implements Morrigan{
	
	private Morrigan morrigan;

	@Override
	public void display() {
		// TODO Auto-generated method stub
		morrigan.display();
	}

	public Change(Morrigan morrigan) {
		this.morrigan = morrigan;
	}

	public Morrigan getMorrigan() {
		return morrigan;
	}

	public void setMorrigan(Morrigan morrigan) {
		this.morrigan = morrigan;
	}
	
	
}


public class Girl extends Change{

	public Girl(Morrigan morrigan) {
		super(morrigan);
	}

	@Override
	public void display() {
		// TODO Auto-generated method stub
		super.display();
		setChange();
	}
	
	
	//为具体的构建添加一些责任
	public void setChange(){
		System.out.println("你是一个漂亮的美女");
	}
}

public class Main {
	public static void main(String[] args) {
		Morrigan morrigan = new Original();
		morrigan.display();
		System.out.println("--------开始进行装饰------------");
		
		Change change = new Girl(morrigan);
		change.display();
		
	}
}
运行结果:

莫丽卡初始化原身...
莫丽卡原身...
--------开始进行装饰------------
莫丽卡原身...
你是一个漂亮的美女

    装饰模式应用场景:

  • 当需要给一个现有类添加附加职责,而又不能采用子类生成的方法进行扩充时。例如:该类被隐藏或者该类是终极类或者采用继承的方式会生成大量的子类。
  • 当需要通过对现有的一组基本功能进行排列组合而产生非常多非常多的功能时,采用继承关系很难实现,而采用装饰模式则很好实现
  • 当对象的功能要求可以动态添加、也可以再动态的撤销时。

    装饰模式在java源码中体现在:InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。

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 factory = new HashMap();
	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 children = new ArrayList();
	@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 childs = new ArrayList();	
	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

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

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

海贼王中路飞在打多弗朗明哥的时候,首先是普通状态,然后发怒开启二挡状态,被多弗朗明哥嘲笑速度快,但是力量低,于是开启三挡状态,又被嘲笑力量够了,但是速度差远了,路飞被逼急,于是开启四挡,最终打败了多弗朗明哥。现在我们通过代码实现这样的一个过程。
//抽象角色 - 路飞的状态
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 companys = new ArrayList();
	
	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 customers = new ArrayList();
	@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 list = new ArrayList();
	@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 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 list) {
		super();
		this.list = list;
	}

	public List getList() {
		return list;
	}

	public void setList(List 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 list = new ArrayList();
	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
分析:艺术公司利用“铜”可以设计出铜像,利用“纸”可以画出图画;造币公司利用“铜”可以印出铜币,利用“纸”可以印出纸币。  
对“铜”和“纸”这两种元素,两个公司的处理方法不同,所以该实例用访问者模式来实现比较适合。

抽象访问者: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 解释器模式

解释器模式的定义和特点
解释器模式的定义

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

解释器模式的特点

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

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

    缺点:

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

    

  • 文法:用于描述语言的语法结构的形成规则
  • 句子:句子是语言的基本单位,是语言集中的一个元素,能有“语法”推导而出
  • 语法树:是句子结构的一种表示,表示了句子的推导结果,有利于理解句子的语法结构的层次
 ::= 
 ::= 韶关|广州
 ::= 老人|妇女|儿童
注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。
// 抽象表达式
public abstract class Expression {
	
	public abstract boolean intercepter(String info);
}

public class TerminalExpression extends Expression{
	private Set list = new HashSet();
	@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("山东的儿童");
	}
}
运行结果:
韶关的老人,本次免费乘车
韶关的年轻人,不是会员,赶下去
广州的妇女,本次免费乘车
广州的儿童,本次免费乘车
山东的儿童,不是会员,赶下去


你可能感兴趣的:(java,23种设计模式)