完整设计模式学习笔记请戳
9.组合模式
需求
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院, 一个学院有多个系

传统方式解决方案如下图

传统方案解决学校院系展示存在的问题分析
- 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
- 实际上我们的要求是 :在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现的管理的操作,比如对学院、系的添加,删除,遍历等
- 解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作。 => 组合模式
基本介绍
- 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构型模式。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象

对原理结构图的说明-即(组合模式的角色及职责)
- Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component 子部件, Component 可以是抽象类或者接口
- Leaf : 在组合中表示叶子节点,叶子节点没有子节点
- Composite :非叶子节点, 用于存储子部件, 在 Component 接口中实现 子部件的相关操作,比如增加(add), 删除。
代码实现
应用实例要求
- 编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院, 一个学院有多个系。
- 思路分析和图解(类图)

Component 组合中对象声明接口
public abstract class OrganizationComponent {
private String name;//名字
private String des;//说明
public OrganizationComponent(String name, String des) {
this.name = name;
this.des = des;
}
public void add(OrganizationComponent component){
//默认实现
throw new UnsupportedOperationException();
}
public void remove(OrganizationComponent component){
//默认实现
throw new UnsupportedOperationException();
}
//子类需要实现
protected abstract void print();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
}
University Composite角色 非叶子节点
public class University extends OrganizationComponent {
List<OrganizationComponent> organizationComponents = new ArrayList<>();
public University(String name, String des) {
super(name, des);
}
@Override
public void add(OrganizationComponent component) {
organizationComponents.add(component);
}
@Override
public void remove(OrganizationComponent component) {
organizationComponents.remove(component);
}
//输出University包含的College
@Override
protected void print() {
System.out.println("-------"+getName()+"-------");
for (OrganizationComponent organizationComponent:organizationComponents) {
organizationComponent.print();
}
}
}
College Composite角色 非叶子节点
public class College extends OrganizationComponent {
List<OrganizationComponent> organizationComponents = new ArrayList<>();
public College(String name, String des) {
super(name, des);
}
@Override
public void add(OrganizationComponent component) {
organizationComponents.add(component);
}
@Override
public void remove(OrganizationComponent component) {
organizationComponents.remove(component);
}
//输出College包含的Department
@Override
protected void print() {
System.out.println("-------"+getName()+"-------");
for (OrganizationComponent organizationComponent:organizationComponents) {
organizationComponent.print();
}
}
}
Department Leaf 叶子节点
public class Department extends OrganizationComponent {
public Department(String name, String des) {
super(name, des);
}
@Override
protected void print() {
System.out.println("-------"+getName()+"-------");
}
}
客户端测试
public class Client {
public static void main(String[] args) {
//从大到小创建对象
//1 创建学校
OrganizationComponent university = new University("清华大学","中国顶级大学");
//2 创建学院
OrganizationComponent college1 = new College("计算机学院","修电脑的");
OrganizationComponent college2 = new College("船舶学院","打渔的");
//3 创建专业
college1.add(new Department("软件工程","写代码的背锅的"));
college1.add(new Department("信息工程","当网管的"));
college1.add(new Department("计算机科学与技术","卖电脑的"));
college2.add(new Department("轮机工程","研究轮船引擎的"));
college2.add(new Department("潜艇工程","国之重器"));
university.add(college1);
university.add(college2);
//4 打印学校
university.print();
// college2.print();
}
}
组合模式在 JDK 集合的源码分析
Java的集合类HashMap就使用了组合模式

- Map就是一个抽象的构建,类似Component
- HashMap是一个中间的构建,类似Composite,实现了一系列的方法put putAll
- Node是HashMap的静态内部类,类似Leaf节点,没有put putAll方法
static class Node<K,V> implements Map.Entry<K,V>
-
组合模式的注意事项和细节
- 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
- 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
10. 外观模式
需求
- 组建一个家庭影院:
DVD 播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为:- 直接用遥控器:统筹各设备开关
- 开爆米花机
- 放 下 屏 幕
- 开 投 影 仪
- 开音响
- 开 DVD,
- 选 dvd
- 去拿爆米花
- 调 暗 灯 光
- 播放
- 观影结束后,关闭各种设备
传统方式实现

传统方式解决影院管理问题分析
- 在 ClientTest 的 main 方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程
- 不利于在 ClientTest 中,去维护对子系统的操作
- 解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法 ready, play, pause, end ),用来访问子系统中的一群接口
- 也就是说 就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节 => 外观模式
基本介绍
- 外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
- 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
对类图说明(分类外观模式的角色)
- 外观类(Facade): 为调用端提供统一的调用接口, 外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
- 调用者(Client): 外观接口的调用者
- 子系统的集合:指模块或者子系统,处理 Facade对象指派的任务,他是功能的实际提供者
代码实现
- 外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能达到目的。比如:在 pc 上安装软件的时候经常有一键安装选项(省去选择安装目录、安装的组件等等),还有就是手机的重启功能(把关机和启动合为一个操作)。
- 外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用
- 示意图说明

子系统集合
public class DVDPlayer {
private DVDPlayer(){}
public static DVDPlayer getInstance(){
return Holder.instance;
}
private static class Holder{
private static final DVDPlayer instance = new DVDPlayer();
}
public void on(){
System.out.println("DVD ON");
}
public void off(){
System.out.println("DVD OFF");
}
public void play(){
System.out.println("DVD IS PLAYING");
}
public void pause(){
System.out.println("DVD PAUSE");
}
}
public class Stereo {
private Stereo(){}
public static Stereo getInstance(){
return Holder.instance;
}
private static class Holder{
private static final Stereo instance = new Stereo();
}
public void on(){
System.out.println("Stereo ON");
}
public void off(){
System.out.println("Stereo OFF");
}
public void trunUp(){
System.out.println("Stereo TURN UP");
}
public void trunDown(){
System.out.println("Stereo TURN DOWN");
}
}
public class TheaterLight {...}
public class Screen {...}
public class Projector {...}
public class Popcorn {...}
外观类
public class HomeTheaterFacade {
//定义各个子系统对象
private DVDPlayer dvdPlayer;
private Popcorn popcorn;
private Projector projector;
private Screen screen;
private Stereo stereo;
private TheaterLight theaterLight;
public HomeTheaterFacade() {
this.dvdPlayer = DVDPlayer.getInstance();
this.popcorn = Popcorn.getInstance();
this.projector = Projector.getInstance();
this.screen = Screen.getInstance();
this.stereo = Stereo.getInstance();
this.theaterLight = TheaterLight.getInstance();
}
//操作分成4步
//1 准备
public void ready(){
popcorn.on();
popcorn.pop();
screen.down();
projector.on();
stereo.on();
dvdPlayer.on();
theaterLight.dim();
}
//2 播放
public void play(){
dvdPlayer.play();
}
//3 暂停
public void pause(){
dvdPlayer.pause();
}
//4 结束
public void end(){
popcorn.off();
theaterLight.bright();
screen.up();
projector.off();
stereo.off();
dvdPlayer.off();
}
}
客户端调用
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
System.out.println("------准备------");
homeTheaterFacade.ready();
System.out.println("------播放------");
homeTheaterFacade.play();
System.out.println("------结束------");
homeTheaterFacade.end();
}
}
控制台输出
------准备------
Popcorn ON
Popcorn POP
Screen DOWN
Projector ON
Stereo ON
DVD ON
TheaterLight DIM
------播放------
DVD IS PLAYING
------结束------
Popcorn OFF
TheaterLight BRIGHT
Screen UP
Projector OFF
Stereo OFF
DVD OFF
外观模式应用案例
外观模式在Android中应用也非常广泛,比如Context类,里面封装了很多方法,还是以startActivity()方法为例。实际上startActivity()是通过ActivityManagerService来实现的,ActivityManagerService我们应该都有耳闻,但是实际开发中一般都用不到,通过封装的方式,Context类隐藏了这些细节,我们只要简单调个方法就可以启动一个新的Activity。 这就是外观模式在Android应用的例子了。当然这种应用比比皆是,我们平时开发也经常用的到。
外观模式的注意事项和细节
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
- 外观模式对客户端与子系统的耦合关系 - 解耦,让子系统内部的模块更易维护和扩展
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
- 当系统需要进行分层设计时,可以考虑使用 Facade 模式
- 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个 Facade 类,来提供遗留系统的比较清晰简单的接口,让新系统与 Facade 类交互,提高复用性
- 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。
11. 享元模式
需求
小型的外包项目,给客户 A 做一个产品展示网站,客户 A 的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:
- 有客户要求以新闻的形式发布
- 有客户人要求以博客的形式发布
- 有客户希望以微信公众号的形式发布
传统方案
- 直接复制粘贴一份,然后根据客户不同要求,进行定制修改
- 给每个网站租用一个空间
- 方案设计示意图

传统方案解决网站展现项目-问题分析
- 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
- 解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源
- 对于代码来说,由于是一份实例,维护和扩展都更加容易
- 上面的解决思路就可以使用 享元模式 来解决
基本介绍
- 享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象
- 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
- 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
- 享元模式经典的应用场景就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式
原理类图

对原理图的说明-即(模式的角色及职责)
- FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态(后面介绍) 的接口或实现
- ConcreteFlyWeight是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
- UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。
- FlyWeightFactory 享元工厂类,用于构建一个池容器(集合), 同时提供从池中获取对象方法
内部状态和外部状态
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态
-
享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态
-
内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
-
外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
-
举个例子:围棋理论上有 361 个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题
代码实现
使用享元模式完成前面提出的网站外包问题

- FlyWeight 抽象的享元角色
public abstract class WebSite {
public abstract void use(User user);
}
- ConcreteFlyWeight 具体的享元角色
public class ConcrateWebSite extends WebSite {
//共享的部分,内部状态
private String type = "";//网站发布的类型
public ConcrateWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站的发布形式为:"+type+"使用中... , 使用者为"+user.getName());
}
}
- FlyWeightFactory 享元工厂类
public class WebSiteFactory {
//集合 充当池的作用
private HashMap<String,ConcrateWebSite> pool = new HashMap<>();
//根据网站发布类型返回网站,如果没有就创建一个网站并放入池中
public WebSite getWebSiteByCategory(String type){
if (!pool.containsKey(type)){
pool.put(type,new ConcrateWebSite(type));
}
return pool.get(type);
}
//获取网站分类的总数
public int getWebsiteCount() {
return pool.size();
}
}
- 外部状态 User
public class User {
private String name;
public User(String name){
this.name = name;
}
public String getName() {
return name;
}
}
- 客户端测试代码
public class Client {
public static void main(String[] args) {
//创建工厂
WebSiteFactory factory = new WebSiteFactory();
WebSite webSite = factory.getWebSiteByCategory("新闻");
webSite.use(new User("Tom"));
WebSite webSite2 = factory.getWebSiteByCategory("博客");
WebSite webSite3 = factory.getWebSiteByCategory("博客");
WebSite webSite4 = factory.getWebSiteByCategory("博客");
webSite2.use(new User("Jerry"));
webSite3.use(new User("Jack"));
webSite4.use(new User("Adam"));
//只要形式相同 只返回一个对象
System.out.println("网站个数为:"+factory.getWebsiteCount());;//2个
}
}
- 控制台输出
网站的发布形式为:新闻使用中...,使用者为Tom
网站的发布形式为:博客使用中...,使用者为Jerry
网站的发布形式为:博客使用中...,使用者为Jack
网站的发布形式为:博客使用中...,使用者为Adam
网站个数为:2
享元模式在 JDK-Interger 的应用源码分析
public class FlyWeightDemo {
public static void main(String[] args) {
//如果 Integer.valueOf(x) x 在 -128 --- 127 直接,就是使用享元模式返回,如果不在范围类,则仍然 new
//小结:
//1. 在 valueOf 方法中,先判断值是否在 IntegerCache 中,如果不在,就创建新的 Integer(new), 否则,就直接从 缓存池返回
//2. valueOf 方法,就使用到享元模式
//3. 如果使用 valueOf 方法得到一个 Integer 实例,范围在 -128 - 127 ,执行速度比 new 快
Integer x = Integer.valueOf(127); // 得到 x 实例,类型 Integer
Integer y = new Integer(127); // 得 到 y 实 例 , 类 型 Integer
Integer z = Integer.valueOf(127);
Integer w = new Integer(127);
System.out.println(x.equals(y)); // 大小,true
System.out.println(x == y ); // false
System.out.println(x == z ); // true
System.out.println(w == x ); // false
System.out.println(w == y ); // false
Integer x1 = Integer.valueOf(200);
Integer x2 = Integer.valueOf(200);
System.out.println(x1==x2);//false 不在缓冲范围内
}
}
Integer.valueOf源码
如果 Integer.valueOf(x) x 在 -128 --- 127 直接,就是使用享元模式返回,如果不在范围类,则仍然 new
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
享元模式的注意事项和细节
- 在享元模式这样理解,“享”就表示共享,“元”表示对象
- 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
- 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
- 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
- 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
- 享元模式经典的应用场景是需要缓冲池的场景,比如 String 常量池、数据库连接池
12. 代理模式
基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式, 主要有三种
- 静态代理
- 动态代理 (JDK 代理、接口代理)
- Cglib 代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
- 代理模式示意图

静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
案例
具体要求
- 定义一个接口:ITeacherDao
- 目标对象 TeacherDAO 实现接口 ITeacherDAO
- 使用静态代理方式,就需要在代理对象 TeacherDAOProxy 中也实现 ITeacherDAO
- 调用的时候通过调用代理对象的方法来调用目标对象.
- 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
代码实现
- 代理接口
public interface ITeacherDao {
void teach();//授课的方法
}
- 目标对象(被代理对象)
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师授课中...");
}
}
- 代理对象
public class TeacherDaoProxy implements ITeacherDao{
private ITeacherDao target;//目标对象 通过接口来聚合
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
System.out.println("开始代理。。。。。。");
target.teach();
System.out.println("提交。。。。。。");
}
}
- 客户端调用
public class Client {
public static void main(String[] args) {
//创建目标对象(被代理对象)
TeacherDao teacherDao = new TeacherDao();
//创建代理对象,同时将被代理对象传递给代理对象
TeacherDaoProxy proxy = new TeacherDaoProxy(teacherDao);
//通过代理对象,调用被代理对象的方法
//即执行的是代理对象的方法,代理对象再去调用目标对象的方法
proxy.teach();
}
}
优缺点
- 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
- 一旦接口增加方法,目标对象与代理对象都要维护
动态代理
基本介绍
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK 代理、接口代理
JDK 中生成代理对象的 API
- 代理类所在包:java.lang.reflect.Proxy
- JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
代码实现
将前面的静态代理改进成动态代理模式(即:JDK 代理模式)
- 代理接口及目标对象同上
- 代理工厂,动态生成代理对象
public class ProxyFactory {
//维护一个目标对象 Object
private Object target;
//构造器 对target进行初始化
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance() {
//说明
/**
* public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h)
*/
//1.loader 指定当前目标对象使用的类加载器,获取加载器的方法固定
//2.interfaces 目标对象实现的接口类型,使用反省方法确认类型
//3.InvocationHandler : 事件处理,执行目标对象的方法时,会触发事件处理方法,
// 会把当前执行的目标对象方法作为一个参数传入
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理开始");
Object returnVal = method.invoke(target, args);
System.out.println("JDK代理提交");
return returnVal;
}
});
}
}
- 客户端调用
public class Client {
public static void main(String[] args) {
//创建一个目标对象
ITeacherDao target = new TeacherDao();
//给目标对象创建代理对象,可以转成ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();
//class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
System.out.println(proxyInstance.getClass());
//通过代理对象,调用目标方法
proxyInstance.teach();
}
}
Cglib代理
- 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理
- Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib 代理归属到动态代理。
- Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截
- 在 AOP 编程中如何选择代理模式:
- 目标对象需要实现接口,用 JDK 代理
- 目标对象不需要实现接口,用 Cglib 代理
- Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
Cglib 代理模式实现步骤
- 需要引入 cglib 的 jar 文件
- asm.jar、 asm-commons.jar、 asm-tree.jar、 cglib-2.2.jar
- 在内存中动态构建子类,注意代理的类不能为 final,否则报错java.lang.IllegalArgumentException:
- 目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
代码实现
- TeacherDao 被代理对象
public class TeacherDao {
public String teach() {
System.out.println("老师授课中..., cglib代理,不需要实现接口");
return "cglib代理模式";
}
}
- cglib 代理工厂
public class ProxyFactory implements MethodInterceptor {
//维护一个目标对象 Object
private Object target;
//传入一个被代理的对象
public ProxyFactory(Object target) {
this.target = target;
}
//返回一个代理对象 是target对象的代理对象
public Object getProxyInstance(){
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
//重写intercept方法,调用目标对象的相关方法
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理模式开始了。。。");
Object returnVal = method.invoke(target, args);
System.out.println("cglib代理模式提交。。。");
return returnVal;
}
}
- 客户端测试代码
public class Client {
public static void main(String[] args) {
//创建一个目标对象
TeacherDao target = new TeacherDao();
//获取到代理对象,并且将目标对象传递给代理对象
TeacherDao proxyInstance = (TeacherDao) new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法,触发interceptf方法,从而实现对目标对象的调用
String result = proxyInstance.teach();
System.out.println(result);//cglib代理模式
}
}
代理模式的几种变体
-
防火墙代理 内网通过代理穿透防火墙,实现对公网的访问。
-
缓存代理 比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok,如果取不到资源,再到公网或者数据库取,然后缓存。
-
远程代理 远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
-
同步代理:主要使用在多线程编程中,完成多线程间同步工作同步代理:主要使用在多线程编程中,完成多线程间同步工作
设计模式学习代码及笔记)
【代码】
github.com/willShuhuan…
【笔记】
设计模式01 七大原则
设计模式02 类关系与UML类图
设计模式03 创建型模式1-单例+工厂
设计模式04 创建型模式2-原型+建造者
设计模式05 结构型模式1-适配器+桥接+装饰者
设计模式06 结构型模式2-组合+外观+享元+代理
设计模式07 行为型模式1-模板方法+命令+访问者
设计模式08 行为型模式2-迭代器+观察者+中介者
设计模式09 行为型模式3-备忘录+解释器+状态
设计模式10 行为型模式4-策略+职责链