深入理解模式
深入理解单例模式——只有一个实例 https://blog.csdn.net/qq_34337272/article/details/80455972
文章目录
前言
初遇设计模式在上个寒假,当时把每个设计模式过了一遍,对设计模式有了一个最初级的了解。这个学期借了几本设计模式的书籍看,听了老师的设计模式课,对设计模式算是有个更进一步的认识。后面可能会不定期更新一下自己对于设计模式的理解。每个设计模式看似很简单,实则想要在一个完整的系统中应用还是非常非常难的。然后我的水品也非常非常有限,代码量也不是很多,只能通过阅读书籍、思考别人的编码经验以及结合自己的编码过程中遇到的问题来总结。
怎么用->怎么用才好->怎么与其他模式结合使用,我想这是每个开发人员都需要逾越的一道鸿沟。
一 单例模式简介
1.1 定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1.2 为什么要用单例模式呢?
在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
简单来说使用单例模式可以带来下面几个好处:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
1.3 为什么不使用全局变量确保一个类只有一个实例呢?
我们知道全局变量分为静态变量和实例变量,静态变量也可以保证该类的实例只存在一个。
只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。
但是,如果说这个对象非常消耗资源,而且程序某次的执行中一直没用,这样就造成了资源的浪费。利用单例模式的话,我们就可以实现在需要使用时才创建对象,这样就避免了不必要的资源浪费。 不仅仅是因为这个原因,在程序中我们要尽量避免全局变量的使用,大量使用全局变量给程序的调试、维护等带来困难。
二 单例的模式的实现
通常单例模式在Java语言中,有两种构建方式:
- 饿汉方式。指全局的单例实例在类装载时构建
- 懒汉方式。指全局的单例实例在第一次被使用时构建。
不管是那种创建方式,它们通常都存在下面几点相似处:
- 单例类必须要有一个 private 访问级别的构造函数,只有这样,才能确保单例不会在系统中的其他代码内被实例化;
- instance 成员变量和 uniqueInstance 方法必须是 static 的。
2.1 饿汉方式(线程安全)
public class Singleton {
//在静态初始化器中创建单例实例,这段代码保证了线程安全
private static Singleton uniqueInstance = new Singleton();
//Singleton类只有一个构造方法并且是被private修饰的,所以用户无法通过new方法创建该对象实例
private Singleton(){}
public static Singleton getInstance(){
return uniqueInstance;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
所谓 “饿汉方式” 就是说JVM在加载这个类时就马上创建此唯一的单例实例,不管你用不用,先创建了再说,如果一直没有被使用,便浪费了空间,典型的空间换时间,每次调用的时候,就不需要再判断,节省了运行时间。
2.2 懒汉式(非线程安全和synchronized关键字线程安全版本 )
public class Singleton {
private static Singleton uniqueInstance;
private Singleton (){
}
//没有加入synchronized关键字的版本是线程不安全的
public static Singleton getInstance() {
//判断当前单例是否已经存在,若存在则返回,不存在则再建立单例
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
所谓 “ 懒汉式” 就是说单例实例在第一次被使用时构建,而不是在JVM在加载这个类时就马上创建此唯一的单例实例。
但是上面这种方式很明显是线程不安全的,如果多个线程同时访问getInstance()方法时就会出现问题。如果想要保证线程安全,一种比较常见的方式就是在getInstance() 方法前加上synchronized关键字,如下:
public static synchronized Singleton getInstance() {
if (instance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
- 1
- 2
- 3
- 4
- 5
- 6
我们知道synchronized关键字偏重量级锁。虽然在JavaSE1.6之后synchronized关键字进行了主要包括:为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升。
但是在程序中每次使用getInstance() 都要经过synchronized加锁这一层,这难免会增加getInstance()的方法的时间消费,而且还可能会发生阻塞。我们下面介绍到的 双重检查加锁版本 就是为了解决这个问题而存在的。
2.3 懒汉式(双重检查加锁版本)
利用双重检查加锁(double-checked locking),首先检查是否实例已经创建,如果尚未创建,“才”进行同步。这样以来,只有一次同步,这正是我们想要的效果。
public class Singleton {
//volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
//检查实例,如果不存在,就进入同步代码块
if (uniqueInstance == null) {
//只有第一次才彻底执行这里的代码
synchronized(Singleton.class) {
//进入同步代码块后,再检查一次,如果仍是null,才创建实例
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
很明显,这种方式相比于使用synchronized关键字的方法,可以大大减少getInstance() 的时间消费。
我们上面使用到了volatile关键字来保证数据的可见性,关于volatile关键字的内容可以看我的这篇文章:
《Java多线程学习(三)volatile关键字》: https://blog.csdn.net/qq_34337272/article/details/79680771
注意: 双重检查加锁版本不适用于1.4及更早版本的Java。
1.4及更早版本的Java中,许多JVM对于volatile关键字的实现会导致双重检查加锁的失效。
2.4 懒汉式(登记式/静态内部类方式)
静态内部实现的单例是懒加载的且线程安全。
只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.5 饿汉式(枚举方式)
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。 它更简洁,自动支持序列化机制,绝对防止多次实例化 (如果单例类实现了Serializable接口,默认情况下每次反序列化总会创建一个新的实例对象,关于单例与序列化的问题可以查看这一篇文章《单例与序列化的那些事儿》),同时这种方式也是《Effective Java 》以及《Java与模式》的作者推荐的方式。
public enum Singleton {
//定义一个枚举的元素,它就是 Singleton 的一个实例
INSTANCE;
public void doSomeThing() {
System.out.println("枚举方法实现单例");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
使用方法:
public class ESTest {
public static void main(String[] args) {
Singleton singleton = Singleton.INSTANCE;
singleton.doSomeThing();//output:枚举方法实现单例
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
《Effective Java 中文版 第二版》
这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。 —-《Effective Java 中文版 第二版》
《Java与模式》
《Java与模式》中,作者这样写道,使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。
2.6 总结
我们主要介绍到了以下几种方式实现单例模式:
- 饿汉方式(线程安全)
- 懒汉式(非线程安全和synchronized关键字线程安全版本)
- 懒汉式(双重检查加锁版本)
- 懒汉式(登记式/静态内部类方式)
- 饿汉式(枚举方式)
参考:
《Head First 设计模式》
《Effective Java 中文版 第二版》
深入理解工厂模式——由对象工厂生成对象
目录:
一 工厂模式介绍
1.1 工厂模式的定义
先来看一下GOF为工厂模式的定义:
“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.”(在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中进行。)
1.2 工厂模式的分类:
(1)简单工厂(Simple Factory)模式,又称静态工厂方法模式(Static Factory Method Pattern)。
(2)工厂方法(Factory Method)模式,又称多态性工厂(Polymorphic Factory)模式或虚拟构造子(Virtual Constructor)模式;
(3)抽象工厂(Abstract Factory)模式,又称工具箱(Kit 或Toolkit)模式。
1.3 在开源框架中的使用
举两个比较常见的例子(我暂时可以准确想到的,当然还有很多很多):
(1)Spring中通过getBean(“xxx”)获取Bean;
(2) Java消息服务JMS中(下面以消息队列ActiveMQ为例子)
关于消息队列ActiveMQ的使用可以查看:消息队列ActiveMQ的使用详解
// 1、创建一个连接工厂对象,需要指定服务的ip及端口。
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.25.155:61616");
// 2、使用工厂对象创建一个Connection对象。
1.4 为什么要用工厂模式
(1) 解耦 :把对象的创建和使用的过程分开
(2)降低代码重复: 如果创建某个对象的过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。
(3) 降低维护成本 :由于创建过程都由工厂统一管理,所以发生业务逻辑变化,不需要找到所有需要创建对象B的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。
关于工厂模式的作用,Mark一篇文章:https://blog.csdn.net/lovelion/article/details/7523392
二 简单工厂模式
2.1 介绍
严格的说,简单工厂模式并不是23种常用的设计模式之一,它只算工厂模式的一个特殊实现。简单工厂模式在实际中的应用相对于其他2个工厂模式用的还是相对少得多,因为它只适应很多简单的情况。
最重要的是它违背了我们在概述中说的 开放-封闭原则 (虽然可以通过反射的机制来避免,后面我们会介绍到) 。因为每次你要新添加一个功能,都需要在生switch-case 语句(或者if-else 语句)中去修改代码,添加分支条件。
2.2 适用场景
(1)需要创建的对象较少。
(2)客户端不关心对象的创建过程。
2.3 简单工厂模式角色分配:
- 工厂(Factory)角色 :简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
- 抽象产品(Product)角色 :简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
- 具体产品(Concrete Product)角色:简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。
2.4 简单工厂实例
创建一个可以绘制不同形状的绘图工具,可以绘制圆形,正方形,三角形,每个图形都会有一个draw()方法用于绘图.
(1)创建Shape接口
public interface Shape {
void draw();
}
(2)创建实现该接口的具体图形类
圆形
public class Circle implements Shape {
public Circle() {
System.out.println("Circle");
}
@Override
public void draw() {
System.out.println("Draw Circle");
}
}
长方形
public class Rectangle implements Shape {
public Rectangle() {
System.out.println("Rectangle");
}
@Override
public void draw() {
System.out.println("Draw Rectangle");
}
}
正方形
public class Square implements Shape {
public Square() {
System.out.println("Square");
}
@Override
public void draw() {
System.out.println("Draw Square");
}
}
(3)创建工厂类:
public class ShapeFactory {
// 使用 getShape 方法获取形状类型的对象
public static Shape getShape(String shapeType) {
if (shapeType == null) {
return null;
}
if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
return new Rectangle();
} else if (shapeType.equalsIgnoreCase("SQUARE")) {
return new Square();
}
return null;
}
}
(4)测试方法:
public class Test {
public static void main(String[] args) {
// 获取 Circle 的对象,并调用它的 draw 方法
Shape circle = ShapeFactory.getShape("CIRCLE");
circle.draw();
// 获取 Rectangle 的对象,并调用它的 draw 方法
Shape rectangle = ShapeFactory.getShape("RECTANGLE");
rectangle.draw();
// 获取 Square 的对象,并调用它的 draw 方法
Shape square = ShapeFactory.getShape("SQUARE");
square.draw();
}
}
输出结果:
Circle
Draw Circle
Rectangle
Draw Rectangle
Square
Draw Square
这样的实现有个问题,如果我们新增产品类的话,就需要修改工厂类中的getShape()方法,这很明显不符合 开放-封闭原则 。
2.5 使用反射机制改善简单工厂
将工厂类改为下面的形式:
package factory_pattern;
/**
* 利用反射解决简单工厂每次增加新了产品类都要修改产品工厂的弊端
*
* @author Administrator
*
*/
public class ShapeFactory2 {
public static Object getClass(Class<? extends Shape> clazz) {
Object obj = null;
try {
obj = Class.forName(clazz.getName()).newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return obj;
}
}
测试方法:
package factory_pattern;
public class Test2 {
public static void main(String[] args) {
Circle circle = (Circle) ShapeFactory2.getClass(factory_pattern.Circle.class);
circle.draw();
Rectangle rectangle = (Rectangle) ShapeFactory2.getClass(factory_pattern.Rectangle.class);
rectangle.draw();
Square square = (Square) ShapeFactory2.getClass(factory_pattern.Square.class);
square.draw();
}
}
这种方式的虽然符合了 开放-关闭原则 ,但是每一次传入的都是产品类的全部路径,这样比较麻烦。如果需要改善的话可以通过 反射+配置文件 的形式来改善,这种方式使用的也是比较多的。
3 工厂方法模式
3.1 介绍
工厂方法模式应该是在工厂模式家族中是用的最多模式,一般项目中存在最多的就是这个模式。
工厂方法模式是简单工厂的仅一步深化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。也就是说 每个对象都有一个与之对应的工厂 。
3.2 适用场景
- 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
- 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
3.3 工厂方法模式角色分配:
- 抽象工厂(Abstract Factory)角色:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
- 具体工厂(Concrete Factory)角色 :这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建某一种产品对象。
- 抽象产品(AbstractProduct)角色 :工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
- 具体产品(Concrete Product)角色 :这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应
3.4 工厂方法模式实例
上面简单工厂例子中的图形接口以及相关图像实现类不变。我们只需要增加一个工厂接口以及实现这个接口的工厂类即可。
(1)增加一个工厂接口:
public interface Factory {
public Shape getShape();
}
(2)增加相关工厂类:
圆形工厂类
public class CircleFactory implements Factory {
@Override
public Shape getShape() {
// TODO Auto-generated method stub
return new Circle();
}
}
长方形工厂类
public class RectangleFactory implements Factory{
@Override
public Shape getShape() {
// TODO Auto-generated method stub
return new Rectangle();
}
}
圆形工厂类
public class SquareFactory implements Factory{
@Override
public Shape getShape() {
// TODO Auto-generated method stub
return new Square();
}
}
(3)测试:
public class Test {
public static void main(String[] args) {
Factory circlefactory = new CircleFactory();
Shape circle = circlefactory.getShape();
circle.draw();
}
}
输出结果:
Circle
Draw Circle
4 抽象工厂模式
4.1 介绍
在工厂方法模式中,其实我们有一个潜在意识的意识。那就是我们生产的都是同一类产品。抽象工厂模式是工厂方法的仅一步深化,在这个模式中的工厂类不单单可以创建一种产品,而是可以创建一组产品。
抽象工厂应该是比较最难理解的一个工厂模式了。
4.2 适用场景
- 和工厂方法一样客户端不需要知道它所创建的对象的类。
- 需要一组对象共同完成某种功能时,并且可能存在多组对象完成不同功能的情况。(同属于同一个产品族的产品)
- 系统结构稳定,不会频繁的增加对象。(因为一旦增加就需要修改原有代码,不符合开闭原则)
4.3 抽象工厂方法模式角色分配:
- 抽象工厂(AbstractFactory)角色 :是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
- 具体工厂类(ConreteFactory)角色 :这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建某一种产品对象。
- 抽象产品(Abstract Product)角色 :工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
- 具体产品(Concrete Product)角色 :抽象工厂模式所创建的任何产品对象都是某一个具体产品类的实例。在抽象工厂中创建的产品属于同一产品族,这不同于工厂模式中的工厂只创建单一产品,我后面也会详解介绍到。
。
4.4 抽象工厂的工厂和工厂方法中的工厂有什么区别呢?
抽象工厂是生产一整套有产品的(至少要生产两个产品),这些产品必须相互是有关系或有依赖的,而工厂方法中的工厂是生产单一产品的工厂。
下面就是抽象工厂图示:
4.5 抽象工厂模式实例
不知道大家玩过穿越火线或者吃鸡这类游戏了吗,游戏中存在各种枪。我们假设现在存在AK、M4A1两类枪,每一种枪对应一种子弹。我们现在这样考虑生产AK的工厂可以顺便生产AK使用的子弹,生产M4A1的工厂可以顺便生产M4A1使用的子弹。(AK工厂生产AK系列产品包括子弹啊,AK枪的类型啊这些,M4A1工厂同理)
(1)创建相关接口:
枪
public interface Gun {
public void shooting();
}
子弹
public interface Bullet {
public void load();
}
(2)创建接口对应实现类:
AK类
public class AK implements Gun{
@Override
public void shooting() {
System.out.println("shooting with AK");
}
}
M4A1类
public class M4A1 implements Gun {
@Override
public void shooting() {
System.out.println("shooting with M4A1");
}
}
AK子弹类
public class AK_Bullet implements Bullet {
@Override
public void load() {
System.out.println("Load bullets with AK");
}
}
M4A1子弹类
public class M4A1
_Bullet implements Bullet {
@Override
public void load() {
System.out.println("Load bullets with M4A1");
}
}
(3)创建工厂接口
public interface Factory {
public Gun produceGun();
public Bullet produceBullet();
}
(4)创建具体工厂
生产AK和AK子弹的工厂
public class AK_Factory implements Factory{
@Override
public Gun produceGun() {
return new AK();
}
@Override
public Bullet produceBullet() {
return new AK_Bullet();
}
}
生产M4A1和M4A1子弹的工厂
public class M4A1_Factory implements Factory{
@Override
public Gun produceGun() {
return new M4A1();
}
@Override
public Bullet produceBullet() {
return new M4A1_Bullet();
}
}
(5)测试
public class Test {
public static void main(String[] args) {
Factory factory;
Gun gun;
Bullet bullet;
factory =new AK_Factory();
bullet=factory.produceBullet();
bullet.load();
gun=factory.produceGun();
gun.shooting();
}
}
输出结果:
Load bullets with AK shooting with AK
深入理解建造者模式 ——组装复杂的实例
历史优质文章推荐:
目录:
无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分,如汽车,它包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节.
一 建造者模式介绍
1.1 定义
建造者模式(Builder Pattern) 又名生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
1.2 为什么要用建造者模式(优点)?
1) 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
2) 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象 。
3) 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
4) 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合 “开闭原则”
1.3 哪些情况不要用建造者模式(缺点)?
1) 产品之间差异性很大的情况:建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
2) 产品内部变化很复杂的情况: 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
1.4 抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
1.4 模式结构
1.4.1 建造者模式的UML结构图
1.4.2 建造者模式主要包含四个角色
Product(产品角色):一个具体的产品对象。
Builder(抽象建造者):创建一个Product对象的各个部件指定的抽象接口。
ConcreteBuilder(具体建造者):实现抽象接口,构建和装配各个部件。
Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
二 建造者模式分析
1 ) 一个典型的复杂对象其类代码示例如下:
public class Product
{
private String partA; //可以是任意类型
private String partB;
private String partC;
//partA的Getter方法和Setter方法省略
//partB的Getter方法和Setter方法省略
//partC的Getter方法和Setter方法省略
}
2 ) 抽象建造者类中定义了产品的创建方法和返回方法,其典型代码如下:
public abstract class Builder
{
protected Product product=new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
public Product getResult()
{
return product;
}
}
3 ) 具体建造者。实现抽象接口,构建和装配各个部件,实例代码如下:
public class ConcreteBuilder extends Builder{
public void buildPartA(){
...
}
public void buildPartB(){
...
}
public void buildPartC(){
...
}
}
4)指挥者类的代码示例如下:
建造者模式的结构中还引入了一个指挥者类Director,该类的作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。
public class Director
{
private Builder builder;
//1 构造方法的方式注入builder对象
public Director(Builder builder)
{
this.builder=builder;
}
//2 set方法注入builder对象
public void setBuilder(Builder builder)
{
this.builder=builer;
}
public Product construct()
{
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
5 ) 客户端类代码片段:
在客户端代码中,无须关心产品对象的具体组装过程,只需确定具体建造者的类型即可,建造者模式将复杂对象的构建与对象的表现分离开来,这样使得同样的构建过程可以创建出不同的表现。
……
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Product product = director.construct();
……
三 实例:KFC套餐
建造者模式可以用于描述KFC如何创建套餐:套餐是一个复杂对象,它一般包含主食(如汉堡、鸡肉卷等)和饮料(如果汁、可乐等)等组成部分,不同的套餐有不同的组成部分,而KFC的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一份完整的套餐,然后返回给顾客。
1)Product(产品角色)
一个具体的产品对象。
public class Meal {
private String food;
private String drink;
public String getFood() {
return food;
}
public void setFood(String food) {
this.food = food;
}
public String getDrink() {
return drink;
}
public void setDrink(String drink) {
this.drink = drink;
}
}
2)Builder(抽象建造者)
创建一个Product对象的各个部件指定的抽象接口。
public abstract class MealBuilder {
Meal meal = new Meal();
public abstract void buildFood();
public abstract void buildDrink();
public Meal getMeal(){
return meal;
}
}
3) ConcreteBuilder(具体建造者)
实现抽象接口,构建和装配各个部件。
A套餐:
public class MealA extends MealBuilder{
public void buildDrink() {
meal.setDrink("可乐");
}
public void buildFood() {
meal.setFood("薯条");
}
}
B套餐:
public class MealB extends MealBuilder{
public void buildDrink() {
meal.setDrink("柠檬果汁");
}
public void buildFood() {
meal.setFood("鸡翅");
}
}
4)Director(指挥者)
构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象,它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
public class KFCWaiter {
private MealBuilder mealBuilder;
public KFCWaiter(MealBuilder mealBuilder) {
this.mealBuilder = mealBuilder;
}
public Meal construct(){
//准备食物
mealBuilder.buildFood();
//准备饮料
mealBuilder.buildDrink();
//准备完毕,返回一个完整的套餐给客户
return mealBuilder.getMeal();
}
}
5)测试类(客户端类)
public class Test {
public static void main(String[] args) {
//套餐A
MealA a = new MealA();
//准备套餐A的服务员
KFCWaiter waiter = new KFCWaiter(a);
//获得套餐
Meal mealA = waiter.construct();
System.out.print("套餐A的组成部分:");
System.out.println("食物:"+mealA.getFood()+"; "+"饮品:"+mealA.getDrink());
}
}
输出结果:
套餐A的组成部分:食物:薯条; 饮品:可乐
四 总结
本文首先介绍了建造者模型包括建造者模型的定义、为什么要用它、那些情况不适合使用这种模式以及抽象工厂模式和建造者模式的区别的简单分析。
然后通过建造者模式的四个角色的常见示例代码,通过代码层面分析了建造者模式。
最后通过一个KFC套餐实例,介绍了建造者模式在实例中的基本使用手段。
参考:
《设计模式之禅》
《图解设计模式》
一起学设计模式 - 适配器模式
适配器模式(Adapter Pattern)
属于结构型模式
的一种,把一个类的接口变成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作...
<!-- more -->
概述
当你想使用一个已经存在的类,而它的接口不符合你的需求,或者你想创建一个可重用的类(与不兼容接口无关的类),这时候可以考虑使用适配器模式
。同时它也是一种包装模式,它与装饰模式
同样具有包装的功能。
案例
笔者算是小米的忠实用户了,从大学期间起至今都是购买的小米,期间发现小米5
在推出的时候,它会送一个type-c
的转接口给我们,那会type-c
数据线应该还不算普及,这种做法还是蛮好的,在使用转接口后Micro USB
得以重复利用,这样一来即使原装的米5数据线丢了也没关系,只要有type-c
转接口,一样可以用Micro USB
充电/连接电脑
类适配器
1.首先定义M4DataLine
代表是Micro USB
,我们目的就是通过适配器能够用米4数据线连接米5手机
class M4DataLine {
public void connection() {
System.out.println("使用小米4数据线连接...");
}
}
2.定义客户端使用的接口,与业务相关
interface Target {
void connection();
}
class M5DataLine implements Target {
@Override
public void connection() {
System.out.println("使用小米5数据线连接...");
}
}
3.创建适配器类,继承了被适配类,同时实现标准接口
class M5DataLineAdapter extends M4DataLine implements Target {
@Override
public void connection() {
System.out.println("插入 type-c 转接头");
super.connection();
}
}
4.客户端代码,测试
public class AdapterMain {
public static void main(String[] args) {
Target target = new M5DataLine();
target.connection();
Target adapter = new M5DataLineAdapter();
adapter.connection();
}
}
5.结果
使用小米5数据线连接...
插入 type-c 转接头
使用小米4数据线连接...
对象适配器
创建适配器类,实现标准接口,将这个调用委托给实现新接口的对象来处理
class M5DataLineAdapter implements Target {
private Target target;
public M5DataLineAdapter(Target target) {
this.target = target;
}
@Override
public void connection() {
System.out.println("插入 type-c 转接头");
target.connection();
}
}
public class AdapterMain {
public static void main(String[] args) {
// 使用特殊功能类,即适配类
Target adapter = new M5DataLineAdapter(new M5DataLine());
adapter.connection();
}
}
区别
类适配器:对象继承的方式,静态的定义。
对象适配器:依赖于对象的组合,都是采用对象组合的方式,也就是对象适配器实现的方式。
JDK 中的适配器使用
使用适配器模式的类
java.util.Arrays#asList()
java.io.InputStreamReader(InputStream)
java.io.OutputStreamWriter(OutputStream)
Java I/O 库大量使用了适配器模式,如 ByteArrayInputStream
是一个适配器类,它继承了 InputStream
的接口,并且封装了一个 byte 数组。换言之,它将一个 byte 数组的接口适配成 InputStream 流处理器的接口。
在 OutputStream
类型中,所有的原始流处理器都是适配器类。ByteArrayOutputStream
继承了 OutputStream
类型,同时持有一个对 byte 数组的引用。它一个 byte 数组的接口适配成 OutputString 类型的接口,因此也是一个对象形式的适配器模式的应用。
FileOutputStream
继承了 OutputStream
类型,同时持有一个对 FileDiscriptor
对象的引用。这是一个将 FileDiscriptor
接口适配成 OutputStream
接口形式的对象型适配器模式。
Reader
类型的原始流处理器都是适配器模式的应用。StringReader
是一个适配器类,StringReader
类继承了 Reader
类型,持有一个对 String 对象的引用。它将 String 的接口适配成 Reader
类型的接口。
Spring 中使用适配器模式的典型应用
在 Spring 的 AOP 里通过使用的 Advice(通知)来增强被代理类的功能。Spring 实现这一 AOP 功能的原理就使用代理模式(1、JDK 动态代理。2、CGLib 字节码生成技术代理。)对类进行方法级别的切面增强,即,生成被代理类的代理类,并在代理类的方法前,设置拦截器,通过执行拦截器中的内容增强了代理方法的功能,实现的面向切面编程。
Advice(通知)的类型有:BeforeAdvice、AfterReturningAdvice、ThrowSadvice 等。每个类型 Advice(通知)都有对应的拦截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor。Spring 需要将每个 Advice(通知)都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对 Advice 进行转换。
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, Object target) throws Throwable;
}
public interface AdvisorAdapter {
boolean supportsAdvice(Advice advice);
MethodInterceptor getInterceptor(Advisor advisor);
}
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
默认的适配器注册表
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {
private final List<AdvisorAdapter> adapters = new ArrayList<AdvisorAdapter>(3);
public DefaultAdvisorAdapterRegistry() {
// 注册适配器
registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
registerAdvisorAdapter(new AfterReturningAdviceAdapter());
registerAdvisorAdapter(new ThrowsAdviceAdapter());
}
@Override
public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
if (adviceObject instanceof Advisor) {
return (Advisor) adviceObject;
}
if (!(adviceObject instanceof Advice)) {
throw new UnknownAdviceTypeException(adviceObject);
}
Advice advice = (Advice) adviceObject;
if (advice instanceof MethodInterceptor) {
// So well-known it doesn't even need an adapter.
return new DefaultPointcutAdvisor(advice);
}
for (AdvisorAdapter adapter : this.adapters) {
// 检查是否支持,这里调用了适配器的方法
if (adapter.supportsAdvice(advice)) {
return new DefaultPointcutAdvisor(advice);
}
}
throw new UnknownAdviceTypeException(advice);
}
@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List<MethodInterceptor> interceptors = new ArrayList<MethodInterceptor>(3);
Advice advice = advisor.getAdvice();
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor) advice);
}
for (AdvisorAdapter adapter : this.adapters) {
// 检查是否支持,这里调用了适配器的方法
if (adapter.supportsAdvice(advice)) {
interceptors.add(adapter.getInterceptor(advisor));
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[interceptors.size()]);
}
@Override
public void registerAdvisorAdapter(AdvisorAdapter adapter) {
this.adapters.add(adapter);
}
}
总结
优点
- 可以让任何两个没有关联的类一起运行
- 提高了类的复用,想使用现有的类,而此类的接口标准又不符合现有系统的需要。通过适配器模式就可以让这些功能得到更好的复用。
- 增加了类的透明度,客户端只关注结果
- 使用适配器的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
缺点
- 过多使用会导致系统凌乱,追溯困难(内部转发导致,调用A适配成B)
适用场景
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
- 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
小故事
魏文王问名医扁鹊说:“你们家兄弟三人,都精于医术,到底哪一位最好呢?”
扁鹊答:“大哥最好,二哥次之,我最差。”
文王再问:“那么为什么你最出名呢?”
扁鹊答说:“我大哥治病,是治病于病情发作之前。由于一般人不知道他率先能铲除病因,所以他的名气无法传出去,只有我们家的人才知道。我二哥治病,是治病于病情初起之时。一般人以为他只能治轻微的小病,所以他的名气只及于本乡里。而我扁鹊治病,是治病于病情严重之时。一般人都看到我在经脉上穿针管来放血、在皮肤上敷药等大手术,所以以为我的医术高明,名气因此响遍全国。”
比较起来,能防范于未然是最高明的,但往往因防范在前,不会出现恶果,使事物保持了原态,没有“明显”的功绩而被忽略。正如不见防火英雄,只有救火英雄一样。高明者不见得一定名声显赫。
建议尽量使用对象的适配器模式,少用继承。适配器模式也是一种包装模式,它与装饰模式同样具有包装的功能,此外,对象适配器模式
还具有委托的意思。总的来说,适配器模式属于补偿模式,专门用来在系统后期扩展、修改时使用,但要注意不要过度使用适配器模式。
参考文献:《大话设计模式》
IBM developerWorks:适配器模式原理及实例介绍
设计模式笔记16:桥接模式(Bridge Pattern)
一、桥接模式的内容
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。
桥梁模式的用意
【GOF95】在提出桥梁模式的时候指出,桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化"。这句话有三个关键词,也就是抽象化、实现化和脱耦。将类与类之间继承的关系,变为抽象类或者接口与接口之间的关联关系,实现了抽象化与实现化的脱耦。
抽象化
存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,从而把不同的实体当做同样的实体对待【LISKOV94】。
实现化
抽象化给出的具体实现,就是实现化。
脱耦
所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联。
将两个角色之间的继承关系改为聚合关系,就是将它们之间的强关联改换成为弱关联。因此,桥梁模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。这就是桥梁模式的用意。
二、 桥梁模式的结构
桥梁模式【GOF95】是对象的结构模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
下图所示就是一个实现了桥梁模式的示意性系统的结构图。
可以看出,这个系统含有两个等级结构,也就是:
- 由抽象化角色和修正抽象化角色组成的抽象化等级结构。
- 由实现化角色和两个具体实现化角色所组成的实现化等级结构。
桥梁模式所涉及的角色有:
- 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。
- 修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
- 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
- 具体实现化(Concrete Implementor)角色:这个角色给出实现化角色接口的具体实现。
三、桥接模式示例代码
-
public interface Implementor
-
{
-
public void operationImpl();
-
}
-
-
public class ConcreteImplementor implements Implementor
-
{
-
public void operationImpl()
-
{
-
//具体实现
-
}
-
}
-
-
public abstract class Abstraction
-
{
-
protected Implementor impl;
-
-
public void setImpl(Implementor impl)
-
{
-
this.impl=impl;
-
}
-
-
public abstract void operation();
-
}
-
-
public class RefinedAbstraction extends Abstraction
-
{
-
public void operation()
-
{
-
//代码
-
impl.operationImpl();
-
//代码
-
}
-
}
-
public interface Color
-
{
-
void bepaint(String penType,String name);
-
}
-
public abstract class Pen
-
{
-
protected Color color;
-
public void setColor(Color color)
-
{
-
this.color=color;
-
}
-
public abstract void draw(String name);
-
}
-
public class SmallPen extends Pen
-
{
-
public void draw(String name)
-
{
-
String penType="小号毛笔绘制";
-
this.color.bepaint(penType,name);
-
}
-
}
-
public class MiddlePen extends Pen
-
{
-
public void draw(String name)
-
{
-
String penType="中号毛笔绘制";
-
this.color.bepaint(penType,name);
-
}
-
}
-
public class BigPen extends Pen
-
{
-
public void draw(String name)
-
{
-
String penType="大号毛笔绘制";
-
this.color.bepaint(penType,name);
-
}
-
}
-
public class Green implements Color
-
{
-
public void bepaint(String penType,String name)
-
{
-
System.out.println(penType + "绿色的"+ name + ".");
-
}
-
}
-
public class Red implements Color
-
{
-
public void bepaint(String penType,String name)
-
{
-
System.out.println(penType + "红色的"+ name + ".");
-
}
-
}
-
public class White implements Color
-
{
-
public void bepaint(String penType,String name)
-
{
-
System.out.println(penType + "白色的"+ name + ".");
-
}
-
}
-
public class Black implements Color
-
{
-
public void bepaint(String penType,String name)
-
{
-
System.out.println(penType + "黑色的"+ name + ".");
-
}
-
}
-
public class Blue implements Color
-
{
-
public void bepaint(String penType,String name)
-
{
-
System.out.println(penType + "蓝色的"+ name + ".");
-
}
-
}
-
-
<config>
-
<className>Blue</className>
-
<className>SmallPen</className>
-
</config>
-
import javax.xml.parsers.*;
-
import org.w3c.dom.*;
-
import org.xml.sax.SAXException;
-
import java.io.*;
-
public class XMLUtilPen
-
{
-
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
-
public static Object getBean(String args)
-
{
-
try
-
{
-
//创建文档对象
-
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
-
DocumentBuilder builder = dFactory.newDocumentBuilder();
-
Document doc;
-
doc = builder.parse(new File("configPen.xml"));
-
NodeList nl=null;
-
Node classNode=null;
-
String cName=null;
-
nl = doc.getElementsByTagName("className");
-
-
if(args.equals("color"))
-
{
-
//获取包含类名的文本节点
-
classNode=nl.item(0).getFirstChild();
-
-
}
-
else if(args.equals("pen"))
-
{
-
//获取包含类名的文本节点
-
classNode=nl.item(1).getFirstChild();
-
}
-
-
cName=classNode.getNodeValue();
-
//通过类名生成实例对象并将其返回
-
Class c=Class.forName(cName);
-
Object obj=c.newInstance();
-
return obj;
-
}
-
catch(Exception e)
-
{
-
e.printStackTrace();
-
return null;
-
}
-
}
-
}
-
public class Client
-
{
-
public static void main(String a[])
-
{
-
Color color;
-
Pen pen;
-
-
color=(Color)XMLUtilPen.getBean("color");
-
pen=(Pen)XMLUtilPen.getBean("pen");
-
-
pen.setColor(color);
-
pen.draw("鲜花");
-
}
-
}
四、桥接模式分析
- 抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程。
- 实现化:针对抽象化给出的具体实现,就是实现化,抽象化与实现化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化的产物。
- 脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。
五、桥接模式优缺点
桥接模式的优点- 分离抽象接口及其实现部分。
- 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
- 实现细节对客户透明,可以对用户隐藏实现细节。
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
六、桥接模式适用环境
在以下情况下可以使用桥接模式:- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
- 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
Bridge (recognizeable by creational methods taking an instance of different abstract/interface type and returning an implementation of own abstract/interface type which delegates/uses the given instance)
- None comes to mind yet. A fictive example would be
new LinkedHashMap(LinkedHashSet<K>, List<V>)
which returns an unmodifiable linked map which doesn't clone the items, but uses them. Thejava.util.Collections#newSetFromMap()
andsingletonXXX()
methods however comes close.
八、参考资料
- http://blog.csdn.net/rocket5725/article/details/4315251
- 《设计模式》刘伟主编清华大学出版社
大话设计模式—组合模式
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
大话设计模式中程杰老师给出的定义是,组合模式:将对象组合成树形结构以表示”部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
组合模式结构图:
首先,让我们通过过一个简单的实例来简单了解一下组合模式做基本的用法。实例演示了一个组织中员工的层次结构。
创建Employee类:
package com.exercise.composite;
import java.util.ArrayList;
import java.util.List;
public class Employee {
private String name;
private String dept;
private int salary;
private List<Employee> subordinates;//部下
//constructor
public Employee(String name, String dept, int salary,
List<Employee> subordinates) {
super();
this.name = name;
this.dept = dept;
this.salary = salary;
this.subordinates = subordinates;
subordinates = new ArrayList<Employee>();
}
public Employee(String name, String dept, int salary) {
super();
this.name = name;
this.dept = dept;
this.salary = salary;
subordinates = new ArrayList<Employee>();
}
public void add(Employee e){
subordinates.add(e);
}
public void remove(Employee e){
subordinates.remove(e);
}
public List<Employee> getSubordinates(){
return subordinates;
}
public String toString(){
return "Employee :[ Name : " + name
+ ", dept : " + dept + ", salary :"
+ salary + " ]";
}
}
测试类:
package com.exercise.composite;
/**
* 使用Employee来创建和打印员工的层次结构
* @author lmb
*
*/
public class CompositePatternDemo {
public static void main(String[] args) {
Employee CEO = new Employee("John","CEO", 30000);
Employee headSales = new Employee("Robert","Head Sales", 20000);
Employee headMarketing = new Employee("Michel","Head Marketing", 20000);
Employee clerk1 = new Employee("Laura","Marketing", 10000);
Employee clerk2 = new Employee("Bob","Marketing", 10000);
Employee salesExecutive1 = new Employee("Richard","Sales", 10000);
Employee salesExecutive2 = new Employee("Rob","Sales", 10000);
CEO.add(headSales);
CEO.add(headMarketing);
headSales.add(salesExecutive1);
headSales.add(salesExecutive2);
headMarketing.add(clerk1);
headMarketing.add(clerk2);
//打印该组织的所有员工
System.out.println("-------------------公司员工情况----------------------");
System.out.println(CEO);
for (Employee headEmployee : CEO.getSubordinates()) {
//打印CEO的直属一级部下
System.out.println(headEmployee);
for (Employee employee : headEmployee.getSubordinates()) {
//打印CEO的二级部下
System.out.println(employee);
}
}
}
}
运行结果:
使用场景:
需求中是体现部分与整体层次结构的时候,以及当我们希望用户可以忽略组合对象与单个对象的不同,统一的使用组合结构中的所有对象时。简而言之,就是涉及到部分、整体场景时,如树形菜单,文件、文件夹的管理。
主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。
一般在工作中说起部分和总体我们想到最多的大概就是总公司和分公司了,如果我们需要做一套办公管理系统,并且总公司的人力资源部、财务部等的办公挂历功能在所有的分公司也都要有,我们该怎么实现?
功能实现:
抽象公司类或接口
package com.composite;
public abstract class Company {
protected String name;
public Company(String name){
this.name = name;
}
public abstract void add(Company company);//add
public abstract void remove(Company company);//remove
public abstract void display(int depth);//display
public abstract void lineofDuty();//line of duty
}
具体公司类(树枝节点)
package com.composite;
import java.util.ArrayList;
import java.util.List;
public class ConcreteCompany extends Company {
private List<Company> childrenCompany = new ArrayList<Company>();
public ConcreteCompany(String name) {
super(name);
}
@Override
public void add(Company company) {
childrenCompany.add(company);
}
@Override
public void display(int depth) {
System.out.println("第 " + depth + " 层的机构名为: " + name);
for (Company c : childrenCompany) {
c.display(depth + 1);
}
}
@Override
public void lineofDuty() {
for (Company c : childrenCompany) {
c.lineofDuty();
}
}
@Override
public void remove(Company company) {
childrenCompany.remove(company);
}
}
财务部和人力资源部(树叶节点)
package com.composite;
public class HRDepartment extends Company {
public HRDepartment(String name) {
super(name);
}
@Override
public void add(Company company) {
}
@Override
public void display(int depth) {
System.out.println("第 " + depth + " 层的机构名为: " + name);
}
@Override
public void lineofDuty() {
System.out.println(name + " 负责员工招聘管理培训");
}
@Override
public void remove(Company company) {
}
}
package com.composite;
public class FinanceDepartment extends Company {
public FinanceDepartment(String name) {
super(name);
}
@Override
public void add(Company company) {
}
@Override
public void display(int depth) {
System.out.println("第 " + depth + " 层的机构名为: " + name);
}
@Override
public void lineofDuty() {
System.out.println(name + " 负责公司财务收支管理");
}
@Override
public void remove(Company company) {
}
}
测试方法
package com.composite;
public class CompositePatternDemo {
public static void main(String[] args) {
//一个总公司
ConcreteCompany root = new ConcreteCompany("北京总公司");
root.add(new HRDepartment("总公司人力资源部"));
root.add(new FinanceDepartment("总公司财务部"));
//三个子公司
ConcreteCompany com1 = new ConcreteCompany("广州分公司");
com1.add(new HRDepartment("广州分公司人力资源部"));
com1.add(new FinanceDepartment("广州分公司财务部"));
root.add(com1);
ConcreteCompany com2 = new ConcreteCompany("杭州分公司");
com2.add(new HRDepartment("杭州分公司人力资源部"));
com2.add(new FinanceDepartment("杭州分公司财务部"));
root.add(com2);
ConcreteCompany com3 = new ConcreteCompany("深圳分公司");
com3.add(new HRDepartment("深圳分公司人力资源部"));
com3.add(new FinanceDepartment("深圳分公司财务部"));
root.add(com3);
System.out.println("-------公司结构图--------");
root.display(1);
System.out.println("----------各部门职责----------");
root.lineofDuty();
}
}
运行结果:
这样,通过组合模式我们就定义了包含人力资源部和财务部这些基本对象和分公司等组合对象的类层次结构。
基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断的递归下去,在客户代码中,任何用到基本对象的地方都可以使用组合对象了。组合模式让客户可以一致的使用组合结构和单个对象。
应用实例:
1、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作符也可以是操作树、操作符和另一个操作数。
2、在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。
优点:
1、高层模块调用简单。
2、节点自由增加。
缺点:
在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
java模式—装饰者模式
装饰者模式
1、意图: 动态地给一个对象添加一些额外的职责。就增加功能来说, Decorator模式相比生成子类更为灵活。该模式以对客 户端透明的方式扩展对象的功能。
2、适用环境
(1)在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
(2)处理那些可以撤消的职责。
(3)当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的 子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
3、参与者
1.Component(被装饰对象的基类)
定义一个对象接口,可以给这些对象动态地添加职责。
2.ConcreteComponent(具体被装饰对象)
定义一个对象,可以给这个对象添加一些职责。
3.Decorator(装饰者抽象类)
维持一个指向Component实例的引用,并定义一个与Component接口一致的接口。
4.ConcreteDecorator(具体装饰者)
具体的装饰对象,给内部持有的具体被装饰对象,增加具体的职责。
4、类图
5、涉及角色
(1)抽象组件:定义一个抽象接口,来规范准备附加功能的类
(2)具体组件:将要被附加功能的类,实现抽象构件角色接口
(3)抽象装饰者:持有对具体构件角色的引用并定义与抽象构件角色一致的接口
(4)具体装饰:实现抽象装饰者角色,负责对具体构件添加额外功能。
6、代码
Component
public interface Person { void eat(); }
ConcreteComponent
public class Man implements Person { public void eat() { System.out.println("男人在吃"); } }
Decorator
public abstract class Decorator implements Person { protected Person person; public void setPerson(Person person) { this.person = person; } public void eat() { person.eat(); } }
ConcreteDectrator
public class ManDecoratorA extends Decorator { public void eat() { super.eat(); reEat(); System.out.println("ManDecoratorA类"); } public void reEat() { System.out.println("再吃一顿饭"); } } public class ManDecoratorB extends Decorator { public void eat() { super.eat(); System.out.println("==============="); System.out.println("ManDecoratorB类"); } }
Test
public class Test { public static void main(String[] args) { Man man = new Man(); ManDecoratorA md1 = new ManDecoratorA(); ManDecoratorB md2 = new ManDecoratorB(); md1.setPerson(man); md2.setPerson(md1); md2.eat(); } }
7、装饰者模式小结:
OO原则:动态地将责任附加到对象上。想要扩展功能, 装饰者提供有别于继承的另一种选择。
8、要点:
1、继承属于扩展形式之一,但不见得是达到弹性设计的最佳方案。
2、在我们的设计中,应该允许行为可以被扩展,而不须修改现有的代码。
3、组合和委托可用于在运行时动态地加上新的行为。
4、除了继承,装饰者模式也可以让我们扩展行为。
5、装饰者模式意味着一群装饰者类, 这些类用来包装具体组件。
6、装饰者类反映出被装饰的组件类型(实际上,他们具有相同的类型,都经过接口或继承实现)。
7、装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
8、你可以有无数个装饰者包装一个组件。
9、 装饰者一般对组建的客户是透明的,除非客户程序依赖于组件的具体类型。
Java设计模式-装饰者模式
模拟穿衣服场景
我们来看下面一个具体的案例:每个人一天起床之后都要穿衣服(来装饰自己),这是必不可少的,这样问题就来了,穿什么?按照什么顺序穿?
如何用程序方便的模拟这个场景的,代码如下:
/**
* 程序模拟一个人穿衣服的过程
* @author: qhyuan1992
*/
// 抽象接口,用来规范将要被附加一些操作的对象
interface People{
public void wear();
}
// 具体的对象,该对象将被附加一些额外的操作
class Jane implements People{
public void wear() {
System.out.println("今天该穿什么呢?");
}
}
// 装饰者类,持有一个将要被装饰的接口对象的实例
class Decorator implements People{
private People people;
public Decorator(People people) {
this.people = people;
}
public void wear() {
people.wear();
}
}
// 具体的装饰者类,负责给增加附加的操作:穿衬衫
class DecoratorShirt extends Decorator{
public DecoratorShirt(People people) {
super(people);
}
public void wear() {
super.wear();
System.out.println("穿个衬衫");
}
}
// 具体的装饰者类,负责给增加附加的操作:穿西服
class DecoratorSuit extends Decorator{
public DecoratorSuit(People people) {
super(people);
}
public void wear() {
super.wear();
System.out.println("穿个西服");
}
}
// 具体的装饰者类,负责给增加附加的操作:穿T-Shirt
class DecoratorTShirt extends Decorator{
public DecoratorTShirt(People people) {
super(people);
}
public void wear() {
super.wear();
System.out.println("穿个T-Shirt");
}
}
// 具体的装饰者类,负责给增加附加的操作:穿裤子
class DecoratorPants extends Decorator{
public DecoratorPants(People people) {
super(people);
}
public void wear() {
super.wear();
System.out.println("穿裤子");
}
}
// 具体的装饰者类,负责给增加附加的操作:穿鞋子
class DecoratorShoes extends Decorator{
public DecoratorShoes(People people) {
super(people);
}
public void wear() {
super.wear();
System.out.println("鞋子");
}
}
public class DecoratorTest {
public static void main(String[] args) {
People p1 = new DecoratorSuit(new DecoratorShirt(new Jane()));
p1.wear();
System.out.println("--------------");
People p2 = new DecoratorTShirt(new DecoratorPants(new Jane()));
p2.wear();
System.out.println("--------------");
People p3 = new DecoratorTShirt(new DecoratorPants(new DecoratorShoes(new Jane())));
p3.wear();
System.out.println("--------------");
People p4 = new DecoratorShoes(new DecoratorPants(new DecoratorTShirt(new Jane())));
p4.wear();
}
}
打印输出:
今天该穿什么呢?
穿个衬衫
穿个西服
————–
今天该穿什么呢?
穿裤子
穿个T-Shirt
————–
今天该穿什么呢?
鞋子
穿裤子
穿个T-Shirt
————–
今天该穿什么呢?
穿个T-Shirt
穿裤子
鞋子
在上面的穿衣服的例子里面:
- People类:抽象接口,用来规范将要被附加一些操作的对象
- Jane类:具体的对象,该对象将被附加一些额外的操作
- Decorator类: 装饰者类,持有一个将要被装饰的接口对象的实例
- DecoratorShirt类:具体的装饰者类,负责给增加附加的操作:穿衬衫
- DecoratorSuit类:具体的装饰者类,负责给增加附加的操作:穿西服
- DecoratorTShirt类:具体的装饰者类,负责给增加附加的操作:穿T-Shirt
- DecoratorPants类:具体的装饰者类,负责给增加附加的操作:穿裤子
- DecoratorShoes类:具体的装饰者类,负责给增加附加的操作:穿鞋子
在测试代码里面一连串的new和java I/O流里面常用的方式几乎一样,没错,这就是装饰者模式。
装饰者模式
装饰者模式介绍
装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。就增加功能来说,Decorator模式比生成子类更为灵活。
装饰者模式的类图结构如下所示
装饰者模式中类或接口的作用:
- 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
- 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。
- 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
- 具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。
下面是模拟穿衣服程序的类图
为什么使用装饰者而不使用继承?
我们知道要实现上面的穿衣服的例子,其实不一定非要使用装饰者模式,使用集成一样可以解决,装饰者和集成一样都是要扩展对象的功能。那么为什么选用装饰者模式呢?
装饰模式可以提供比继承更多的灵活性。装饰模式允许动态增加或删除一个装饰的功能。继承需要在此之前就要确定好对应的类。
我们知道在上面模拟的例子里面一个人穿什么衣服,按照什么顺序来穿都是不定的,换句话说有很多种排列组合的方式,如果使用继承要考虑这么多情况就需要很多的类,然而使用装饰者就可以很方便的在使用的时候动态的创造组合不同的行为。
装饰者模式的使用
简化装饰者模式
只有一个具体构件角色
如果具体构件(ConcreteComponent)角色只有一个,那么可以去掉抽象的Component接口,把Decorator作为ConcreteComponent的子类。
代码:
class Jane{
public void wear(){
System.out.println("今天该穿什么呢?");
}
}
class Decorator extends Jane{
private Jane Jane;
public Decorator(Jane Jane) {
this.Jane = Jane;
}
public void wear() {
Jane.wear();
}
}
class DecoratorShirt extends Decorator{
public DecoratorShirt(Jane Jane) {
super(Jane);
}
public void wear() {
super.wear();
System.out.println("穿个衬衫");
}
}
class DecoratorSuit extends Decorator{
public DecoratorSuit(Jane Jane) {
super(Jane);
}
public void wear() {
super.wear();
System.out.println("穿个西服");
}
}
class DecoratorTShirt extends Decorator{
public DecoratorTShirt(Jane Jane) {
super(Jane);
}
public void wear() {
super.wear();
System.out.println("穿个T-Shirt");
}
}
class DecoratorPants extends Decorator{
public DecoratorPants(Jane Jane) {
super(Jane);
}
public void wear() {
super.wear();
System.out.println("穿裤子");
}
}
class DecoratorShoes extends Decorator{
public DecoratorShoes(Jane Jane) {
super(Jane);
}
public void wear() {
super.wear();
System.out.println("穿鞋子");
}
}
public class DecoratorTest {
public static void main(String[] args) {
Jane p1 = new DecoratorSuit(new DecoratorShirt(new Jane()));
p1.wear();
System.out.println("==============");
Jane p2 = new DecoratorTShirt(new DecoratorPants(new Jane()));
p2.wear();
System.out.println("==============");
Jane p3 = new DecoratorTShirt(new DecoratorPants(new DecoratorShoes(new Jane())));
p3.wear();
System.out.println("==============");
Jane p4 = new DecoratorShoes(new DecoratorPants(new DecoratorTShirt(new Jane())));
p4.wear();
}
}
只有一个装饰角色
如果具体装饰(ConcreteDecorator)角色只有一个,那么可以去掉Decorator类。
代码:
interface People{
public void wear();
}
class Jane implements People{
public void wear() {
System.out.println("Jane:今天该穿什么呢?");
}
}
class LiMing implements People{
public void wear() {
System.out.println("LiMing:今天该穿什么呢?");
}
}
class DecoratorShoes implements People{
private People people;
public DecoratorShoes(People people) {
this.people = people;
}
public void wear() {
people.wear();
System.out.println("穿鞋子");
}
}
public class DecoratorTest {
public static void main(String[] args) {
People p1 = new DecoratorShoes(new Jane());
p1.wear();
People p2 = new DecoratorShoes(new LiMing());
p2.wear();
}
}
输出:
Jane:今天该穿什么呢?
穿鞋子
LiMing:今天该穿什么呢?
穿鞋子
具体构件角色和具体装饰角色都只有一个
极端地,具体构件(ConcreteComponent)角色只有一个,具体装饰(ConcreteDecorator)角色也只有一个。
class Jane {
public void wear() {
System.out.println("Jane:今天该穿什么呢?");
}
}
class DecoratorShoes extends Jane{
private Jane jane;
public DecoratorShoes(Jane jane) {
this.jane = jane;
}
public void wear() {
jane.wear();
System.out.println("穿鞋子");
}
}
public class DecoratorTest {
public static void main(String[] args) {
Jane j = new DecoratorShoes(new Jane());
j.wear();
}
}
此时完全退化成了最简单的继承形式。如下:
class Jane {
public void wear() {
System.out.println("Jane:今天该穿什么呢?");
}
}
class DecoratorShoes extends Jane{
public void wear() {
super.wear();
System.out.println("穿鞋子");
}
}
public class DecoratorTest {
public static void main(String[] args) {
DecoratorShoes j = new DecoratorShoes();
j.wear();
}
}
半透明装饰者
装饰者模式中装饰(Decorator)角色持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。也就是说不允许Decorator类扩展Component类的中接口。然而实际情况中很可能在Decorator中定义了一些新的方法。这时的装饰者成为半透明的装饰者模式,相应的上面的标准的装饰者模式成为透明的装饰者模式。
比如Decorator类里面增加eat方法,该方法在People类里面并没有.
良好的程序设计方式告诉我们声明变量尽量要使用顶层的超类,利用多态达到同样的代码实现不同的效果,在装饰者模式中也一样,要求要声明一个Component类型的对象,而不要声明一个Decorator更不要是ConcreteComponent类型的对象。
比如在下面的示例代码中:
// 尽量不要这样声明
DecoratorSuit decoratorSuit = new DecoratorSuit(new DecoratorShirt(new Jane()));
// 而应该这样声明
People p1 = new DecoratorSuit(new DecoratorShirt(new Jane()));
示例代码:
interface People{
public void wear();
}
class Jane implements People{
public void wear() {
System.out.println("今天该穿什么呢?");
}
}
class Decorator implements People{
private People people;
public Decorator(People people) {
this.people = people;
}
public void wear() {
people.wear();
}
public void eat() {
}
}
class DecoratorShirt extends Decorator{
public DecoratorShirt(People people) {
super(people);
}
public void wear() {
super.wear();
System.out.println("穿个衬衫");
}
public void eat() {
System.out.println("衬衫-去吃大餐");
}
}
class DecoratorSuit extends Decorator{
public DecoratorSuit(People people) {
super(people);
}
public void wear() {
super.wear();
System.out.println("穿个西服");
}
public void eat() {
System.out.println("西服-去吃大餐");
}
}
public class DecoratorTest {
public static void main(String[] args) {
// 尽量不要这样声明
DecoratorSuit decoratorSuit = new DecoratorSuit(new DecoratorShirt(new Jane()));
decoratorSuit.wear();
decoratorSuit.eat();
/*----------------------------------------------------------*/
// 要这样声明
People p1 = new DecoratorSuit(new DecoratorShirt(new Jane()));
p1.wear();
//p.eat();// error
/*----------------------------------------------------------*/
People p2 = new DecoratorSuit(new DecoratorShirt(new Jane()));
((DecoratorSuit)p2).eat();
}
}
输出
今天该穿什么呢?
穿个衬衫
穿个西服
西服-去吃大餐
今天该穿什么呢?
穿个衬衫
穿个西服
西服-去吃大餐
上面的代码在Decorator类里面提供了新的方法eat,而该方法在People类里面是没有的,因此如果像上面的代码里面在声明对象的时候生命成超类,也就是People类(真正的类型是DecoratorSuit类),当不调用eat方法的时候没有问题,但是必须要调用该方法的话,就必须要向下转型。
java设计模式之外观模式(门面模式)
针对外观模式,在项目开发和实际运用中十分频繁,但是其极易理解,下面就简要介绍一下。
一、概念介绍
外观模式(Facade),他隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。这种类型的设计模式属于结构性模式。为子系统中的一组接口提供了一个统一的访问接口,这个接口使得子系统更容易被访问或者使用。
二、角色及使用场景
简单来说,该模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用。这个模式中,设计到3个角色。
1).门面角色:外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。
2).子系统角色:实现了子系统的功能。它对客户角色和Facade时未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。
3).客户角色:通过调用Facede来完成要实现的功能。
使用场景:
1- 为复杂的模块或子系统提供外界访问的模块;
2- 子系统相互独立;
3- 在层析结构中,可以使用外观模式定义系统的每一层的入口。
三、实例
下面,我们就通过一个简单的例子来实现该模式。
每个Computer都有CPU、Memory、Disk。在Computer开启和关闭的时候,相应的部件也会开启和关闭,所以,使用了该外观模式后,会使用户和部件之间解耦。如:
包体的创建:
代码实现
首先是子系统类:
【代码清单--1】
1 package com.huawei.facadeDesign.children; 2 3 import org.apache.log4j.Logger; 4 5 /** 6 * cpu子系统类 7 * @author Administrator 8 * 9 */ 10 public class CPU 11 { 12 public static final Logger LOGGER = Logger.getLogger(CPU.class); 13 public void start() 14 { 15 LOGGER.info("cpu is start..."); 16 } 17 18 public void shutDown() 19 { 20 LOGGER.info("CPU is shutDown..."); 21 } 22 }
【代码清单--2】
1 package com.huawei.facadeDesign.children; 2 3 import org.apache.log4j.Logger; 4 5 /** 6 * Disk子系统类 7 * @author Administrator 8 * 9 */ 10 public class Disk 11 { 12 public static final Logger LOGGER = Logger.getLogger(Disk.class); 13 public void start() 14 { 15 LOGGER.info("Disk is start..."); 16 } 17 18 public void shutDown() 19 { 20 LOGGER.info("Disk is shutDown..."); 21 } 22 }
【代码清单--3】
1 package com.huawei.facadeDesign.children; 2 3 import org.apache.log4j.Logger; 4 5 /** 6 * Memory子系统类 7 * @author Administrator 8 * 9 */ 10 public class Memory 11 { 12 public static final Logger LOGGER = Logger.getLogger(Memory.class); 13 public void start() 14 { 15 LOGGER.info("Memory is start..."); 16 } 17 18 public void shutDown() 19 { 20 LOGGER.info("Memory is shutDown..."); 21 } 22 }
然后是,门面类Facade
【代码清单--4】
1 package com.huawei.facadeDesign.facade; 2 3 import org.apache.log4j.Logger; 4 5 import com.huawei.facadeDesign.children.CPU; 6 import com.huawei.facadeDesign.children.Disk; 7 import com.huawei.facadeDesign.children.Memory; 8 9 10 /** 11 * 门面类(核心) 12 * @author Administrator 13 * 14 */ 15 public class Computer 16 { 17 public static final Logger LOGGER = Logger.getLogger(Computer.class); 18 19 private CPU cpu; 20 private Memory memory; 21 private Disk disk; 22 public Computer() 23 { 24 cpu = new CPU(); 25 memory = new Memory(); 26 disk = new Disk(); 27 } 28 public void start() 29 { 30 LOGGER.info("Computer start begin"); 31 cpu.start(); 32 disk.start(); 33 memory.start(); 34 LOGGER.info("Computer start end"); 35 } 36 37 public void shutDown() 38 { 39 LOGGER.info("Computer shutDown begin"); 40 cpu.shutDown(); 41 disk.shutDown(); 42 memory.shutDown(); 43 LOGGER.info("Computer shutDown end..."); 44 } 45 }
最后为,客户角色。
【代码清单--5】
1 package com.huawei.facadeDesign; 2 3 import org.apache.log4j.Logger; 4 5 import com.huawei.facadeDesign.facade.Computer; 6 7 /** 8 * 客户端类 9 * @author Administrator 10 * 11 */ 12 public class Cilent { 13 public static final Logger LOGGER = Logger.getLogger(Cilent.class); 14 public static void main(String[] args) 15 { 16 Computer computer = new Computer(); 17 computer.start(); 18 LOGGER.info("================="); 19 computer.shutDown(); 20 } 21 22 }
【代码清单--6】运行结果
从上面的实例来看,有了这个Facade类,也就是Computer类,用户就不用亲自去调用子系统中的Disk,Memory、CPU类了,不需要知道系统内部的实现细节,甚至都不用知道系统内部的构成。客户端只需要跟Facade交互就可以了。
四、优点
- 松散耦合
使得客户端和子系统之间解耦,让子系统内部的模块功能更容易扩展和维护;
- 简单易用
客户端根本不需要知道子系统内部的实现,或者根本不需要知道子系统内部的构成,它只需要跟Facade类交互即可。
- 更好的划分访问层次
有些方法是对系统外的,有些方法是系统内部相互交互的使用的。子系统把那些暴露给外部的功能集中到门面中,这样就可以实现客户端的使用,很好的隐藏了子系统内部的细节。
Java设计模式(十一) 享元模式
本文转发自技术世界,原文链接 http://www.jasongj.com/design_pattern/flyweight/
享元模式介绍
享元模式适用场景
面向对象技术可以很好的解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致对象创建及垃圾回收的代价过高,造成性能下降等问题。享元模式通过共享相同或者相似的细粒度对象解决了这一类问题。
享元模式定义
享元模式(Flyweight Pattern),又称轻量级模式(这也是其英文名为FlyWeight的原因),通过共享技术有效地实现了大量细粒度对象的复用。
享元模式类图
享元模式角色划分
- FlyWeight 享元接口或者(抽象享元类),定义共享接口
- ConcreteFlyWeight 具体享元类,该类实例将实现共享
- UnSharedConcreteFlyWeight 非共享享元实现类
- FlyWeightFactory 享元工厂类,控制实例的创建和共享
内部状态 vs. 外部状态
- 内部状态是存储在享元对象内部,一般在构造时确定或通过setter设置,并且不会随环境改变而改变的状态,因此内部状态可以共享。
- 外部状态是随环境改变而改变、不可以共享的状态。外部状态在需要使用时通过客户端传入享元对象。外部状态必须由客户端保存。
享元模式实例解析
本文代码可从作者Github下载
享元接口,定义共享接口
1
|
package com.jasongj.flyweight;
|
具体享元类,实现享元接口。该类的对象将被复用
1
|
package com.jasongj.flyweight;
|
享元模式中,最关键的享元工厂。它将维护已创建的享元实例,并通过实例标记(一般用内部状态)去索引对应的实例。当目标对象未创建时,享元工厂负责创建实例并将其加入标记-对象映射。当目标对象已创建时,享元工厂直接返回已有实例,实现对象的复用。
1
|
package com.jasongj.factory;
|
从上面代码中可以看到,享元模式中对象的复用完全依靠享元工厂。同时本例中实现了对象创建的懒加载。并且为了保证线程安全及效率,本文使用了双重检查(Double Check)。
本例中,name
可以认为是内部状态,在构造时确定。externalState
属于外部状态,由客户端在调用时传入。
享元模式分析
享元模式优点
- 享元模式的外部状态相对独立,使得对象可以在不同的环境中被复用(共享对象可以适应不同的外部环境)
- 享元模式可共享相同或相似的细粒度对象,从而减少了内存消耗,同时降低了对象创建与垃圾回收的开销
享元模式缺点
- 外部状态由客户端保存,共享对象读取外部状态的开销可能比较大
- 享元模式要求将内部状态与外部状态分离,这使得程序的逻辑复杂化,同时也增加了状态维护成本
享元模式已(未)遵循的OOP原则
已遵循的OOP原则
- 依赖倒置原则
- 迪米特法则
- 里氏替换原则
- 接口隔离原则
- 单一职责原则
- 开闭原则
未遵循的OOP原则
- NA
Java设计模式系列
- Java设计模式(一) 简单工厂模式不简单
- Java设计模式(二) 工厂方法模式
- Java设计模式(三) 抽象工厂模式
- Java设计模式(四) 观察者模式
- Java设计模式(五) 组合模式
- Java设计模式(六) 代理模式 VS. 装饰模式
- Java设计模式(七) Spring AOP JDK动态代理 vs. cglib
- Java设计模式(八) 适配器模式
- Java设计模式(九) 桥接模式
- Java设计模式(十) 你真的用对单例模式了吗?
- Java设计模式(十一) 享元模式
- Java设计模式(十二) 策略模式
轻松学,Java 中的代理模式及动态代理
前几天我写了《秒懂,Java 注解 (Annotation)你可以这样学》,因为注解其实算反射技术中的一部分,然后我想了一下,反射技术中还有个常见的概念就是动态代理,于是索性再写一篇关于动态代理的博文好了。
我们先来分析代理这个词。
代理
代理是英文 Proxy 翻译过来的。我们在生活中见到过的代理,大概最常见的就是朋友圈中卖面膜的同学了。
她们从厂家拿货,然后在朋友圈中宣传,然后卖给熟人。
按理说,顾客可以直接从厂家购买产品,但是现实生活中,很少有这样的销售模式。一般都是厂家委托给代理商进行销售,顾客跟代理商打交道,而不直接与产品实际生产者进行关联。
所以,代理就有一种中间人的味道。
接下来,我们说说软件中的代理模式。
代理模式
代理模式是面向对象编程中比较常见的设计模式。
这是常见代理模式常见的 UML 示意图。
需要注意的有下面几点:
- 用户只关心接口功能,而不在乎谁提供了功能。上图中接口是 Subject。
Java代理模式及其应用
一. 代理与代理模式
1). 代理
代理模式其实很常见,比如买火车票这件小事:黄牛相当于是我们本人的的代理,我们可以通过黄牛买票。通过黄牛买票,我们可以避免与火车站的直接交互,可以省很多事,并且还能享受到黄牛更好的服务(如果钱给够的话)。在软件开发中,代理也具有类似的作用,并且一般可以分为静态代理和动态代理两种,上述的这个黄牛买票的例子就是静态代理。
那么,静态代理与动态代理的区别是什么呢?所谓静态代理,其实质是自己手写(或者用工具生成)代理类,也就是在程序运行前就已经存在的编译好的代理类。但是,如果我们需要很多的代理,每一个都这么去创建实属浪费时间,而且会有大量的重复代码,此时我们就可以采用动态代理,动态代理可以在程序运行期间根据需要动态的创建代理类及其实例来完成具体的功能。总的来说,根据代理类的创建时机和创建方式的不同,我们可以将代理分为静态代理和动态代理两种形式。
就像我们也可以自己直接去买票一样,在软件开发中,方法直接调用就可以完成功能,为什么非要通过代理呢?原因是采用代理模式可以有效的将具体的实现(买票过程)与调用方(买票者)进行解耦,通过面向接口进行编码完全将具体的实现(买票过程)隐藏在内部(黄牛)。此外,代理类不仅仅是一个隔离客户端和目标类的中介,我们还可以借助代理来在增加一些功能,而不需要修改原有代码,是开闭原则的典型应用。代理类主要负责为目标类预处理消息、过滤消息、把消息转发给目标类,以及事后处理消息等。代理类与目标类之间通常会存在关联关系,一个代理类的对象与一个目标类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用目标类的对象的相关方法,来提供特定的服务。总的来说,
-
代理对象存在的价值主要用于拦截对真实业务对象的访问(我们不需要直接与火车站打交道,而是把这个任务委托给黄牛);
-
代理对象应该具有和目标对象(真实业务对象)相同的方法,即实现共同的接口或继承于同一个类;
-
代理对象应该是目标对象的增强,否则我们就没有必要使用代理了。
事实上,真正的业务功能还是由目标类来实现,代理类只是用于扩展、增强目标类的行为。例如,在项目开发中我们没有加入缓冲、日志这些功能而后期需要加入,我们就可以使用代理来实现,而没有必要去直接修改已经封装好的目标类。
2). 代理模式
代理模式是较常见的模式之一,在许多框架中经常见到,比如Spring的面向切面的编程,MyBatis中缓存机制对PooledConnection的管理等。代理模式使得客户端在使用目标对象的时候间接通过操作代理对象进行,代理对象是对目标对象的增强,代理模式的UML示意图如下:
代理模式包含如下几个角色:
-
客户端:客户端面向接口编程,使用代理角色完成某项功能。
-
抽象主题:一般实现为接口,是对(被代理对象的)行为的抽象。
-
被代理角色(目标类):直接实现上述接口,是抽象主题的具体实现。
-
代理角色(代理类):实现上述接口,是对被代理角色的增强。
代理模式的使用本质上是对开闭原则(Open for Extension, Close for Modification)的直接支持。
二. 静态代理
静态代理的实现模式一般是:首先创建一个接口(JDK代理都是面向接口的),然后创建具体实现类来实现这个接口,然后再创建一个代理类同样实现这个接口,不同之处在于,具体实现类的方法中需要将接口中定义的方法的业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层。下面我们借用CSDN博友frank909在其《轻松学,Java 中的代理模式及动态代理》一文中的例子来了解静态代理:
我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?
电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告,现在用代码来进行模拟。
1). 抽象主题(接口)
首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为Movie,代表电影这个主题。
package com.frank.test;
public interface Movie {
void play();
}
2). 被代理角色(目标类)与代理角色(代理类)
然后,我们要有一个真正的实现这个 Movie 接口的类和一个只是实现接口的代理类。
package com.frank.test;
public class RealMovie implements Movie {
@Override
public void play() {
// TODO Auto-generated method stub
System.out.println("您正在观看电影 《肖申克的救赎》");
}
}
这个表示真正的影片。它实现了 Movie 接口,play()方法调用时,影片就开始播放。那么代理类呢?
package com.frank.test;
public class Cinema implements Movie {
RealMovie movie;
public Cinema(RealMovie movie) {
super();
this.movie = movie;
}
@Override
public void play() {
guanggao(true); // 代理类的增强处理
movie.play(); // 代理类把具体业务委托给目标类,并没有直接实现
guanggao(false); // 代理类的增强处理
}
public void guanggao(boolean isStart){
if ( isStart ) {
System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
} else {
System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
}
}
}
Cinema 就是代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。也就是说,Cinema(代理类) 与 RealMovie(目标类) 都可以播放电影,但是除此之外,Cinema(代理类)还对“播放电影”这个行为进行进一步增强,即增加了额外的处理,同时不影响RealMovie(目标类)的实现。
3). 客户端
package com.frank.test;
public class ProxyTest {
public static void main(String[] args) {
RealMovie realmovie = new RealMovie();
Movie movie = new Cinema(realmovie);
movie.play();
}
}/** Output
电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!
您正在观看电影 《肖申克的救赎》
电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!
**/
现在可以看到,代理模式可以在不修改被代理对象的基础上(符合开闭原则),通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。如前所述,由于Cinema(代理类)是事先编写、编译好的,而不是在程序运行过程中动态生成的,因此这个例子是一个静态代理的应用。
三. 动态代理
在第一节我们已经提到,动态代理可以在程序运行期间根据需要动态的创建代理类及其实例来完成具体的功能,下面我们结合具体实例来介绍JDK动态代理。
1). 抽象主题(接口)
同样地,首先得有一个接口,通用的接口是代理模式实现的基础。
package cn.inter;
public interface Subject {
public void doSomething();
}
2). 被代理角色(目标类)
然后,我们要有一个真正的实现这个 Subject 接口的类,以便代理。
package cn.impl;
import cn.inter.Subject;
public class RealSubject implements Subject {
public void doSomething() {
System.out.println("call doSomething()");
}
}
3). 代理角色(代理类)与客户端
在动态代理中,代理类及其实例是程序自动生成的,因此我们不需要手动去创建代理类。在Java的动态代理机制中,InvocationHandler(Interface)接口和Proxy(Class)类是实现我们动态代理所必须用到的。事实上,Proxy通过使用InvocationHandler对象生成具体的代理代理对象,下面我们看一下对InvocationHandler接口的实现:
package cn.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Title: InvocationHandler 的实现
* Description: 每个代理的实例都有一个与之关联的 InvocationHandler
* 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它调用invoke()去处理。
*
* @author rico
* @created 2017年7月3日 下午3:08:55
*/
public class ProxyHandler implements InvocationHandler {
private Object proxied; // 被代理对象
public ProxyHandler(Object proxied) {
this.proxied = proxied;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 在转调具体目标对象之前,可以执行一些功能处理
System.out.println("前置增强处理: yoyoyo...");
// 转调具体目标对象的方法(三要素:实例对象 + 实例方法 + 实例方法的参数)
Object obj = method.invoke(proxied, args);
// 在转调具体目标对象之后,可以执行一些功能处理
System.out.println("后置增强处理:hahaha...");
return obj;
}
}
在实现了InvocationHandler接口后,我们就可以创建代理对象了。在Java的动态代理机制中,我们使用Proxy类的静态方法newProxyInstance创建代理对象,如下:
package cn.client;
import java.lang.reflect.Proxy;
import cn.handler.ProxyHandler;
import cn.impl.RealSubject;
import cn.inter.Subject;
public class Test {
public static void main(String args[]) {
// 真实对象real
Subject real = new RealSubject();
// 生成real的代理对象
Subject proxySubject = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(), new Class[] { Subject.class },
new ProxyHandler(real));
proxySubject.doSomething();
System.out.println("代理对象的类型 : " + proxySubject.getClass().getName());
System.out.println("代理对象所在类的父类型 : " + proxySubject.getClass().getGenericSuperclass());
}
}/** Output
前置增强处理: yoyoyo...
call doSomething()
后置增强处理:hahaha...
代理对象的类型 : com.sun.proxy.$Proxy0
代理对象所在类的父类型 : class java.lang.reflect.Proxy
**/
到此为止,我们给出了完整的基于JDK动态代理机制的代理模式的实现。我们从上面的实例中可以看到,代理对象proxySubject的类型为”com.sun.proxy.$Proxy0”,这恰好印证了proxySubject对象是一个代理对象。除此之外,我们还发现代理对象proxySubject所对应的类继承自java.lang.reflect.Proxy类,这也正是JDK动态代理机制无法实现对class的动态代理的原因:Java只允许单继承。
4). JDK中InvocationHandler接口与Proxy类
(1). InvocationHandler接口
InvocationHandler 是一个接口,官方文档解释说:每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler中的invoke() 方法决定了怎么样处理代理传递过来的方法调用。
(2). Proxy类
JDK通过 Proxy 的静态方法 newProxyInstance 动态地创建代理,该方法在Java中的声明如下:
/**
* @description
* @author rico
* @created 2017年7月3日 下午3:16:49
* @param loader 类加载器
* @param interfaces 目标类所实现的接口
* @param h InvocationHandler 实例
* @return
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
事实上,Proxy 动态产生的代理对象调用目标方法时,代理对象会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
五. Spring AOP 与 动态代理
AOP 专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在 Java EE 应用中,常常通过 AOP 来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等,AOP 已经成为一种非常常用的解决方案。
AOP机制是 Spring 所提供的核心功能之一,其既是Java动态代理机制的经典应用,也是动态AOP实现的代表。Spring AOP默认使用Java动态代理来创建AOP代理,具体通过以下几个步骤来完成:
-
Spring IOC 容器创建Bean(目标类对象);
-
Bean创建完成后,Bean后处理器(BeanPostProcessor)根据具体的切面逻辑及Bean本身使用Java动态代理技术生成代理对象;
-
应用程序使用上述生成的代理对象替代原对象来完成业务逻辑,从而达到增强处理的目的。
Java设计模式之责任链模式、职责链模式
什么是链
-
public abstract class Handler {
-
-
/**
-
* 持有后继的责任对象
-
*/
-
protected Handler successor;
-
/**
-
* 示意处理请求的方法,虽然这个示意方法是没有传入参数的
-
* 但实际是可以传入参数的,根据具体需要来选择是否传递参数
-
*/
-
public abstract void handleRequest();
-
/**
-
* 取值方法
-
*/
-
public Handler getSuccessor() {
-
return successor;
-
}
-
/**
-
* 赋值方法,设置后继的责任对象
-
*/
-
public void setSuccessor(Handler successor) {
-
this.successor = successor;
-
}
-
-
}
-
public class ConcreteHandler extends Handler {
-
/**
-
* 处理方法,调用此方法处理请求
-
*/
-
-
public void handleRequest() {
-
/**
-
* 判断是否有后继的责任对象
-
* 如果有,就转发请求给后继的责任对象
-
* 如果没有,则处理请求
-
*/
-
if(getSuccessor() != null)
-
{
-
System.out.println("放过请求");
-
getSuccessor().handleRequest();
-
}else
-
{
-
System.out.println("处理请求");
-
}
-
}
-
-
}
-
public class Client {
-
-
public static void main(String[] args) {
-
//组装责任链
-
Handler handler1 = new ConcreteHandler();
-
Handler handler2 = new ConcreteHandler();
-
handler1.setSuccessor(handler2);
-
//提交请求
-
handler1.handleRequest();
-
}
-
-
}
-
public abstract class Handler {
-
/**
-
* 持有下一个处理请求的对象
-
*/
-
protected Handler successor = null;
-
/**
-
* 取值方法
-
*/
-
public Handler getSuccessor() {
-
return successor;
-
}
-
/**
-
* 设置下一个处理请求的对象
-
*/
-
public void setSuccessor(Handler successor) {
-
this.successor = successor;
-
}
-
/**
-
* 处理聚餐费用的申请
-
* @param user 申请人
-
* @param fee 申请的钱数
-
* @return 成功或失败的具体通知
-
*/
-
public abstract String handleFeeRequest(String user , double fee);
-
}
-
public class ProjectManager extends Handler {
-
-
-
public String handleFeeRequest(String user, double fee) {
-
-
String str = "";
-
//项目经理权限比较小,只能在500以内
-
if(fee < 500)
-
{
-
//为了测试,简单点,只同意张三的请求
-
if("张三".equals(user))
-
{
-
str = "成功:项目经理同意【" + user + "】的聚餐费用,金额为" + fee + "元";
-
}else
-
{
-
//其他人一律不同意
-
str = "失败:项目经理不同意【" + user + "】的聚餐费用,金额为" + fee + "元";
-
}
-
}else
-
{
-
//超过500,继续传递给级别更高的人处理
-
if(getSuccessor() != null)
-
{
-
return getSuccessor().handleFeeRequest(user, fee);
-
}
-
}
-
return str;
-
}
-
-
}
-
public class DeptManager extends Handler {
-
-
-
public String handleFeeRequest(String user, double fee) {
-
-
String str = "";
-
//部门经理的权限只能在1000以内
-
if(fee < 1000)
-
{
-
//为了测试,简单点,只同意张三的请求
-
if("张三".equals(user))
-
{
-
str = "成功:部门经理同意【" + user + "】的聚餐费用,金额为" + fee + "元";
-
}else
-
{
-
//其他人一律不同意
-
str = "失败:部门经理不同意【" + user + "】的聚餐费用,金额为" + fee + "元";
-
}
-
}else
-
{
-
//超过1000,继续传递给级别更高的人处理
-
if(getSuccessor() != null)
-
{
-
return getSuccessor().handleFeeRequest(user, fee);
-
}
-
}
-
return str;
-
}
-
-
}
-
public class GeneralManager extends Handler {
-
-
-
public String handleFeeRequest(String user, double fee) {
-
-
String str = "";
-
//总经理的权限很大,只要请求到了这里,他都可以处理
-
if(fee >= 1000)
-
{
-
//为了测试,简单点,只同意张三的请求
-
if("张三".equals(user))
-
{
-
str = "成功:总经理同意【" + user + "】的聚餐费用,金额为" + fee + "元";
-
}else
-
{
-
//其他人一律不同意
-
str = "失败:总经理不同意【" + user + "】的聚餐费用,金额为" + fee + "元";
-
}
-
}else
-
{
-
//如果还有后继的处理对象,继续传递
-
if(getSuccessor() != null)
-
{
-
return getSuccessor().handleFeeRequest(user, fee);
-
}
-
}
-
return str;
-
}
-
-
}
-
public class Client {
-
-
public static void main(String[] args) {
-
//先要组装责任链
-
Handler h1 = new GeneralManager();
-
Handler h2 = new DeptManager();
-
Handler h3 = new ProjectManager();
-
h3.setSuccessor(h2);
-
h2.setSuccessor(h1);
-
-
//开始测试
-
String test1 = h3.handleFeeRequest("张三", 300);
-
System.out.println("test1 = " + test1);
-
String test2 = h3.handleFeeRequest("李四", 300);
-
System.out.println("test2 = " + test2);
-
System.out.println("---------------------------------------");
-
-
String test3 = h3.handleFeeRequest("张三", 700);
-
System.out.println("test3 = " + test3);
-
String test4 = h3.handleFeeRequest("李四", 700);
-
System.out.println("test4 = " + test4);
-
System.out.println("---------------------------------------");
-
-
String test5 = h3.handleFeeRequest("张三", 1500);
-
System.out.println("test5 = " + test5);
-
String test6 = h3.handleFeeRequest("李四", 1500);
-
System.out.println("test6 = " + test6);
-
}
-
-
}
责任链模式实现的三种方式
责任链模式
责任链模式的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系, 将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。这里就不再过多的介绍什么是责任链模式,主要来说说java中如何编写。主要从下面3个框架中的代码中介绍。
- servlet中的filter
- dubbo中的filter
- mybatis中的plugin 这3个框架在实现责任链方式不尽相同。
servlet中的Filter
servlet中分别定义了一个 Filter和FilterChain的接口,核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public final class ApplicationFilterChain implements FilterChain { private int pos = 0 ; //当前执行filter的offset private int n; //当前filter的数量 private ApplicationFilterConfig[] filters; //filter配置类,通过getFilter()方法获取Filter private Servlet servlet @Override public void doFilter(ServletRequest request, ServletResponse response) { if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter(); filter.doFilter(request, response, this ); } else { // filter都处理完毕后,执行servlet servlet.service(request, response); } } } |
代码还算简单,结构也比较清晰,定义一个Chain,里面包含了Filter列表和servlet,达到在调用真正servlet之前进行各种filter逻辑。
Dubbo中的Filter
Dubbo在创建Filter的时候是另外一个方法,通过把Filter封装成 Invoker的匿名类,通过链表这样的数据结构来完成责任链,核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private static <T> Invoker<T> buildInvokerChain( final Invoker<T> invoker, String key, String group) { Invoker<T> last = invoker; //只获取满足条件的Filter List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter. class ).getActivateExtension(invoker.getUrl(), key, group); if (filters.size() > 0 ) { for ( int i = filters.size() - 1 ; i >= 0 ; i --) { final Filter filter = filters.get(i); final Invoker<T> next = last; last = new Invoker<T>() { ... public Result invoke(Invocation invocation) throws RpcException { return filter.invoke(next, invocation); } ... }; } } return last; } |
Dubbo的责任链就没有类似FilterChain这样的类吧Filter和调用Invoker结合起来,而是通过创建一个链表,调用的时候我们只知道第一个节点,每个节点包含了下一个调用的节点信息。 这里的虽然Invoker封装Filter没有显示的指定next,但是通过java匿名类和final的机制达到同样的效果。
Mybatis中的Plugin
Mybatis可以配置各种Plugin,无论是官方提供的还是自己定义的,Plugin和Filter类似,就在执行Sql语句的时候做一些操作。Mybatis的责任链则是通过动态代理的方式,使用Plugin代理实际的Executor类。(这里实际还使用了组合模式,因为Plugin可以嵌套代理),核心代码如下:
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
|
public class Plugin implements InvocationHandler{ private Object target; private Interceptor interceptor; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (满足代理条件) { return interceptor.intercept( new Invocation(target, method, args)); } return method.invoke(target, args); } //对传入的对象进行代理,可能是实际的Executor类,也可能是Plugin代理类 public static Object wrap(Object target, Interceptor interceptor) { Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0 ) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } } |
简单的示意图如下:
总结
这里简单介绍了Servlet、Dubbo、Mybatis对责任链模式的不同实现手段,其中Servlet是相对比较清晰,又易于实现的方式,而Dubbo和Mybatis则适合在原有代码基础上,增加责任链模式代码改动量最小的。