设计模式概述
软件设计模式的产生背景 “设计模式”最初并不是出现在软件设计中,而是被用于建筑领域的设计中。
软件设计模式的概念 软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用,多数人知晓的、经过分类编目的、代码设计经验的总结。
它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。
社戏设计模式的必要性 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
正确使用设计模式具有以下优点:
可以提高程序员的思维能力、编程能力和设计能力
使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期
使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强
设计模式的分类
创建型模式 用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离 ”。GoF(四人组)书中提供了单例、原型、工厂方法、抽象工厂、建造者等5中创建型模式。
结构型模式 用于描述如何将类或对象按某种布局 组成更大的结构,GoF(四人组)书中提供了代理、适配器、桥接、装饰、外观、享元、组合等7中结构模式。
行为型模式 用于描述类或对象之间 怎样相互协作共同完成 单个对象无法单独完成的任务,以及怎样分配职责。GoF(四人组)书中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等11种行为型模式。
UML 统一建模语言(Unified Modeling Language,UML),用来设计软件的可视化建模语言。能表达软件设计中的动态和静态信息。
UML从目标系统的不同角度除法,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等9种图。
类图概述 类图(Class diagram)显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及他们与其他类的关系等。类图不显示暂时性的信息。类图是面显贵对象建模的主要组成部分。
类与类之间关系的表示方式 关联关系 关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。我们先介绍一般关联。
关联又可以分为单向关联,双向关联,自关联。
1、单向关联
在UML类图中单向关联用一个带箭头的实线表示。上图表示每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。
2、双向关联
从上图中我们很容易看出,所谓的双向关联就是双方各自持有对方类型的成员变量。
在UML类图中,双向关联用一个不带箭头的直线表示。上图中在Customer类中维护一个List<Product>,表示一个顾客可以购买多个商品;在Product类中维护一个Customer类型的成员变量表示这个产品被哪个顾客所购买。
3、自关联
自关联在UML类图中用一个带有箭头且指向自身的线表示。上图的意思就是Node类包含类型为Node的成员变量,也就是“自己包含自己”。
聚合关系 聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。
聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分 ,但是成员对象可以脱离整体对象而独立存在 。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。下图所示是大学和教师的关系图:
组合关系 组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。
在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在 。例如,头和嘴的关系,没有了头,嘴也就不存在了。
在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。下图所示是头和嘴的关系图:
依赖关系 依赖关系是一种使用关系,它是对象之间耦合度最弱 的一种关联方式,是临时性的关联 。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法 来完成一些职责。
在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图,司机驾驶汽车:
继承关系 继承关系是对象之间耦合度最大 的一种关系,表示一般与特殊的关系 ,是父类与子类之间的关系 ,是一种继承关系。
在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。例如,Student 类和 Teacher 类都是 Person 类的子类,其类图如下图所示:
实现关系 实现关系是接口与实现类之间的关系 。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交通工具,其类图如图 9 所示。
软件设计原则 开闭原则 程序需要拓展的时候,不能修改原有代码,实现热插拔 效果
对扩展开放,对修改关闭
里氏代换原则 子类可以扩展弗雷的功能,但不能改变父类原有的功能
任何基类可以出现的地方,子类一定可以出现
依赖倒转原则 对抽象进行编程,不要对实现进行编程
抽象不应该依赖细节,细节应该依赖抽象
接口隔离原则 一个类对另一个类的依赖应该建立在最小的接口上,客户端不应该被迫依赖于它不使用的方法
大接口尽量解耦成多个小接口
迪米特法则 又叫最少知识原则
如果两个软件实体无需直接通信,就不应该发生直接的相互调用,可以通过第三方(代理)转发该调用
合成复用原则 尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
设计模式 创建者模式 单例模式 单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
线程安全不安全标准:
对象是否被多次实例化
实例对象是否一致(即对象是否不是完整的对象)
饿汉式-方式1(静态变量方式) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Singleton { private Singleton () {} private static Singleton instance = new Singleton(); public static Singleton getInstance () { return instance; } }
说明:
该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
饿汉式-方式2(静态代码块方式) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Singleton { private Singleton () {} private static Singleton instance; static { instance = new Singleton(); } public static Singleton getInstance () { return instance; } }
说明:
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本上一样,当然该方式也存在内存浪费问题。
懒汉式-方式1(线程不安全 ) 问题:可能多次实例化(线程不安全)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Singleton { private Singleton () {} private static Singleton instance; public static Singleton getInstance () { if (instance == null ) { instance = new Singleton(); } return instance; } }
说明:
从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。
懒汉式-方式2(线程安全 synchronized) 问题:性能不够优
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Singleton { private Singleton () {} private static Singleton instance; public static synchronized Singleton getInstance () { if (instance == null ) { instance = new Singleton(); } return instance; } }
说明:
该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()
方法上添加了**synchronized
**关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。
懒汉式-方式3(双重检查锁 synchronized + volatile ) 再来讨论一下懒汉模式中加锁的问题,对于 getInstance()
方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Singleton { private Singleton () {} private static Singleton instance; public static Singleton getInstance () { if (instance == null ) { synchronized (Singleton.class) { if (instance == null ) { instance = new Singleton(); } } } return instance; } }
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序 操作(导致成员变量未被完全初始化,即只是半个对象 )。
问题:可能对象不完整(即实例对象不一致,属于线程不安全)
要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile
关键字, volatile
关键字可以保证可见性和有序性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Singleton { private Singleton () {} private static volatile Singleton instance; public static Singleton getInstance () { if (instance == null ) { synchronized (Singleton.class) { if (instance == null ) { instance = new Singleton(); } } } return instance; } }
小结:
添加 volatile
关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。
懒汉式-方式4(静态内部类方式) 静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性 。静态属性由于被 static
修饰,保证只被实例化一次,并且严格保证实例化顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Singleton { private Singleton () {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance () { return SingletonHolder.INSTANCE; } }
说明:
第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder
并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
小结:
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
枚举方式 枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
1 2 3 4 5 6 public enum Singleton { INSTANCE; }
说明:
枚举方式属于恶汉式方式。
工厂方法模式 设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。
具体类的设计如下:
在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背开闭原则 。
如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦 。
简单工厂模式 简单工厂不是一种设计模式 ,反而比较像是一种编程习惯。
结构
简单工厂包含如下角色:
抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
具体产品 :实现或者继承抽象产品的子类
具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。
实现
现在使用简单工厂对上面案例进行改进,类图如下:
工厂类代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 public class SimpleCoffeeFactory { public Coffee createCoffee (String type) { Coffee coffee = null ; if ("americano" .equals(type)) { coffee = new AmericanoCoffee(); } else if ("latte" .equals(type)) { coffee = new LatteCoffee(); } return coffee; } }
工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合。
后期如果再加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。
优缺点
优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
扩展
在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是23种设计模式中 的。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 public class SimpleCoffeeFactory { public static Coffee createCoffee (String type) { Coffee coffee = null ; if ("americano" .equals(type)) { coffee = new AmericanoCoffee(); } else if ("latte" .equals(type)) { coffee = new LatteCoffee(); } return coffe; } }
工厂方法模式 针对上例中的缺点(有新产品时,还是需要修改工厂类源码,违反开闭原则),使用工厂方法模式就可以完美的解决,完全遵循开闭原则。
概念:
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象**。工厂方法使一个产品类的实例化延迟到其工厂的子类。
结构
工厂方法模式的主要角色:
抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂 的工厂方法来创建产品。
具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
实现
使用工厂方法模式对上例进行改进,类图如下:
代码如下:
抽象工厂:
1 2 3 4 public interface CoffeeFactory { Coffee createCoffee () ; }
具体工厂:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class LatteCoffeeFactory implements CoffeeFactory { public Coffee createCoffee () { return new LatteCoffee(); } } public class AmericanCoffeeFactory implements CoffeeFactory { public Coffee createCoffee () { return new AmericanCoffee(); } }
咖啡店类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class CoffeeStore { private CoffeeFactory factory; public CoffeeStore (CoffeeFactory factory) { this .factory = factory; } public Coffee orderCoffee (String type) { Coffee coffee = factory.createCoffee(); coffee.addMilk(); coffee.addsugar(); return coffee; } }
从以上的编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码 了,这样就解决了简单工厂模式的缺点。
优缺点
优点:
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则; **
缺点:
每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
抽象工厂模式 前面介绍的工厂方法模式中考虑的是一类产品的生产 ,如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。
这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品
但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。
抽象工厂模式将考虑多等级产品的生产 ,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。
概念
是一种为访问类提供一个创建一组相关或相互依赖对象的接口 ,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
结构
抽象工厂模式的主要角色如下:
抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
实现
现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。所以这个案例可以使用抽象工厂模式实现。类图如下:
代码如下:
抽象工厂:
1 2 3 4 5 6 public interface DessertFactory { Coffee createCoffee () ; Dessert createDessert () ; }
具体工厂:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class AmericanDessertFactory implements DessertFactory { public Coffee createCoffee () { return new AmericanCoffee(); } public Dessert createDessert () { return new MatchaMousse(); } } public class ItalyDessertFactory implements DessertFactory { public Coffee createCoffee () { return new LatteCoffee(); } public Dessert createDessert () { return new Tiramisu(); } }
如果要加同一个产品族 的话,只需要再加一个对应的工厂类 即可,不需要修改其他的类。
优缺点
优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
使用场景
当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
系统中有多个产品族,但每次只使用其中的某一族产品 。如有人只喜欢穿某一个品牌的衣服和鞋。
系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。
模式扩展 简单工厂+配置文件解除耦合
可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。
第一步:定义配置文件
为了演示方便,我们使用properties文件作为配置文件,名称为bean.properties
1 2 american =com.itheima.pattern.factory.config_factory.AmericanCoffee latte =com.itheima.pattern.factory.config_factory.LatteCoffee
第二步:改进工厂类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class CoffeeFactory { private static Map<String,Coffee> map = new HashMap(); static { Properties p = new Properties(); InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties" ); try { p.load(is); Set<Object> keys = p.keySet(); for (Object key : keys) { String className = p.getProperty((String) key); Class clazz = Class.forName(className); Coffee obj = (Coffee) clazz.newInstance(); map.put((String)key,obj); } } catch (Exception e) { e.printStackTrace(); } } public static Coffee createCoffee (String name) { return map.get(name); } }
静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次。
JDK源码解析-Collection.iterator方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Demo { public static void main (String[] args) { List<String> list = new ArrayList<>(); list.add("令狐冲" ); list.add("风清扬" ); list.add("任我行" ); Iterator<String> it = list.iterator(); while (it.hasNext()) { String ele = it.next(); System.out.println(ele); } } }
对上面的代码大家应该很熟,使用迭代器遍历集合,获取集合中的元素。而单列集合获取迭代器的方法就使用到了工厂方法模式。我们看通过类图看看结构:
Collection接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。
另:
1,DateForamt类中的getInstance()方法使用的是工厂模式;
2,Calendar类中的getInstance()方法使用的是工厂模式;
原型模式 概述
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
结构
原型模式包含如下角色:
抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
访问类:使用具体原型类中的 clone() 方法来复制新的对象。
接口类图如下:
实现
原型模式的克隆分为浅克隆 和深克隆 。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
Java中的Object类中提供了 clone()
方法来实现浅克隆。 Cloneable
接口是上面的类图中的抽象原型类,而实现了Cloneable
接口的子实现类就是具体的原型类。代码如下:
Realizetype
(具体的原型类):
1 2 3 4 5 6 7 8 9 10 11 12 public class Realizetype implements Cloneable { public Realizetype () { System.out.println("具体的原型对象创建完成!" ); } @Override protected Realizetype clone () throws CloneNotSupportedException { System.out.println("具体原型复制成功!" ); return (Realizetype) super .clone(); } }
PrototypeTest
(测试访问类):
1 2 3 4 5 6 7 8 public class PrototypeTest { public static void main (String[] args) throws CloneNotSupportedException { Realizetype r1 = new Realizetype(); Realizetype r2 = r1.clone(); System.out.println("对象r1和r2是同一个对象?" + (r1 == r2)); } }
案例
用原型模式生成“三好学生”奖状
同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后在修改奖状上的名字即可。
类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class Citation implements Cloneable { private String name; public void setName (String name) { this .name = name; } public String getName () { return (this .name); } public void show () { System.out.println(name + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!" ); } @Override public Citation clone () throws CloneNotSupportedException { return (Citation) super .clone(); } } public class CitationTest { public static void main (String[] args) throws CloneNotSupportedException { Citation c1 = new Citation(); c1.setName("张三" ); Citation c2 = c1.clone(); c2.setName("李四" ); c1.show(); c2.show(); } }
使用场景
对象的创建非常复杂,可以使用原型模式快捷的创建对象。
性能和安全要求比较高。
扩展:(深克隆:序列化与反序列化)
将上面的“三好学生”奖状的案例中Citation类的name属性修改为Student类型的属性。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class Citation implements Cloneable { private Student stu; public Student getStu () { return stu; } public void setStu (Student stu) { this .stu = stu; } void show () { System.out.println(stu.getName() + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!" ); } @Override public Citation clone () throws CloneNotSupportedException { return (Citation) super .clone(); } } public class Student { private String name; private String address; public Student (String name, String address) { this .name = name; this .address = address; } public Student () { } public String getName () { return name; } public void setName (String name) { this .name = name; } public String getAddress () { return address; } public void setAddress (String address) { this .address = address; } } public class CitationTest { public static void main (String[] args) throws CloneNotSupportedException { Citation c1 = new Citation(); Student stu = new Student("张三" , "西安" ); c1.setStu(stu); Citation c2 = c1.clone(); Student stu1 = c2.getStu(); stu1.setName("李四" ); System.out.println("stu和stu1是同一个对象?" + (stu == stu1)); c1.show(); c2.show(); } }
运行结果为:
说明:
stu对象和stu1对象是同一个对象,就会产生将stu1对象中name属性值改为“李四”,两个Citation(奖状)对象中显示的都是李四。这就是浅克隆的效果,对具体原型类(Citation)中的引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流 。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class CitationTest1 { public static void main (String[] args) throws Exception { Citation c1 = new Citation(); Student stu = new Student("张三" , "西安" ); c1.setStu(stu); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\b.txt" )); oos.writeObject(c1); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\b.txt" )); Citation c2 = (Citation) ois.readObject(); Student stu1 = c2.getStu(); stu1.setName("李四" ); System.out.println("stu和stu1是同一个对象?" + (stu == stu1)); c1.show(); c2.show(); } }
运行结果为:
注意:Citation类和Student类必须实现Serializable接口 ,否则会抛NotSerializableException异常。
建造者模式 概述
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
结构
建造者(Builder)模式包含如下角色:
抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
产品类(Product):要创建的复杂对象。
指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
类图如下:
实例
创建共享单车:
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。
这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者;Director是指挥者。类图如下:
具体的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 public class Bike { private String frame; private String seat; public String getFrame () { return frame; } public void setFrame (String frame) { this .frame = frame; } public String getSeat () { return seat; } public void setSeat (String seat) { this .seat = seat; } } public abstract class Builder { protected Bike mBike = new Bike(); public abstract void buildFrame () ; public abstract void buildSeat () ; public abstract Bike createBike () ; } public class MobikeBuilder extends Builder { @Override public void buildFrame () { mBike.setFrame("铝合金车架" ); } @Override public void buildSeat () { mBike.setSeat("真皮车座" ); } @Override public Bike createBike () { return mBike; } } public class OfoBuilder extends Builder { @Override public void buildFrame () { mBike.setFrame("碳纤维车架" ); } @Override public void buildSeat () { mBike.setSeat("橡胶车座" ); } @Override public Bike createBike () { return mBike; } } public class Director { private Builder mBuilder; public Director (Builder builder) { mBuilder = builder; } public Bike construct () { mBuilder.buildFrame(); mBuilder.buildSeat(); return mBuilder.createBike(); } } public class Client { public static void main (String[] args) { showBike(new OfoBuilder()); showBike(new MobikeBuilder()); } private static void showBike (Builder builder) { Director director = new Director(builder); Bike bike = director.construct(); System.out.println(bike.getFrame()); System.out.println(bike.getSeat()); } }
注意:
上面示例是 Builder模式的常规用法,指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类, 但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public abstract class Builder { protected Bike mBike = new Bike(); public abstract void buildFrame () ; public abstract void buildSeat () ; public abstract Bike createBike () ; public Bike construct () { this .buildFrame(); this .BuildSeat(); return this .createBike(); } }
说明:
这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果construct()
过于复杂,建议还是封装到 Director
中。
优缺点
优点:
建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点:
造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
使用场景
建造者(Builder)模式创建的是复杂对象 ,其产品的各个部分经常变化 ,但将它们组合在一起的算法却相对稳定 ,所以它通常在以下场合使用。
创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
模式扩展
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。
重构前代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public class Phone { private String cpu; private String screen; private String memory; private String mainboard; public Phone (String cpu, String screen, String memory, String mainboard) { this .cpu = cpu; this .screen = screen; this .memory = memory; this .mainboard = mainboard; } public String getCpu () { return cpu; } public void setCpu (String cpu) { this .cpu = cpu; } public String getScreen () { return screen; } public void setScreen (String screen) { this .screen = screen; } public String getMemory () { return memory; } public void setMemory (String memory) { this .memory = memory; } public String getMainboard () { return mainboard; } public void setMainboard (String mainboard) { this .mainboard = mainboard; } @Override public String toString () { return "Phone{" + "cpu='" + cpu + '\'' + ", screen='" + screen + '\'' + ", memory='" + memory + '\'' + ", mainboard='" + mainboard + '\'' + '}' ; } } public class Client { public static void main (String[] args) { Phone phone = new Phone("intel" ,"三星屏幕" ,"金士顿" ,"华硕" ); System.out.println(phone); } }
上面在客户端代码中构建Phone对象,传递了四个参数,如果参数更多呢?代码的可读性及使用的成本就是比较高。
重构后代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public class Phone { private String cpu; private String screen; private String memory; private String mainboard; private Phone (Builder builder) { cpu = builder.cpu; screen = builder.screen; memory = builder.memory; mainboard = builder.mainboard; } public static final class Builder { private String cpu; private String screen; private String memory; private String mainboard; public Builder () {} public Builder cpu (String val) { cpu = val; return this ; } public Builder screen (String val) { screen = val; return this ; } public Builder memory (String val) { memory = val; return this ; } public Builder mainboard (String val) { mainboard = val; return this ; } public Phone build () { return new Phone(this ); } } @Override public String toString () { return "Phone{" + "cpu='" + cpu + '\'' + ", screen='" + screen + '\'' + ", memory='" + memory + '\'' + ", mainboard='" + mainboard + '\'' + '}' ; } } public class Client { public static void main (String[] args) { Phone phone = new Phone.Builder() .cpu("intel" ) .mainboard("华硕" ) .memory("金士顿" ) .screen("三星" ) .build(); System.out.println(phone); } }
重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。
对比 工厂方法模式VS建造者模式
工厂方法模式注重的是整体对象的创建 方式;
而建造者模式注重的是部件构建的过程 ,意在通过一步一步地精确构造创建出一个复杂的对象 。
我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。
抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品 :具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式则是要求按照指定的蓝图建造产品 ,它的主要目的是通过组装零配件而产生一个新产品。
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
结构型模式 结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式 和对象结构型模式 ,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分为以下 7 种:
代理模式
适配器模式
装饰者模式
桥接模式
外观模式
组合模式
享元模式
代理模式 概述
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期 就生成,而动态代理代理类则是在Java运行时 动态生成。动态代理又有JDK代理和CGLib代理两种。
结构
代理(Proxy)模式分为三种角色:
抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
静态代理 我们通过案例来感受一下静态代理。
【例】火车站卖票
如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象 。类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public interface SellTickets { void sell () ; } public class TrainStation implements SellTickets { public void sell () { System.out.println("火车站卖票" ); } } public class ProxyPoint implements SellTickets { private TrainStation station = new TrainStation(); public void sell () { System.out.println("代理点收取一些服务费用" ); station.sell(); } } public class Client { public static void main (String[] args) { ProxyPoint pp = new ProxyPoint(); pp.sell(); } }
从上面代码中可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介 。同时也对sell方法进行了增强(代理点收取一些服务费用)。
JDK动态代理 接下来我们使用动态代理实现上面案例,先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来==获取==代理对象 。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public interface SellTickets { void sell () ; } public class TrainStation implements SellTickets { public void sell () { System.out.println("火车站卖票" ); } } public class ProxyFactory { private TrainStation station = new TrainStation(); public SellTickets getProxyObject () { SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance( station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler() { public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理点收取一些服务费用(JDK动态代理方式)" ); Object result = method.invoke(station, args); return result; } }); return sellTickets; } } public class Client { public static void main (String[] args) { ProxyFactory factory = new ProxyFactory(); SellTickets proxyObject = factory.getProxyObject(); proxyObject.sell(); } }
使用了动态代理,我们思考下面问题:
执行流程 如下:
1. 在测试类中通过代理对象调用sell()方法
2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
CGLIB动态代理 同样是上面的案例,我们再次使用CGLIB代理实现。
如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理 。
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
CGLIB是第三方提供的包,所以需要引入jar包的坐标:
1 2 3 4 5 <dependency > <groupId > cglib</groupId > <artifactId > cglib</artifactId > <version > 2.2.2</version > </dependency >
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class TrainStation { public void sell () { System.out.println("火车站卖票" ); } } public class ProxyFactory implements MethodInterceptor { private TrainStation target = new TrainStation(); public TrainStation getProxyObject () { Enhancer enhancer =new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this ); TrainStation obj = (TrainStation) enhancer.create(); return obj; } public TrainStation intercept (Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)" ); TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args); return result; } } public class Client { public static void main (String[] args) { ProxyFactory factory = new ProxyFactory(); TrainStation proxyObject = factory.getProxyObject(); proxyObject.sell(); } }
对比
jdk代理和CGLIB代理
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理 ,因为CGLib原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理 。
动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
优点:
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
代理对象可以扩展目标对象的功能;
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点:
使用场景
远程(Remote)代理(案例RPC思想 )
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
防火墙(Firewall)代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
适配器模式 概述
如果去欧洲国家去旅游的话,他们的插座如下图最左边,是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑,手机在当地不能直接充电。所以就需要一个插座转换器,转换器第1面插入当地的插座,第2面供我们充电,这样使得我们的插头在当地能使用。生活中这样的例子很多,手机充电器(将220v转换为5v的电压),读卡器等,其实就是使用到了适配器模式。
定义:
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
结构
适配器模式(Adapter)包含以下主要角色:
目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
类适配器模式 实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
【例】读卡器
现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。
类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public interface SDCard { String readSD () ; void writeSD (String msg) ; } public class SDCardImpl implements SDCard { public String readSD () { String msg = "sd card read a msg :hello word SD" ; return msg; } public void writeSD (String msg) { System.out.println("sd card write msg : " + msg); } } public class Computer { public String readSD (SDCard sdCard) { if (sdCard == null ) { throw new NullPointerException("sd card null" ); } return sdCard.readSD(); } } public interface TFCard { String readTF () ; void writeTF (String msg) ; } public class TFCardImpl implements TFCard { public String readTF () { String msg ="tf card read msg : hello word tf card" ; return msg; } public void writeTF (String msg) { System.out.println("tf card write a msg : " + msg); } } public class SDAdapterTF extends TFCardImpl implements SDCard { public String readSD () { System.out.println("adapter read tf card " ); return readTF(); } public void writeSD (String msg) { System.out.println("adapter write tf card" ); writeTF(msg); } } public class Client { public static void main (String[] args) { Computer computer = new Computer(); SDCard sdCard = new SDCardImpl(); System.out.println(computer.readSD(sdCard)); System.out.println("------------" ); SDAdapterTF adapter = new SDAdapterTF(); System.out.println(computer.readSD(adapter)); } }
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。
对象适配器模式 实现方式:对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
【例】读卡器
我们使用对象适配器模式将读卡器的案例进行改写。类图如下:
代码如下:
类适配器模式的代码,我们只需要修改适配器类(SDAdapterTF)和测试类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class SDAdapterTF implements SDCard { private TFCard tfCard; public SDAdapterTF (TFCard tfCard) { this .tfCard = tfCard; } public String readSD () { System.out.println("adapter read tf card " ); return tfCard.readTF(); } public void writeSD (String msg) { System.out.println("adapter write tf card" ); tfCard.writeTF(msg); } } public class Client { public static void main (String[] args) { Computer computer = new Computer(); SDCard sdCard = new SDCardImpl(); System.out.println(computer.readSD(sdCard)); System.out.println("------------" ); TFCard tfCard = new TFCardImpl(); SDAdapterTF adapter = new SDAdapterTF(tfCard); System.out.println(computer.readSD(adapter)); } }
注意:还有一个适配器模式是接口适配器模式 。当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter ,实现所有方法。而此时我们只需要继承该抽象类即可。
应用场景
以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
JDK源码解析 Reader(字符流)、InputStream(字节流)的适配使用的是InputStreamReader。
InputStreamReader继承自java.io包中的Reader,对他中的抽象的未实现的方法给出实现。如:
1 2 3 4 5 6 7 public int read () throws IOException { return sd.read(); } public int read (char cbuf[], int offset, int length) throws IOException { return sd.read(cbuf, offset, length); }
如上代码中的sd(StreamDecoder类对象),在Sun的JDK实现中,实际的方法实现是对sun.nio.cs.StreamDecoder类的同名方法的调用封装。类结构图如下:
从上图可以看出:
InputStreamReader是对同样实现了Reader的StreamDecoder的封装。
StreamDecoder不是Java SE API中的内容,是Sun JDK给出的自身实现。但我们知道他们对构造方法中的字节流类(InputStream)进行封装,并通过该类进行了字节流和字符流之间的解码转换。
结论:
从表层来看,InputStreamReader做了InputStream字节流类到Reader字符流之间的转换。而从如上Sun JDK中的实现类关系结构中可以看出,是StreamDecoder的设计实现在实际上采用了适配器模式。
装饰者模式 概述
我们先来看一个快餐店的例子。
快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。
使用继承的方式存在的问题:
定义:
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
结构
装饰(Decorator)模式中的角色:
抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
案例
我们使用装饰者模式对快餐店案例进行改进,体会装饰者模式的精髓。
类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 public abstract class FastFood { private float price; private String desc; public FastFood () { } public FastFood (float price, String desc) { this .price = price; this .desc = desc; } public void setPrice (float price) { this .price = price; } public float getPrice () { return price; } public String getDesc () { return desc; } public void setDesc (String desc) { this .desc = desc; } public abstract float cost () ; } public class FriedRice extends FastFood { public FriedRice () { super (10 , "炒饭" ); } public float cost () { return getPrice(); } } public class FriedNoodles extends FastFood { public FriedNoodles () { super (12 , "炒面" ); } public float cost () { return getPrice(); } } public abstract class Garnish extends FastFood { private FastFood fastFood; public FastFood getFastFood () { return fastFood; } public void setFastFood (FastFood fastFood) { this .fastFood = fastFood; } public Garnish (FastFood fastFood, float price, String desc) { super (price,desc); this .fastFood = fastFood; } } public class Egg extends Garnish { public Egg (FastFood fastFood) { super (fastFood,1 ,"鸡蛋" ); } public float cost () { return getPrice() + getFastFood().getPrice(); } @Override public String getDesc () { return super .getDesc() + getFastFood().getDesc(); } } public class Bacon extends Garnish { public Bacon (FastFood fastFood) { super (fastFood,2 ,"培根" ); } @Override public float cost () { return getPrice() + getFastFood().getPrice(); } @Override public String getDesc () { return super .getDesc() + getFastFood().getDesc(); } } public class Client { public static void main (String[] args) { FastFood food = new FriedRice(); System.out.println(food.getDesc() + " " + food.cost() + "元" ); System.out.println("========" ); FastFood food1 = new FriedRice(); food1 = new Egg(food1); System.out.println(food1.getDesc() + " " + food1.cost() + "元" ); System.out.println("========" ); FastFood food2 = new FriedNoodles(); food2 = new Bacon(food2); System.out.println(food2.getDesc() + " " + food2.cost() + "元" ); } }
好处:
使用场景
当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况主要有两类:
第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
第二类是因为类定义不能继承(如final类)
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
当对象的功能要求可以动态地添加,也可以再动态地撤销时。
JDK源码解析 IO流中的包装类使用到了装饰者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。
我们以BufferedWriter举例来说明,先看看如何使用BufferedWriter
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Demo { public static void main (String[] args) throws Exception { FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt" ); BufferedWriter bw = new BufferedWriter(fw); bw.write("hello Buffered" ); bw.close(); } }
使用起来感觉确实像是装饰者模式,接下来看它们的结构:
小结:
BufferedWriter使用装饰者模式对Writer子实现类进行了增强,添加了缓冲区,提高了写数据的效率。
代理和装饰者的区别 静态代理和装饰者模式的区别:
相同点:
都要实现与目标类相同的业务接口
在两个类中都要声明目标对象
都可以在不修改目标类的前提下增强目标方法
不同点:
目的不同 装饰者是为了增强目标对象 静态代理是为了保护和隐藏目标对象
获取目标对象构建的地方不同 装饰者是由外界传递进来,可以通过构造方法传递 静态代理是在代理类内部创建,以此来隐藏目标对象
桥接模式 概述
现在有一个需求,需要创建不同的图形,并且每个图形都有可能会有不同的颜色。我们可以利用继承的方式来设计类的关系:
我们可以发现有很多的类,假如我们再增加一个形状或再增加一种颜色,就需要创建更多的类。
试想,在一个有多种可能会变化的维度的系统中,用继承方式会造成类爆炸,扩展起来不灵活。每次在一个维度上新增一个具体实现都要增加多个子类。为了更加灵活的设计系统,我们此时可以考虑使用桥接模式。
定义: 将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
结构
桥接(Bridge)模式包含以下主要角色:
抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。
扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法 。
实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。
具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。
案例
【例】视频播放器
需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式。
类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public interface VideoFile { void decode (String fileName) ; } public class AVIFile implements VideoFile { public void decode (String fileName) { System.out.println("avi视频文件:" + fileName); } } public class REVBBFile implements VideoFile { public void decode (String fileName) { System.out.println("rmvb文件:" + fileName); } } public abstract class OperatingSystemVersion { protected VideoFile videoFile; public OperatingSystemVersion (VideoFile videoFile) { this .videoFile = videoFile; } public abstract void play (String fileName) ; } public class Windows extends OperatingSystem { public Windows (VideoFile videoFile) { super (videoFile); } public void play (String fileName) { videoFile.decode(fileName); } } public class Mac extends OperatingSystemVersion { public Mac (VideoFile videoFile) { super (videoFile); } public void play (String fileName) { videoFile.decode(fileName); } } public class Client { public static void main (String[] args) { OperatingSystem os = new Windows(new AVIFile()); os.play("战狼3" ); } }
好处:
使用场景
当==一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时==。
当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
外观模式 概述
有些人可能炒过股票,但其实大部分人都不太懂,这种没有足够了解证券知识的情况下做股票是很容易亏钱的,刚开始炒股肯定都会想,如果有个懂行的帮帮手就好,其实基金就是个好帮手,支付宝里就有许多的基金,它将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债券、外汇等领域,而基金投资的收益归持有者所有,管理机构收取一定比例的托管管理费用。
定义:
又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观(Facade)模式是“迪米特法则”的典型应用
结构
外观(Facade)模式包含以下主要角色:
外观(Facade)角色:为多个子系统对外提供一个共同的接口。
子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
案例
【例】智能家电控制
小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这些智能家电的开启和关闭。类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 public class Light { public void on () { System.out.println("打开了灯...." ); } public void off () { System.out.println("关闭了灯...." ); } } public class TV { public void on () { System.out.println("打开了电视...." ); } public void off () { System.out.println("关闭了电视...." ); } } public class AirCondition { public void on () { System.out.println("打开了空调...." ); } public void off () { System.out.println("关闭了空调...." ); } } public class SmartAppliancesFacade { private Light light; private TV tv; private AirCondition airCondition; public SmartAppliancesFacade () { light = new Light(); tv = new TV(); airCondition = new AirCondition(); } public void say (String message) { if (message.contains("打开" )) { on(); } else if (message.contains("关闭" )) { off(); } else { System.out.println("我还听不懂你说的!!!" ); } } private void on () { System.out.println("起床了" ); light.on(); tv.on(); airCondition.on(); } private void off () { System.out.println("睡觉了" ); light.off(); tv.off(); airCondition.off(); } } public class Client { public static void main (String[] args) { SmartAppliancesFacade facade = new SmartAppliancesFacade(); facade.say("打开家电" ); facade.say("关闭家电" ); } }
好处:
降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
缺点:
使用场景
对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
当一个复杂系统的子系统很多时,==外观模式可以为系统设计一个简单的接口供外界访问==。
当==客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性==。
源码解析 使用tomcat作为web容器时,接收浏览器发送过来的请求,tomcat会将请求信息封装成ServletRequest对象,如下图①处对象。但是大家想想ServletRequest是一个接口,它还有一个子接口HttpServletRequest,而我们知道该request对象肯定是一个HttpServletRequest对象的子实现类对象,到底是哪个类的对象呢?可以通过输出request对象,我们就会发现是一个名为RequestFacade的类的对象。
RequestFacade类就使用了外观模式。先看结构图:
为什么在此处使用外观模式呢?
定义 RequestFacade 类,分别实现 ServletRequest ,同时定义私有成员变量 Request ,并且方法的实现调用 Request 的实现。然后,将 RequestFacade上转为 ServletRequest 传给 servlet 的 service 方法,这样==即使在 servlet 中被下转为 RequestFacade ,也不能访问私有成员变量对象中的方法。既用了 Request ,又能防止其中方法被不合理的访问==。
组合模式 概述
对于这个图片肯定会非常熟悉,上图我们可以看做是一个文件系统,对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点后,就可以对叶子节点进行相关的操作。可以将这颗树理解成一个大的容器,容器里面包含很多的成员对象,这些成员对象即可是容器对象也可以是叶子对象。但是由于容器对象和叶子对象在功能上面的区别,使得我们在使用的过程中必须要区分容器对象和叶子对象,但是这样就会给客户带来不必要的麻烦,作为客户而已,它始终希望能够一致的对待容器对象和叶子对象。
定义:
又名部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
结构 组合模式主要包含三种角色:
抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
案例实现 【例】软件菜单
如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称。
要实现该案例,我们先画出类图:
代码实现:
不管是菜单还是菜单项,都应该继承自统一的接口,这里姑且将这个统一的接口称为菜单组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public abstract class MenuComponent { protected String name; protected int level; public void add (MenuComponent menuComponent) { throw new UnsupportedOperationException(); } public void remove (MenuComponent menuComponent) { throw new UnsupportedOperationException(); } public MenuComponent getChild (int i) { throw new UnsupportedOperationException(); } public String getName () { return name; } public void print () { throw new UnsupportedOperationException(); } }
这里的MenuComponent定义为抽象类,因为有一些共有的属性和行为要在该类中实现,Menu和MenuItem类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法,举例来说,Menu类可以包含子菜单,因此需要覆盖add()、remove()、getChild()方法,但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常,你也可以根据自己的需要改写默认实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class Menu extends MenuComponent { private List<MenuComponent> menuComponentList; public Menu (String name,int level) { this .level = level; this .name = name; menuComponentList = new ArrayList<MenuComponent>(); } @Override public void add (MenuComponent menuComponent) { menuComponentList.add(menuComponent); } @Override public void remove (MenuComponent menuComponent) { menuComponentList.remove(menuComponent); } @Override public MenuComponent getChild (int i) { return menuComponentList.get(i); } @Override public void print () { for (int i = 1 ; i < level; i++) { System.out.print("--" ); } System.out.println(name); for (MenuComponent menuComponent : menuComponentList) { menuComponent.print(); } } }
Menu类已经实现了除了getName方法的其他所有方法,因为Menu类具有添加菜单,移除菜单和获取子菜单的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MenuItem extends MenuComponent { public MenuItem (String name,int level) { this .name = name; this .level = level; } @Override public void print () { for (int i = 1 ; i < level; i++) { System.out.print("--" ); } System.out.println(name); } }
MenuItem是菜单项,不能再有子菜单,所以添加菜单,移除菜单和获取子菜单的功能并不能实现。
组合模式的分类
在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式。
透明组合模式
透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent
声明了 add
、remove
、getChild
方法,这样做的好处是确保所有的构件类都有相同的接口 。透明组合模式也是组合模式的标准形式。
透明组合模式的缺点是不够安全 ,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)
安全组合模式
在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu
类中声明并实现这些方法。安全组合模式的缺点是不够透明 ,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件 。
优点
组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
使用场景
组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方 。比如:文件目录显示,多级目录呈现等树形结构数据的操作。
享元模式 定义:
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。
结构
享元(Flyweight )模式中存在以下两种状态:
内部状态 ,即不会随着环境的改变而改变的可共享部分。
外部状态 ,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。(实现方式:可以作为方法的形式参数进行传递 )
享元模式的主要有以下角色:
抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
具体享元(Concrete Flyweight)角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
案例实现
【例】俄罗斯方块
下面的图片是众所周知的俄罗斯方块中的一个个方块,如果在俄罗斯方块这个游戏中,每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,下面利用享元模式进行实现。
先来看类图:
代码如下:
俄罗斯方块有不同的形状,我们可以对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为。
1 2 3 4 5 6 7 public abstract class AbstractBox { public abstract String getShape () ; public void display (String color) { System.out.println("方块形状:" + this .getShape() + " 颜色:" + color); } }
接下来就是定义不同的形状了,IBox类、LBox类、OBox类等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class IBox extends AbstractBox { @Override public String getShape () { return "I" ; } } public class LBox extends AbstractBox { @Override public String getShape () { return "L" ; } } public class OBox extends AbstractBox { @Override public String getShape () { return "O" ; } }
提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class BoxFactory { private static HashMap<String, AbstractBox> map; private BoxFactory () { map = new HashMap<String, AbstractBox>(); AbstractBox iBox = new IBox(); AbstractBox lBox = new LBox(); AbstractBox oBox = new OBox(); map.put("I" , iBox); map.put("L" , lBox); map.put("O" , oBox); } public static final BoxFactory getInstance () { return SingletonHolder.INSTANCE; } private static class SingletonHolder { private static final BoxFactory INSTANCE = new BoxFactory(); } public AbstractBox getBox (String key) { return map.get(key); } }
优缺点和使用场景
优点
极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
享元模式中的外部状态相对独立,且不影响内部状态
缺点
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
使用场景:
一个系统有大量相同或者相似的对象,造成内存的大量耗费。
对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
在使用享元模式时需要维护一个存储享元对象的享元池 ,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
JDK源码解析 Integer类使用了享元模式。我们先看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Demo { public static void main (String[] args) { Integer i1 = 127 ; Integer i2 = 127 ; System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2)); Integer i3 = 128 ; Integer i4 = 128 ; System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4)); } }
运行上面代码,结果如下:
为什么第一个输出语句输出的是true,第二个输出语句输出的是false?通过反编译软件进行反编译,代码如下:
1 2 3 4 5 6 7 8 9 10 public class Demo { public static void main (String[] args) { Integer i1 = Integer.valueOf((int )127 ); Integer i2 Integer.valueOf((int )127 ); System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f" ).append((boolean )(i1 == i2)).toString()); Integer i3 = Integer.valueOf((int )128 ); Integer i4 = Integer.valueOf((int )128 ); System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f" ).append((boolean )(i3 == i4)).toString()); } }
上面代码可以看到,直接给Integer类型的变量赋值基本数据类型数据的操作底层使用的是 valueOf()
,所以只需要看该方法即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public final class Integer extends Number implements Comparable <Integer > { public static Integer valueOf (int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } private static class IntegerCache { static final int low = -128 ; static final int high; static final Integer cache[]; static { int h = 127 ; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high" ); if (integerCacheHighPropValue != null ) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127 ); h = Math.min(i, Integer.MAX_VALUE - (-low) -1 ); } catch ( NumberFormatException nfe) { } } high = h; cache = new Integer[(high - low) + 1 ]; int j = low; for (int k = 0 ; k < cache.length; k++) cache[k] = new Integer(j++); assert IntegerCache.high >= 127 ; } private IntegerCache () {} } }
可以看到 Integer
默认先创建并缓存 -128 ~ 127
之间数的 Integer
对象,当调用 valueOf
时如果参数在 -128 ~ 127
之间则计算下标并从缓存中返回,否则创建一个新的 Integer
对象。
行为型模式 行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式分为:
模板方法模式(类)
策略模式(对象)
命令模式(对象)
职责链模式(对象)
状态模式(对象)
观察者模式(对象)
中介者模式(对象)
迭代器模式(对象)
访问者模式(对象)
备忘录模式(对象)
解释器模式(类)
以上 11 种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。
模板方法模式 概述
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知 ,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工 作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
定义:
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤 。
结构
模板方法(Template Method)模式包含以下主要角色:
案例实现 【例】炒菜
炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 public abstract class AbstractClass { public final void cookProcess () { this .pourOil(); this .heatOil(); this .pourVegetable(); this .pourSauce(); this .fry(); } public void pourOil () { System.out.println("倒油" ); } public void heatOil () { System.out.println("热油" ); } public abstract void pourVegetable () ; public abstract void pourSauce () ; public void fry () { System.out.println("炒啊炒啊炒到熟啊" ); } } public class ConcreteClass_BaoCai extends AbstractClass { @Override public void pourVegetable () { System.out.println("下锅的蔬菜是包菜" ); } @Override public void pourSauce () { System.out.println("下锅的酱料是辣椒" ); } } public class ConcreteClass_CaiXin extends AbstractClass { @Override public void pourVegetable () { System.out.println("下锅的蔬菜是菜心" ); } @Override public void pourSauce () { System.out.println("下锅的酱料是蒜蓉" ); } } public class Client { public static void main (String[] args) { ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai(); baoCai.cookProcess(); ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin(); caiXin.cookProcess(); } }
注意:为防止恶意操作,一般模板方法都加上 final 关键词。
优缺点
适用场景
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
JDK源码解析 InputStream
类就使用了模板方法模式。在InputStream
类中定义了多个 read()
方法,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public abstract class InputStream implements Closeable { public abstract int read () throws IOException ; public int read (byte b[]) throws IOException { return read(b, 0 , b.length); } public int read (byte b[], int off, int len) throws IOException { if (b == null ) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0 ) { return 0 ; } int c = read(); if (c == -1 ) { return -1 ; } b[off] = (byte )c; int i = 1 ; try { for (; i < len ; i++) { c = read(); if (c == -1 ) { break ; } b[off + i] = (byte )c; } } catch (IOException ee) { } return i; } }
从上面代码可以看到,无参的 read()
方法是抽象方法,要求子类必须实现。而 read(byte b[])
方法调用了 read(byte b[], int off, int len)
方法,所以在此处重点看的方法是带三个参数的方法。
在该方法中第18行、27行,可以看到调用了无参的抽象的 read()
方法。
总结如下: 在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据呢?由子类实现。
策略模式 概述
先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。
作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进行开发,也可以使用eclipse进行开发,也可以使用其他的一些开发工具。
定义:
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
结构
策略模式的主要角色如下:
抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
案例实现 【例】促销活动
一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:
代码如下:
定义百货公司所有促销活动的共同接口
1 2 3 public interface Strategy { void show () ; }
定义具体策略角色(Concrete Strategy):每个节日具体的促销活动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class StrategyA implements Strategy { public void show () { System.out.println("买一送一" ); } } public class StrategyB implements Strategy { public void show () { System.out.println("满200元减50元" ); } } public class StrategyC implements Strategy { public void show () { System.out.println("满1000元加一元换购任意200元以下商品" ); } }
定义环境角色(Context):用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员
1 2 3 4 5 6 7 8 9 10 11 12 13 public class SalesMan { private Strategy strategy; public SalesMan (Strategy strategy) { this .strategy = strategy; } public void salesManShow () { strategy.show(); } }
优缺点
1,优点:
策略类之间可以自由切换
由于策略类都实现同一个接口,所以使它们之间可以自由切换。
易于扩展
增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
避免使用多重条件选择语句(if else),充分体现面向对象设计思想。
2,缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
使用场景
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
系统中各算法彼此完全独立(而模板方法是已经确定了各步骤的顺序,只是其中一些的步骤具体实现不知道) ,且要求对客户隐藏具体算法的实现细节时。
系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
JDK源码解析 Comparator
中的策略模式。在Arrays类中有一个 sort()
方法,如下:
1 2 3 4 5 6 7 8 9 10 11 12 public class Arrays { public static <T> void sort (T[] a, Comparator<? super T> c) { if (c == null ) { sort(a); } else { if (LegacyMergeSort.userRequested) legacyMergeSort(a, c); else TimSort.sort(a, 0 , a.length, c, null , 0 , 0 ); } } }
Arrays就是一个环境角色类,这个sort方法可以传一个新策略让Arrays根据这个策略来进行排序。就比如下面的测试类。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class demo { public static void main (String[] args) { Integer[] data = {12 , 2 , 3 , 2 , 4 , 5 , 1 }; Arrays.sort(data, new Comparator<Integer>() { public int compare (Integer o1, Integer o2) { return o2 - o1; } }); System.out.println(Arrays.toString(data)); } }
这里我们在调用Arrays的sort方法时,第二个参数传递的是Comparator接口的子实现类对象。所以Comparator充当的是抽象策略角色,而具体的子实现类充当的是具体策略角色。环境角色类(Arrays)应该持有抽象策略的引用来调用。那么,Arrays类的sort方法到底有没有使用Comparator子实现类中的 compare()
方法吗?让我们继续查看TimSort类的 sort()
方法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class TimSort <T > { static <T> void sort (T[] a, int lo, int hi, Comparator<? super T> c, T[] work, int workBase, int workLen) { assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length; int nRemaining = hi - lo; if (nRemaining < 2 ) return ; if (nRemaining < MIN_MERGE) { int initRunLen = countRunAndMakeAscending(a, lo, hi, c); binarySort(a, lo, hi, lo + initRunLen, c); return ; } ... } private static <T> int countRunAndMakeAscending (T[] a, int lo, int hi,Comparator<? super T> c) { assert lo < hi; int runHi = lo + 1 ; if (runHi == hi) return 1 ; if (c.compare(a[runHi++], a[lo]) < 0 ) { while (runHi < hi && c.compare(a[runHi], a[runHi - 1 ]) < 0 ) runHi++; reverseRange(a, lo, runHi); } else { while (runHi < hi && c.compare(a[runHi], a[runHi - 1 ]) >= 0 ) runHi++; } return runHi - lo; } }
上面的代码中最终会跑到 countRunAndMakeAscending()
这个方法中。我们可以看见,只用了compare方法,所以在调用Arrays.sort方法只传具体compare重写方法的类对象就行,这也是Comparator接口中必须要子类实现的一个方法。
命令模式 概述
日常生活中,我们出去吃饭都会遇到下面的场景。
定义:
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
结构
命令模式包含以下主要角色:
抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
案例实现
将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。
服务员: 就是调用者角色,由她来发起命令。
资深大厨: 就是接收者角色,真正命令执行的对象。
订单: 命令中包含订单。
类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 public interface Command { void execute () ; } public class OrderCommand implements Command { private SeniorChef receiver; private Order order; public OrderCommand (SeniorChef receiver, Order order) { this .receiver = receiver; this .order = order; } public void execute () { System.out.println(order.getDiningTable() + "桌的订单:" ); Set<String> keys = order.getFoodDic().keySet(); for (String key : keys) { receiver.makeFood(order.getFoodDic().get(key),key); } try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(order.getDiningTable() + "桌的饭弄好了" ); } } public class Order { private int diningTable; private Map<String, Integer> foodDic = new HashMap<String, Integer>(); public int getDiningTable () { return diningTable; } public void setDiningTable (int diningTable) { this .diningTable = diningTable; } public Map<String, Integer> getFoodDic () { return foodDic; } public void setFoodDic (String name, int num) { foodDic.put(name,num); } } public class SeniorChef { public void makeFood (int num,String foodName) { System.out.println(num + "份" + foodName); } } public class Waitor { private ArrayList<Command> commands; public Waitor () { commands = new ArrayList(); } public void setCommand (Command cmd) { commands.add(cmd); } public void orderUp () { System.out.println("美女服务员:叮咚,大厨,新订单来了......." ); for (int i = 0 ; i < commands.size(); i++) { Command cmd = commands.get(i); if (cmd != null ) { cmd.execute(); } } } } public class Client { public static void main (String[] args) { Order order1 = new Order(); order1.setDiningTable(1 ); order1.getFoodDic().put("西红柿鸡蛋面" ,1 ); order1.getFoodDic().put("小杯可乐" ,2 ); Order order2 = new Order(); order2.setDiningTable(3 ); order2.getFoodDic().put("尖椒肉丝盖饭" ,1 ); order2.getFoodDic().put("小杯雪碧" ,1 ); SeniorChef receiver=new SeniorChef(); OrderCommand cmd1 = new OrderCommand(receiver, order1); OrderCommand cmd2 = new OrderCommand(receiver, order2); Waitor invoker = new Waitor(); invoker.setCommand(cmd1); invoker.setCommand(cmd2); invoker.orderUp(); } }
优缺点 1,优点:
降低系统的耦合度(调用者和接收者) 。命令模式能将调用操作的对象与实现该操作的对象解耦。
增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
可以实现宏命令 。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
2,缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。
系统结构更加复杂。
使用场景
系统需要将请求调用者 和请求接收者 解耦,使得调用者和接收者不直接交互。
系统需要在不同的时间指定请求、将请求排队和执行请求 。
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
JDK源码解析 Runable
是一个典型命令模式,Runnable
担当命令的角色,Thread
充当的是调用者,start
方法就是其执行方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public interface Runnable { public abstract void run () ; } public class Thread implements Runnable { private Runnable target; public synchronized void start () { if (threadStatus != 0 ) throw new IllegalThreadStateException(); group.add(this ); boolean started = false ; try { start0(); started = true ; } finally { try { if (!started) { group.threadStartFailed(this ); } } catch (Throwable ignore) { } } } private native void start0 () ; }
会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者(下面的Receiver
就是)。
解释
如果不定义Receiver
类,就更像==策略模式==
下面的Receiver就是放在了重写的run
方法中,这个方法使得Runnable
类成为了命令角色,从而该方法中如果使用了新的自定义的Receiver
类,那么就是接收者角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class TurnOffThread implements Runnable { private Receiver receiver; public TurnOffThread (Receiver receiver) { this .receiver = receiver; } public void run () { receiver.turnOFF(); } }
1 2 3 4 5 6 7 8 9 10 11 public class Demo { public static void main (String[] args) { Receiver receiver = new Receiver(); TurnOffThread turnOffThread = new TurnOffThread(receiver); Thread thread = new Thread(turnOffThread); thread.start(); } }
责任链模式 概述
在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等。
定义:
又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
结构
职责链模式主要包含以下角色:
抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
案例实现 现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行。
类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 public class LeaveRequest { private String name; private int num; private String content; public LeaveRequest (String name, int num, String content) { this .name = name; this .num = num; this .content = content; } public String getName () { return name; } public int getNum () { return num; } public String getContent () { return content; } } public abstract class Handler { protected final static int NUM_ONE = 1 ; protected final static int NUM_THREE = 3 ; protected final static int NUM_SEVEN = 7 ; private int numStart; private int numEnd; private Handler nextHandler; public Handler (int numStart) { this .numStart = numStart; } public Handler (int numStart, int numEnd) { this .numStart = numStart; this .numEnd = numEnd; } public void setNextHandler (Handler nextHandler) { this .nextHandler = nextHandler; } public final void submit (LeaveRequest leave) { if (0 == this .numStart){ return ; } if (leave.getNum() >= this .numStart){ this .handleLeave(leave); if (null != this .nextHandler && leave.getNum() > numEnd){ this .nextHandler.submit(leave); } else { System.out.println("流程结束" ); } } } protected abstract void handleLeave (LeaveRequest leave) ; } public class GroupLeader extends Handler { public GroupLeader () { super (Handler.NUM_ONE, Handler.NUM_THREE); } @Override protected void handleLeave (LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。" ); System.out.println("小组长审批:同意。" ); } } public class Manager extends Handler { public Manager () { super (Handler.NUM_THREE, Handler.NUM_SEVEN); } @Override protected void handleLeave (LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。" ); System.out.println("部门经理审批:同意。" ); } } public class GeneralManager extends Handler { public GeneralManager () { super (Handler.NUM_SEVEN); } @Override protected void handleLeave (LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。" ); System.out.println("总经理审批:同意。" ); } } public class Client { public static void main (String[] args) { LeaveRequest leave = new LeaveRequest("小花" ,5 ,"身体不适" ); GroupLeader groupLeader = new GroupLeader(); Manager manager = new Manager(); GeneralManager generalManager = new GeneralManager(); groupLeader.setNextHandler(manager); manager.setNextHandler(generalManager); groupLeader.submit(leave); } }
优缺点
优点:
降低了对象之间的耦合度
该模式降低了请求发送者和接收者的耦合度。
增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则。
缺点:
==不能保证每个请求一定被处理==。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
源码解析 在javaWeb应用开发中,FilterChain是职责链(过滤器)模式的典型应用,以下是Filter的模拟实现分析:
状态模式 概述
【例】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。
类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 public interface ILift { public final static int OPENING_STATE = 1 ; public final static int CLOSING_STATE = 2 ; public final static int RUNNING_STATE = 3 ; public final static int STOPPING_STATE = 4 ; public void setState (int state) ; public void open () ; public void close () ; public void run () ; public void stop () ; } public class Lift implements ILift { private int state; @Override public void setState (int state) { this .state = state; } @Override public void close () { switch (this .state) { case OPENING_STATE: System.out.println("电梯关门了。。。" ); this .setState(CLOSING_STATE); break ; case CLOSING_STATE: break ; case RUNNING_STATE: break ; case STOPPING_STATE: break ; } } @Override public void open () { switch (this .state) { case OPENING_STATE: break ; case CLOSING_STATE: System.out.println("电梯门打开了。。。" ); this .setState(OPENING_STATE); break ; case RUNNING_STATE: break ; case STOPPING_STATE: System.out.println("电梯门开了。。。" ); this .setState(OPENING_STATE); break ; } } @Override public void run () { switch (this .state) { case OPENING_STATE: break ; case CLOSING_STATE: System.out.println("电梯开始运行了。。。" ); this .setState(RUNNING_STATE); break ; case RUNNING_STATE: break ; case STOPPING_STATE: System.out.println("电梯开始运行了。。。" ); this .setState(RUNNING_STATE); break ; } } @Override public void stop () { switch (this .state) { case OPENING_STATE: break ; case CLOSING_STATE: System.out.println("电梯停止了。。。" ); this .setState(STOPPING_STATE); break ; case RUNNING_STATE: System.out.println("电梯停止了。。。" ); this .setState(STOPPING_STATE); break ; case STOPPING_STATE: break ; } } } public class Client { public static void main (String[] args) { Lift lift = new Lift(); lift.setState(ILift.STOPPING_STATE); lift.open(); lift.close(); lift.run(); lift.stop(); } }
问题分析:
使用了大量的switch…case这样的判断(if…else也是一样),使程序的可阅读性变差。
扩展性很差。如果新加了断电的状态,我们需要修改上面判断逻辑
定义:
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
结构 状态模式包含以下主要角色。
环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
具体状态(Concrete State)角色:实现抽象状态所对应的行为。
案例实现 对上述电梯的案例使用状态模式进行改进。类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 public abstract class LiftState { protected Context context; public void setContext (Context context) { this .context = context; } public abstract void open () ; public abstract void close () ; public abstract void run () ; public abstract void stop () ; } public class OpenningState extends LiftState { @Override public void open () { System.out.println("电梯门开启..." ); } @Override public void close () { super .context.setLiftState(Context.closeingState); super .context.getLiftState().close(); } @Override public void run () { } @Override public void stop () { } } public class RunningState extends LiftState { @Override public void open () { } @Override public void close () { } @Override public void run () { System.out.println("电梯正在运行..." ); } @Override public void stop () { super .context.setLiftState(Context.stoppingState); super .context.stop(); } } public class StoppingState extends LiftState { @Override public void open () { super .context.setLiftState(Context.openningState); super .context.getLiftState().open(); } @Override public void close () { super .context.setLiftState(Context.closeingState); super .context.getLiftState().close(); } @Override public void run () { super .context.setLiftState(Context.runningState); super .context.getLiftState().run(); } @Override public void stop () { System.out.println("电梯停止了..." ); } } public class ClosingState extends LiftState { @Override public void close () { System.out.println("电梯门关闭..." ); } @Override public void open () { super .context.setLiftState(Context.openningState); super .context.open(); } @Override public void run () { super .context.setLiftState(Context.runningState); super .context.run(); } @Override public void stop () { super .context.setLiftState(Context.stoppingState); super .context.stop(); } } public class Context { public final static OpenningState openningState = new OpenningState(); public final static ClosingState closeingState = new ClosingState(); public final static RunningState runningState = new RunningState(); public final static StoppingState stoppingState = new StoppingState(); private LiftState liftState; public LiftState getLiftState () { return this .liftState; } public void setLiftState (LiftState liftState) { this .liftState = liftState; this .liftState.setContext(this ); } public void open () { this .liftState.open(); } public void close () { this .liftState.close(); } public void run () { this .liftState.run(); } public void stop () { this .liftState.stop(); } } public class Client { public static void main (String[] args) { Context context = new Context(); context.setLiftState(new ClosingState()); context.open(); context.close(); context.run(); context.stop(); } }
优缺点
使用场景
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时 ,就可以考虑使用状态模式。
一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
观察者模式 定义:
又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
结构
在观察者模式中有如下角色:
Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
案例实现 【例】微信公众号
在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。
类图如下:
代码如下:
定义抽象观察者类,里面定义一个更新的方法
1 2 3 public interface Observer { void update (String message) ; }
定义具体观察者类,微信用户是观察者,里面实现了更新的方法
1 2 3 4 5 6 7 8 9 10 11 12 public class WeixinUser implements Observer { private String name; public WeixinUser (String name) { this .name = name; } @Override public void update (String message) { System.out.println(name + "-" + message); } }
定义抽象主题类,提供了attach、detach、notify三个方法
1 2 3 4 5 6 7 8 9 10 11 public interface Subject { public void attach (Observer observer) ; public void detach (Observer observer) ; public void notify (String message) ; }
微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class SubscriptionSubject implements Subject { private List<Observer> weixinUserlist = new ArrayList<Observer>(); @Override public void attach (Observer observer) { weixinUserlist.add(observer); } @Override public void detach (Observer observer) { weixinUserlist.remove(observer); } @Override public void notify (String message) { for (Observer observer : weixinUserlist) { observer.update(message); } } }
客户端程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Client { public static void main (String[] args) { SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject(); WeixinUser user1=new WeixinUser("孙悟空" ); WeixinUser user2=new WeixinUser("猪悟能" ); WeixinUser user3=new WeixinUser("沙悟净" ); mSubscriptionSubject.attach(user1); mSubscriptionSubject.attach(user2); mSubscriptionSubject.attach(user3); mSubscriptionSubject.notify("传智黑马的专栏更新了" ); } }
优缺点
使用场景
对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。
JDK中提供的实现 在 Java 中,通过 java.util.Observable
类和 java.util.Observer
接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1,Observable
类
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
void addObserver(Observer o)
方法:用于将新的观察者对象添加到集合中。
void notifyObservers(Object arg)
方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
void setChange()
方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
2,Observer
接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。
【例】警察抓小偷
警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。代码如下:
小偷是一个被观察者,所以需要继承Observable类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Thief extends Observable { private String name; public Thief (String name) { this .name = name; } public void setName (String name) { this .name = name; } public String getName () { return name; } public void steal () { System.out.println("小偷:我偷东西了,有没有人来抓我!!!" ); super .setChanged(); super .notifyObservers(); } }
警察是一个观察者,所以需要让其实现Observer接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Policemen implements Observer { private String name; public Policemen (String name) { this .name = name; } public void setName (String name) { this .name = name; } public String getName () { return name; } @Override public void update (Observable o, Object arg) { System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!" ); } }
客户端代码
1 2 3 4 5 6 7 8 9 10 11 12 public class Client { public static void main (String[] args) { Thief t = new Thief("隔壁老王" ); Policemen p = new Policemen("小李" ); t.addObserver(p); t.steal(); } }
中介者模式 概述
一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下左图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。
如果引入中介者模式,那么同事类之间的关系将变为星型结构,从下右图中可以看到,任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。
定义:
又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。
结构
中介者模式包含以下主要角色:
抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
案例实现 【例】租房
现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。
类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 public abstract class Mediator { public abstract void constact (String message,Person person) ; } public abstract class Person { protected String name; protected Mediator mediator; public Person (String name,Mediator mediator) { this .name = name; this .mediator = mediator; } } public class HouseOwner extends Person { public HouseOwner (String name, Mediator mediator) { super (name, mediator); } public void constact (String message) { mediator.constact(message, this ); } public void getMessage (String message) { System.out.println("房主" + name +"获取到的信息:" + message); } } public class Tenant extends Person { public Tenant (String name, Mediator mediator) { super (name, mediator); } public void constact (String message) { mediator.constact(message, this ); } public void getMessage (String message) { System.out.println("租房者" + name +"获取到的信息:" + message); } } public class MediatorStructure extends Mediator { private HouseOwner houseOwner; private Tenant tenant; public HouseOwner getHouseOwner () { return houseOwner; } public void setHouseOwner (HouseOwner houseOwner) { this .houseOwner = houseOwner; } public Tenant getTenant () { return tenant; } public void setTenant (Tenant tenant) { this .tenant = tenant; } public void constact (String message, Person person) { if (person == houseOwner) { tenant.getMessage(message); } else { houseOwner.getMessage(message); } } } public class Client { public static void main (String[] args) { MediatorStructure mediator = new MediatorStructure(); HouseOwner houseOwner = new HouseOwner("张三" , mediator); Tenant tenant = new Tenant("李四" , mediator); mediator.setHouseOwner(houseOwner); mediator.setTenant(tenant); tenant.constact("需要租三室的房子" ); houseOwner.constact("我这有三室的房子,你需要租吗?" ); } }
优缺点
一对多关联转变为一对一的关联 没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。
缺点:
当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
使用场景
系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
当想创建一个运行于多个类之间 的对象,又不想生成新的子类时。
迭代器模式 定义:
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
结构
迭代器模式主要包含以下角色:
抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
案例实现 【例】定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现,涉及到的类如下:
代码如下:
定义迭代器接口,声明hasNext、next方法
1 2 3 4 public interface StudentIterator { boolean hasNext () ; Student next () ; }
定义具体的迭代器类,重写所有的抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class StudentIteratorImpl implements StudentIterator { private List<Student> list; private int position = 0 ; public StudentIteratorImpl (List<Student> list) { this .list = list; } @Override public boolean hasNext () { return position < list.size(); } @Override public Student next () { Student currentStudent = list.get(position); position ++; return currentStudent; } }
定义抽象容器类,包含添加元素,删除元素,获取迭代器对象的方法
1 2 3 4 5 6 7 public interface StudentAggregate { void addStudent (Student student) ; void removeStudent (Student student) ; StudentIterator getStudentIterator () ; }
定义具体的容器类,重写所有的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class StudentAggregateImpl implements StudentAggregate { private List<Student> list = new ArrayList<Student>(); @Override public void addStudent (Student student) { this .list.add(student); } @Override public void removeStudent (Student student) { this .list.remove(student); } @Override public StudentIterator getStudentIterator () { return new StudentIteratorImpl(list); } }
优缺点
优点:
它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求 。
缺点:
增加了类的个数,这在一定程度上增加了系统的复杂性。
使用场景
当需要为聚合对象提供多种遍历方式时。
当需要为遍历不同的聚合结构提供一个统一的接口时。
当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
JDK源码解析 迭代器模式在JAVA的很多集合类中被广泛应用,接下来看看JAVA源码中是如何使用迭代器模式的。
1 2 3 4 5 List<String> list = new ArrayList<>(); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }
看完这段代码是不是很熟悉,与我们上面代码基本类似。单列集合都使用到了迭代器,我们以ArrayList举例来说明
List:抽象聚合类
ArrayList:具体的聚合类
Iterator:抽象迭代器
list.iterator():返回的是实现了 Iterator
接口的具体迭代器对象
具体的来看看 ArrayList的代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class ArrayList <E > extends AbstractList <E > implements List <E >, RandomAccess , Cloneable , java .io .Serializable { public Iterator<E> iterator () { return new Itr(); } private class Itr implements Iterator <E > { int cursor; int lastRet = -1 ; int expectedModCount = modCount; Itr() {} public boolean hasNext () { return cursor != size; } public E next () { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this .elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1 ; return (E) elementData[lastRet = i]; } ... }
这部分代码还是比较简单,大致就是在 iterator
方法中返回了一个实例化的 Iterator
对象。Itr是一个内部类,它实现了 Iterator
接口并重写了其中的抽象方法。
注意:
当我们在使用JAVA开发的时候,想使用迭代器模式的话,只要让我们自己定义的容器类实现java.util.Iterable
并实现其中的iterator()方法使其返回一个 java.util.Iterator
的实现类就可以了。
访问者模式 定义:
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
结构 访问者模式包含以下主要角色:
抽象访问者(Visitor)角色:定义了对每一个元素(Element)
访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。
抽象元素(Element)角色:定义了一个接受访问者的方法(accept
),其意义是指,每一个元素都要可以被访问者访问。
具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
对象结构(Object Structure)角色 :定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element
),并且可以迭代这些元素,供访问者访问。
案例实现 【例】给宠物喂食
现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。
访问者角色:给宠物喂食的人
具体访问者角色:主人、其他人
抽象元素角色:动物抽象类
具体元素角色:宠物狗、宠物猫
结构对象角色:主人家
类图如下:
代码如下:
创建抽象访问者接口
1 2 3 4 5 public interface Person { void feed (Cat cat) ; void feed (Dog dog) ; }
创建不同的具体访问者角色(主人和其他人),都需要实现 Person
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Owner implements Person { @Override public void feed (Cat cat) { System.out.println("主人喂食猫" ); } @Override public void feed (Dog dog) { System.out.println("主人喂食狗" ); } } public class Someone implements Person { @Override public void feed (Cat cat) { System.out.println("其他人喂食猫" ); } @Override public void feed (Dog dog) { System.out.println("其他人喂食狗" ); } }
定义抽象节点 – 宠物
1 2 3 public interface Animal { void accept (Person person) ; }
定义实现Animal
接口的 具体节点(元素)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Dog implements Animal { @Override public void accept (Person person) { person.feed(this ); System.out.println("好好吃,汪汪汪!!!" ); } } public class Cat implements Animal { @Override public void accept (Person person) { person.feed(this ); System.out.println("好好吃,喵喵喵!!!" ); } }
定义对象结构,此案例中就是主人的家
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Home { private List<Animal> nodeList = new ArrayList<Animal>(); public void action (Person person) { for (Animal node : nodeList) { node.accept(person); } } public void add (Animal animal) { nodeList.add(animal); } }
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Client { public static void main (String[] args) { Home home = new Home(); home.add(new Dog()); home.add(new Cat()); Owner owner = new Owner(); home.action(owner); Someone someone = new Someone(); home.action(someone); } }
优缺点
优点:
扩展性好 在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
复用性好 通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
分离无关行为 通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
违反了依赖倒置原则
访问者模式依赖了具体类,而没有依赖抽象类。
使用场景
扩展 访问者模式用到了一种双分派 的技术。
分派:
变量被==声明时的类型叫做变量的静态类型==,有些人又把静态类型叫做明显类型;而==变量所引用的对象的真实类型又叫做变量的实际类型==。比如 Map map = new HashMap()
,map变量的静态类型是 Map
,实际类型是 HashMap
。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。
静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。
动态分派(Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。
动态分派:
==通过方法的重写支持动态分派。==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Animal { public void execute () { System.out.println("Animal" ); } } public class Dog extends Animal { @Override public void execute () { System.out.println("dog" ); } } public class Cat extends Animal { @Override public void execute () { System.out.println("cat" ); } } public class Client { public static void main (String[] args) { Animal a = new Dog(); a.execute(); Animal a1 = new Cat(); a1.execute(); } }
上面代码的结果大家应该直接可以说出来,这不就是多态吗!运行执行的是子类中的方法。
Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。
静态分派:
==通过方法重载支持静态分派。==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class Animal {} public class Dog extends Animal {} public class Cat extends Animal {} public class Execute { public void execute (Animal a) { System.out.println("Animal" ); } public void execute (Dog d) { System.out.println("dog" ); } public void execute (Cat c) { System.out.println("cat" ); } } public class Client { public static void main (String[] args) { Animal a = new Animal(); Animal a1 = new Dog(); Animal a2 = new Cat(); Execute exe = new Execute(); exe.execute(a); exe.execute(a1); exe.execute(a2); } }
运行结果:
这个结果可能出乎一些人的意料了,为什么呢?
重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。
双分派:
所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class Animal { public void accept (Execute exe) { exe.execute(this ); } } public class Dog extends Animal { public void accept (Execute exe) { exe.execute(this ); } } public class Cat extends Animal { public void accept (Execute exe) { exe.execute(this ); } } public class Execute { public void execute (Animal a) { System.out.println("animal" ); } public void execute (Dog d) { System.out.println("dog" ); } public void execute (Cat c) { System.out.println("cat" ); } } public class Client { public static void main (String[] args) { Animal a = new Animal(); Animal d = new Dog(); Animal c = new Cat(); Execute exe = new Execute(); a.accept(exe); d.accept(exe); c.accept(exe); } }
在上面代码中,客户端将Execute对象做为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派
,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。
说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改。
运行结果如下:
双分派实现动态绑定的本质,就是在重载方法委派(静态分派)的前面加上了继承体系中覆盖(重写)(动态分派)的环节,由于覆盖是动态的,所以重载就是动态的了。
备忘录模式 概述
备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
定义:
又叫==快照模式==,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。
结构 备忘录模式的主要角色如下:
发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录有两个等效的接口:
窄接口 :管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。
宽接口 :与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
案例实现
【例】游戏挑战BOSS
游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。
要实现上述案例,有两种方式:
“白箱”备忘录模式 备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。类图如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 public class GameRole { private int vit; private int atk; private int def; public void initState () { this .vit = 100 ; this .atk = 100 ; this .def = 100 ; } public void fight () { this .vit = 0 ; this .atk = 0 ; this .def = 0 ; } public RoleStateMemento saveState () { return new RoleStateMemento(vit, atk, def); } public void recoverState (RoleStateMemento roleStateMemento) { this .vit = roleStateMemento.getVit(); this .atk = roleStateMemento.getAtk(); this .def = roleStateMemento.getDef(); } public void stateDisplay () { System.out.println("角色生命力:" + vit); System.out.println("角色攻击力:" + atk); System.out.println("角色防御力:" + def); } public int getVit () { return vit; } public void setVit (int vit) { this .vit = vit; } public int getAtk () { return atk; } public void setAtk (int atk) { this .atk = atk; } public int getDef () { return def; } public void setDef (int def) { this .def = def; } } public class RoleStateMemento { private int vit; private int atk; private int def; public RoleStateMemento (int vit, int atk, int def) { this .vit = vit; this .atk = atk; this .def = def; } public int getVit () { return vit; } public void setVit (int vit) { this .vit = vit; } public int getAtk () { return atk; } public void setAtk (int atk) { this .atk = atk; } public int getDef () { return def; } public void setDef (int def) { this .def = def; } } public class RoleStateCaretaker { private RoleStateMemento roleStateMemento; public RoleStateMemento getRoleStateMemento () { return roleStateMemento; } public void setRoleStateMemento (RoleStateMemento roleStateMemento) { this .roleStateMemento = roleStateMemento; } } public class Client { public static void main (String[] args) { System.out.println("------------大战Boss前------------" ); GameRole gameRole = new GameRole(); gameRole.initState(); gameRole.stateDisplay(); RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker(); roleStateCaretaker.setRoleStateMemento(gameRole.saveState()); System.out.println("------------大战Boss后------------" ); gameRole.fight(); gameRole.stateDisplay(); System.out.println("------------恢复之前状态------------" ); gameRole.recoverState(roleStateCaretaker.getRoleStateMemento()); gameRole.stateDisplay(); } }
分析:白箱备忘录模式是破坏封装性 的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。
“黑箱”备忘录模式 备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类 设计成发起人类 的成员内部类。
将 RoleStateMemento
设为 GameRole
的内部类,从而将 RoleStateMemento
对象封装在 GameRole
里面;在外面提供一个标识接口 Memento
给 RoleStateCaretaker
及其他对象使用。这样 GameRole
类看到的是 RoleStateMemento
所有的接口,而RoleStateCaretaker
及其他对象看到的仅仅是标识接口 Memento
所暴露出来的接口,从而维护了封装型。类图如下:
代码如下:
窄接口Memento
,这是一个标识接口,因此没有定义出任何的方法
1 2 public interface Memento {}
定义发起人类 GameRole
,并在内部定义备忘录内部类 RoleStateMemento
(该内部类设置为私有的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 /游戏角色类 public class GameRole { private int vit; private int atk; private int def; public void initState () { this .vit = 100 ; this .atk = 100 ; this .def = 100 ; } public void fight () { this .vit = 0 ; this .atk = 0 ; this .def = 0 ; } public Memento saveState () { return new RoleStateMemento(vit, atk, def); } public void recoverState (Memento memento) { RoleStateMemento roleStateMemento = (RoleStateMemento) memento; this .vit = roleStateMemento.getVit(); this .atk = roleStateMemento.getAtk(); this .def = roleStateMemento.getDef(); } public void stateDisplay () { System.out.println("角色生命力:" + vit); System.out.println("角色攻击力:" + atk); System.out.println("角色防御力:" + def); } public int getVit () { return vit; } public void setVit (int vit) { this .vit = vit; } public int getAtk () { return atk; } public void setAtk (int atk) { this .atk = atk; } public int getDef () { return def; } public void setDef (int def) { this .def = def; } private class RoleStateMemento implements Memento { private int vit; private int atk; private int def; public RoleStateMemento (int vit, int atk, int def) { this .vit = vit; this .atk = atk; this .def = def; } public int getVit () { return vit; } public void setVit (int vit) { this .vit = vit; } public int getAtk () { return atk; } public void setAtk (int atk) { this .atk = atk; } public int getDef () { return def; } public void setDef (int def) { this .def = def; } } }
负责人角色类 RoleStateCaretaker
能够得到的备忘录对象是以 Memento
为接口的,由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容
1 2 3 4 5 6 7 8 9 10 11 12 public class RoleStateCaretaker { private Memento memento; public Memento getMemento () { return memento; } public void setMemento (Memento memento) { this .memento = memento; } }
客户端测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Client { public static void main (String[] args) { System.out.println("------------大战Boss前------------" ); GameRole gameRole = new GameRole(); gameRole.initState(); gameRole.stateDisplay(); RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker(); roleStateCaretaker.setMemento(gameRole.saveState()); System.out.println("------------大战Boss后------------" ); gameRole.fight(); gameRole.stateDisplay(); System.out.println("------------恢复之前状态------------" ); gameRole.recoverState(roleStateCaretaker.getMemento()); gameRole.stateDisplay(); } }
优缺点
优点:
提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息(指的“黑箱备忘录模式” )。
简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
缺点:
资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
使用场景
解释器模式 概述
如上图,设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static int add (int a,int b) { return a + b; } public static int add (int a,int b,int c) { return a + b + c; } public static int add (Integer ... arr) { int sum = 0 ; for (Integer i : arr) { sum += i; } return sum; }
上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如 1+2+3+4+5、1+2+3-4等等。
显然,现在需要一种翻译识别机器,能够解析由数字以及 + - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。
定义:
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种“语言”。比如加减法运算,规则为:由数值和+-符号组成的合法序列,“1+3-2” 就是这种语言的句子。
解释器就是要解析出来语句的含义。但是如何描述规则呢?
文法(语法)规则:
文法是用于描述语言的语法结构的形式规则。
1 2 3 4 expression ::= value | plus | minus plus ::= expression ‘+’ expression minus ::= expression ‘-’ expression value ::= integer
注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。
上面规则描述为 :
表达式可以是一个值,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数。
抽象语法树:
在计算机科学中,抽象语法树(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
用树形来表示符合文法规则的句子。
结构 解释器模式包含以下主要角色。
抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
案例实现 【例】设计实现加减法的软件
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 public abstract class AbstractExpression { public abstract int interpret (Context context) ; } public class Value extends AbstractExpression { private int value; public Value (int value) { this .value = value; } @Override public int interpret (Context context) { return value; } @Override public String toString () { return new Integer(value).toString(); } } public class Plus extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public Plus (AbstractExpression left, AbstractExpression right) { this .left = left; this .right = right; } @Override public int interpret (Context context) { return left.interpret(context) + right.interpret(context); } @Override public String toString () { return "(" + left.toString() + " + " + right.toString() + ")" ; } } public class Minus extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public Minus (AbstractExpression left, AbstractExpression right) { this .left = left; this .right = right; } @Override public int interpret (Context context) { return left.interpret(context) - right.interpret(context); } @Override public String toString () { return "(" + left.toString() + " - " + right.toString() + ")" ; } } public class Variable extends AbstractExpression { private String name; public Variable (String name) { this .name = name; } @Override public int interpret (Context ctx) { return ctx.getValue(this ); } @Override public String toString () { return name; } } public class Context { private Map<Variable, Integer> map = new HashMap<Variable, Integer>(); public void assign (Variable var , Integer value) { map.put(var , value); } public int getValue (Variable var ) { Integer value = map.get(var ); return value; } } public class Client { public static void main (String[] args) { Context context = new Context(); Variable a = new Variable("a" ); Variable b = new Variable("b" ); Variable c = new Variable("c" ); Variable d = new Variable("d" ); Variable e = new Variable("e" ); context.assign(a, 1 ); context.assign(b, 2 ); context.assign(c, 3 ); context.assign(d, 4 ); context.assign(e, 5 ); AbstractExpression expression = new Minus(new Plus(new Plus(new Plus(a, b), c), d), e); System.out.println(expression + "= " + expression.interpret(context)); } }
优缺点
使用场景
==当语言的文法较为简单,且执行效率不是关键问题时==。
当问题重复出现,且可以用一种简单的语言来进行表达时。
当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。
自定义Spring框架 spring使用回顾 自定义spring框架前,先回顾一下spring框架的使用,从而分析spring的核心,并对核心功能进行模拟。
数据访问层。定义UserDao接口及其子实现类
1 2 3 4 5 6 7 8 9 10 public interface UserDao { public void add () ; } public class UserDaoImpl implements UserDao { public void add () { System.out.println("userDaoImpl ...." ); } }
业务逻辑层。定义UserService接口及其子实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public interface UserService { public void add () ; } public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao (UserDao userDao) { this .userDao = userDao; } public void add () { System.out.println("userServiceImpl ..." ); userDao.add(); } }
定义UserController类,使用main方法模拟controller层
1 2 3 4 5 6 7 8 9 10 public class UserController { public static void main (String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml" ); UserService userService = applicationContext.getBean("userService" , UserService.class); userService.add(); } }
编写配置文件。在类路径下编写一个名为ApplicationContext.xml的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" > <bean id="userService" class ="com.itheima.service.impl.UserServiceImpl" > <property name="userDao" ref="userDao"></property> </bean> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean> </beans>
代码运行结果如下:
通过上面代码及结果可以看出:
userService对象是从applicationContext容器对象获取到的,也就是userService对象交由spring进行管理。
上面结果可以看到调用了UserDao对象中的add方法,也就是说UserDao子实现类对象也交由spring管理了。
UserService中的userDao变量我们并没有进行赋值,但是可以正常使用,说明spring已经将UserDao对象赋值给了userDao变量。
上面三点体现了Spring框架的IOC(Inversion of Control)和DI(Dependency Injection, DI)
spring核心功能结构 Spring大约有20个模块,由1300多个不同的文件构成。这些模块可以分为:
核心容器、AOP和设备支持、数据访问与集成、Web组件、通信报文和集成测试等,下面是 Spring 框架的总体架构图:
核心容器由 beans、core、context 和 expression(Spring Expression Language,SpEL)4个模块组成。
spring-beans和spring-core模块是Spring框架的核心模块,包含了==控制反转(Inversion of Control,IOC)==和==依赖注入(Dependency Injection,DI)==。BeanFactory使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。==BeanFactory属于延时加载==,也就是说在实例化容器对象后并不会自动实例化Bean,只有当Bean被使用时,BeanFactory才会对该 Bean 进行实例化与依赖关系的装配。
spring-context模块构架于核心模块之上,扩展了BeanFactory,为它添加了Bean生命周期控制、框架事件体系及资源加载透明化等功能。此外,该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等,ApplicationContext 是该模块的核心接口,它的超类是 BeanFactory。与BeanFactory不同,它是==非延迟加载==。ApplicationContext实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。
spring-context-support模块是对Spring IoC容器及IoC子容器的扩展支持。
spring-context-indexer模块是Spring的类管理组件和Classpath扫描组件。
spring-expression 模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也可以方便地调用对象方法,以及操作数组、集合等。它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。EL的特性是基于Spring产品的需求而设计的,可以非常方便地同Spring IoC进行交互。
bean概述 Spring 就是面向 Bean
的编程(==BOP,Bean Oriented Programming==),Bean 在 Spring 中处于核心地位。Bean对于Spring的意义就像Object对于OOP的意义一样,Spring中没有Bean也就没有Spring存在的意义。Spring IoC容器通过配置文件或者注解的方式来管理bean对象之间的依赖关系。
spring中bean用于对一个类进行封装。如下面的配置:
1 2 3 4 <bean id ="userService" class ="com.itheima.service.impl.UserServiceImpl" > <property name ="userDao" ref ="userDao" > </property > </bean > <bean id ="userDao" class ="com.itheima.dao.impl.UserDaoImpl" > </bean >
为什么Bean如此重要呢?
spring 将bean对象交由一个叫IOC容器进行管理。
bean对象之间的依赖关系在配置文件中体现,并由spring完成。
Spring IOC相关接口分析 BeanFactory解析 Spring中Bean的创建是典型的工厂模式,这一系列的Bean工厂,即IoC容器 ,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中有许多IoC容器的实现供用户选择,其相互关系如下图所示。
其中,BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,BeanFactory有三个重要的子接口:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是从类图中我们可以发现最终的默认实现类是DefaultListableBeanFactory
,它实现了所有的接口。
那么为何要定义这么多层次的接口呢?
每个接口都有它的使用场合,主要是为了区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。例如,
ListableBeanFactory
接口表示这些Bean可列表化。
HierarchicalBeanFactory
表示这些Bean 是有继承关系的,也就是每个 Bean 可能有父 Bean
AutowireCapableBeanFactory
接口定义Bean的自动装配规则。
这三个接口共同定义了Bean的集合、Bean之间的关系及Bean行为。最基本的IoC容器接口是BeanFactory
,来看一下它的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&" ; Object getBean (String name) throws BeansException ; <T> T getBean (String name, Class<T> requiredType) throws BeansException ; Object getBean (String name, Object... args) throws BeansException ; <T> T getBean (Class<T> requiredType) throws BeansException ; <T> T getBean (Class<T> requiredType, Object... args) throws BeansException ; <T> ObjectProvider<T> getBeanProvider (Class<T> requiredType) ; <T> ObjectProvider<T> getBeanProvider (ResolvableType requiredType) ; boolean containsBean (String name) ; boolean isSingleton (String name) throws NoSuchBeanDefinitionException ; boolean isPrototype (String name) throws NoSuchBeanDefinitionException ; boolean isTypeMatch (String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException ; boolean isTypeMatch (String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException ; @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException; String[] getAliases(String name); }
在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心你的Bean是如何定义及怎样加载的 。正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的。
BeanFactory
有一个很重要的子接口,就是ApplicationContext
接口,该接口主要来规范容器中的bean对象是==非延时加载==,即在创建容器对象的时候就对象bean进行初始化,并存储到一个容器中。
要知道工厂是如何产生对象的,我们需要看具体的IoC容器实现 ,Spring提供了许多IoC容器实现,比如:
ClasspathXmlApplicationContext
: 根据类路径加载xml配置文件,并创建IOC容器对象。
FileSystemXmlApplicationContext
:根据系统路径加载xml配置文件,并创建IOC容器对象。
AnnotationConfigApplicationContext
:加载注解类配置,并创建IOC容器。
BeanDefinition解析 Spring IoC容器管理我们定义的各种Bean对象及其相互关系,而Bean对象在Spring实现中是以BeanDefinition
来描述的,如下面配置文件
1 2 3 4 <bean id ="userDao" class ="com.itheima.dao.impl.UserDaoImpl" > </bean > bean标签还有很多属性: scope、init-method、destory-method等。
其继承体系如下图所示。
BeanDefinitionReader解析 Bean的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。这个解析过程主要通过BeanDefinitionReader来完成,看看Spring中BeanDefinitionReader的类结构图,如下图所示。
看看BeanDefinitionReader接口定义的功能来理解它具体的作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public interface BeanDefinitionReader { BeanDefinitionRegistry getRegistry () ; @Nullable ResourceLoader getResourceLoader () ; @Nullable ClassLoader getBeanClassLoader () ; BeanNameGenerator getBeanNameGenerator () ; int loadBeanDefinitions (Resource resource) throws BeanDefinitionStoreException ; int loadBeanDefinitions (Resource... resources) throws BeanDefinitionStoreException ; int loadBeanDefinitions (String location) throws BeanDefinitionStoreException ; int loadBeanDefinitions (String... locations) throws BeanDefinitionStoreException ; }
BeanDefinitionRegistry解析 BeanDefinitionReader用来解析bean定义,并封装BeanDefinition对象,而我们定义的配置文件中定义了很多bean标签,所以就有一个问题,解析的BeanDefinition对象存储到哪儿?答案就是BeanDefinition
的注册中心,而该注册中心顶层接口就是BeanDefinitionRegistry
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public interface BeanDefinitionRegistry extends AliasRegistry { void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException ; void removeBeanDefinition (String beanName) throws NoSuchBeanDefinitionException ; BeanDefinition getBeanDefinition (String beanName) throws NoSuchBeanDefinitionException ; boolean containsBeanDefinition (String beanName) ; String[] getBeanDefinitionNames(); int getBeanDefinitionCount () ; boolean isBeanNameInUse (String beanName) ; }
继承结构图如下:
从上面类图可以看到BeanDefinitionRegistry接口的子实现类主要有以下几个:
创建容器 ClassPathXmlApplicationContext
对Bean配置资源的载入是从refresh()
方法开始的。refresh()方法是一个模板方法 ,规定了 IoC 容器的启动流程,有些逻辑要交给其子类实现。它对 Bean 配置资源进行载入,==ClassPathXmlApplicationContext
通过调用其父类AbstractApplicationContext
的refresh()
方法启动整个IoC容器对Bean定义的载入过程。(模板方法模式) ==
自定义SpringIOC 现要对下面的配置文件进行解析,并自定义Spring框架的IOC对涉及到的对象进行管理。
1 2 3 4 5 6 7 <?xml version="1.0" encoding="UTF-8"?> <beans > <bean id ="userService" class ="com.itheima.service.impl.UserServiceImpl" > <property name ="userDao" ref ="userDao" > </property > </bean > <bean id ="userDao" class ="com.itheima.dao.impl.UserDaoImpl" > </bean > </beans >
定义bean相关的pojo类 PropertyValue类 用于封装bean的属性,体现到上面的配置文件就是封装bean标签的子标签property标签数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class PropertyValue { private String name; private String ref; private String value; public PropertyValue () { } public PropertyValue (String name, String ref,String value) { this .name = name; this .ref = ref; this .value = value; } public String getName () { return name; } public void setName (String name) { this .name = name; } public String getRef () { return ref; } public void setRef (String ref) { this .ref = ref; } public String getValue () { return value; } public void setValue (String value) { this .value = value; } }
MutablePropertyValues类 一个bean标签可以有多个property子标签,所以再定义一个MutablePropertyValues类,用来存储并管理多个PropertyValue对象 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public class MutablePropertyValues implements Iterable <PropertyValue > { private final List<PropertyValue> propertyValueList; public MutablePropertyValues () { this .propertyValueList = new ArrayList<PropertyValue>(); } public MutablePropertyValues (List<PropertyValue> propertyValueList) { this .propertyValueList = (propertyValueList != null ? propertyValueList : new ArrayList<PropertyValue>()); } public PropertyValue[] getPropertyValues() { return this .propertyValueList.toArray(new PropertyValue[0 ]); } public PropertyValue getPropertyValue (String propertyName) { for (PropertyValue pv : this .propertyValueList) { if (pv.getName().equals(propertyName)) { return pv; } } return null ; } @Override public Iterator<PropertyValue> iterator () { return propertyValueList.iterator(); } public boolean isEmpty () { return this .propertyValueList.isEmpty(); } public MutablePropertyValues addPropertyValue (PropertyValue pv) { for (int i = 0 ; i < this .propertyValueList.size(); i++) { PropertyValue currentPv = this .propertyValueList.get(i); if (currentPv.getName().equals(pv.getName())) { this .propertyValueList.set(i, new PropertyValue(pv.getName(),pv.getRef(), pv.getValue())); return this ; } } this .propertyValueList.add(pv); return this ; } public boolean contains (String propertyName) { return getPropertyValue(propertyName) != null ; } }
BeanDefinition类 BeanDefinition类用来封装bean信息 的,主要包含id(即bean对象的名称)、class(需要交由spring管理的类的全类名)及子标签property数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class BeanDefinition { private String id; private String className; private MutablePropertyValues propertyValues; public BeanDefinition () { propertyValues = new MutablePropertyValues(); } public String getId () { return id; } public void setId (String id) { this .id = id; } public String getClassName () { return className; } public void setClassName (String className) { this .className = className; } public void setPropertyValues (MutablePropertyValues propertyValues) { this .propertyValues = propertyValues; } public MutablePropertyValues getPropertyValues () { return propertyValues; } }
定义注册表相关类 BeanDefinitionRegistry接口 BeanDefinitionRegistry接口定义了注册表的相关操作 ,定义如下功能:
注册BeanDefinition对象到注册表中
从注册表中删除指定名称的BeanDefinition对象
根据名称从注册表中获取BeanDefinition对象
判断注册表中是否包含指定名称的BeanDefinition对象
获取注册表中BeanDefinition对象的个数
获取注册表中所有的BeanDefinition的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public interface BeanDefinitionRegistry { void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) ; void removeBeanDefinition (String beanName) throws Exception ; BeanDefinition getBeanDefinition (String beanName) throws Exception ; boolean containsBeanDefinition (String beanName) ; int getBeanDefinitionCount () ; String[] getBeanDefinitionNames(); }
SimpleBeanDefinitionRegistry类 该类实现了BeanDefinitionRegistry接口,定义了Map集合作为注册表容器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry { private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>(); @Override public void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) { beanDefinitionMap.put(beanName,beanDefinition); } @Override public void removeBeanDefinition (String beanName) throws Exception { beanDefinitionMap.remove(beanName); } @Override public BeanDefinition getBeanDefinition (String beanName) throws Exception { return beanDefinitionMap.get(beanName); } @Override public boolean containsBeanDefinition (String beanName) { return beanDefinitionMap.containsKey(beanName); } @Override public int getBeanDefinitionCount () { return beanDefinitionMap.size(); } @Override public String[] getBeanDefinitionNames() { return beanDefinitionMap.keySet().toArray(new String[1 ]); } }
定义解析器相关类 BeanDefinitionReader接口 BeanDefinitionReader是用来解析配置文件并在注册表中注册bean的信息 。定义了两个规范:
获取注册表的功能,让外界可以通过该对象获取注册表对象。
加载配置文件,并注册bean数据。
1 2 3 4 5 6 7 public interface BeanDefinitionReader { BeanDefinitionRegistry getRegistry () ; void loadBeanDefinitions (String configLocation) throws Exception ; }
XmlBeanDefinitionReader类 XmlBeanDefinitionReader类是专门用来解析xml配置文件 的。该类实现BeanDefinitionReader接口并实现接口中的两个功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class XmlBeanDefinitionReader implements BeanDefinitionReader { private BeanDefinitionRegistry registry; public XmlBeanDefinitionReader () { this .registry = new SimpleBeanDefinitionRegistry(); } @Override public BeanDefinitionRegistry getRegistry () { return registry; } @Override public void loadBeanDefinitions (String configLocation) throws Exception { InputStream is = this .getClass().getClassLoader().getResourceAsStream(configLocation); SAXReader reader = new SAXReader(); Document document = reader.read(is); Element rootElement = document.getRootElement(); parseBean(rootElement); } private void parseBean (Element rootElement) { List<Element> elements = rootElement.elements(); for (Element element : elements) { String id = element.attributeValue("id" ); String className = element.attributeValue("class" ); BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setId(id); beanDefinition.setClassName(className); List<Element> list = element.elements("property" ); MutablePropertyValues mutablePropertyValues = new MutablePropertyValues(); for (Element element1 : list) { String name = element1.attributeValue("name" ); String ref = element1.attributeValue("ref" ); String value = element1.attributeValue("value" ); PropertyValue propertyValue = new PropertyValue(name,ref,value); mutablePropertyValues.addPropertyValue(propertyValue); } beanDefinition.setPropertyValues(mutablePropertyValues); registry.registerBeanDefinition(id,beanDefinition); } } }
IOC容器相关类 BeanFactory接口 在该接口中定义IOC容器的统一规范即获取bean对象 。
1 2 3 4 5 6 public interface BeanFactory { Object getBean (String name) throws Exception ; <T> T getBean (String name, Class<? extends T> clazz) throws Exception ; }
ApplicationContext接口 该接口的所以的子实现类对bean对象的创建都是非延时 的,所以在该接口中定义 refresh()
方法,该方法主要完成以下两个功能:
加载配置文件。
根据注册表中的BeanDefinition对象封装的数据进行bean对象的创建。
1 2 3 4 public interface ApplicationContext extends BeanFactory { void refresh () throws IllegalStateException, Exception ; }
AbstractApplicationContext类
作为ApplicationContext接口的子类 ,所以该类也是非延时加载,所以需要在该类中定义一个Map集合,作为bean对象存储的容器。
声明BeanDefinitionReader类型的变量,用来进行xml配置文件的解析,符合单一职责原则。
BeanDefinitionReader类型的对象创建交由子类实现,因为只有子类明确到底创建BeanDefinitionReader哪儿个子实现类对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public abstract class AbstractApplicationContext implements ApplicationContext { protected BeanDefinitionReader beanDefinitionReader; protected Map<String, Object> singletonObjects = new HashMap<String, Object>(); protected String configLocation; public void refresh () throws IllegalStateException, Exception { beanDefinitionReader.loadBeanDefinitions(configLocation); finishBeanInitialization(); } private void finishBeanInitialization () throws Exception { BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); String[] beanNames = registry.getBeanDefinitionNames(); for (String beanName : beanNames) { BeanDefinition beanDefinition = registry.getBeanDefinition(beanName); getBean(beanName); } } }
注意:该类finishBeanInitialization()方法中调用getBean()方法使用到了模板方法模式。
ClassPathXmlApplicationContext类 该类主要是加载类路径下的配置文件,并进行bean对象的创建,主要完成以下功能:
在构造方法中,创建BeanDefinitionReader对象。
在构造方法中,调用refresh()方法,用于进行配置文件加载、创建bean对象并存储到容器中。
重写父接口中的getBean()方法,并实现依赖注入操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 public class ClassPathXmlApplicationContext extends AbstractApplicationContext { public ClassPathXmlApplicationContext (String configLocation) { this .configLocation = configLocation; beanDefinitionReader = new XmlBeanDefinitionReader(); try { this .refresh(); } catch (Exception e) { } } @Override public Object getBean (String name) throws Exception { Object obj = singletonObjects.get(name); if (obj != null ) { return obj; } BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); BeanDefinition beanDefinition = registry.getBeanDefinition(name); if (beanDefinition == null ) { return null ; } String className = beanDefinition.getClassName(); Class<?> clazz = Class.forName(className); Object beanObj = clazz.newInstance(); MutablePropertyValues propertyValues = beanDefinition.getPropertyValues(); for (PropertyValue propertyValue : propertyValues) { String propertyName = propertyValue.getName(); String value = propertyValue.getValue(); String ref = propertyValue.getRef(); if (ref != null && !"" .equals(ref)) { Object bean = getBean(ref); String methodName = StringUtils.getSetterMethodNameByFieldName(propertyName); Method[] methods = clazz.getMethods(); for (Method method : methods) { if (method.getName().equals(methodName)) { method.invoke(beanObj,bean); } } } if (value != null && !"" .equals(value)) { String methodName = StringUtils.getSetterMethodNameByFieldName(propertyName); Method method = clazz.getMethod(methodName, String.class); method.invoke(beanObj,value); } } singletonObjects.put(name,beanObj); return beanObj; } @Override public <T> T getBean (String name, Class<? extends T> clazz) throws Exception { Object bean = getBean(name); if (bean != null ) { return clazz.cast(bean); } return null ; } }
自定义Spring IOC总结 使用到的设计模式
工厂模式。这个使用工厂模式 + 配置文件的方式。
单例模式。Spring IOC管理的bean对象都是单例的,此处的单例不是通过构造器进行单例的控制的,而是spring框架对每一个bean只创建了一个对象。
模板方法模式。AbstractApplicationContext类中的finishBeanInitialization()方法调用了子类的getBean()方法,因为getBean()的实现和环境息息相关。
迭代器模式。对于MutablePropertyValues类定义使用到了迭代器模式,因为此类存储并管理PropertyValue对象,也属于一个容器,所以给该容器提供一个遍历方式。
==spring框架其实使用到了很多设计模式,如AOP使用到了代理模式,选择JDK代理或者CGLIB代理使用到了策略模式,还有适配器模式,装饰者模式,观察者模式等==。
符合大部分设计原则 整个设计和Spring的设计还是有一定的出入 spring框架底层是很复杂的,进行了很深入的封装,并对外提供了很好的扩展性。而我们自定义SpringIOC有以下几个目的:
了解Spring底层对对象的大体管理机制。
了解设计模式在具体的开发中的使用。
以后学习spring源码,通过该案例的实现,可以降低spring学习的入门成本。