代理模式 【设计模式之禅作者】

2023-11-08

代理模式

12.1 我是游戏至尊

      2007年,感觉很无聊,于是就玩了一段时间的网络游戏,游戏名就不说了,要不就有做广告的嫌疑,反正就是打怪、升级、砍人、被人砍,然后继续打怪、升级、打怪、升级······我花了两个月的时间升级到80级,已经很有成就感了,但是还被人杀死,高手到处都是,GM(Game Master,游戏管理员)也不管,对于咱这种非RMB玩家基本上都是懒得搭理。在这段时间我是体会到网络游戏的乐与苦,参与家族(工会)攻城,胜利后那叫一个乐呀,感觉自己真是一个“狂暴战士”,无往不胜!那苦是什么呢?就是升级,为了升一级,就要到出杀怪,做任务,那个游戏还很变态,外挂管得很严,基本上出个外挂,没了两天就开始封账号,不敢用,升级基本上都要靠自己手打,累呀!我曾经的记录是连着打了23个小时,睡觉在梦中还和大BOSS在PK。有这样一段经历还是很有意思,作为架构师是不是可以把这段经历通过架构的方式记录下呢?当然可以了,我们把这段打游戏的过程系统化,非常简单的一个过程,如图12-1所示。

clip_image002

图12-1 游戏过程

      太简单了,定义一个接口IGamePlayer,是所有喜爱网络游戏的玩家,然后定义一个具体的实现类GamePlayer,实现每个游戏爱好者为了玩游戏要执行的功能。代码也非常简单,我们先来看IGamePlayer,如代码清单12-1所示。

代码清单12-1 游戏者接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface IGamePlayer {
 
//登录游戏
 
public void login(String user,String password);
 
//杀怪,网络游戏的主要特色
 
public void killBoss();
 
//升级
 
public void upgrade();
 
}

      非常简单,定义了三个方法,分别是我们在网络游戏中最常用的功能:登录游戏、杀怪和升级,其实现类如代码清单12-2所示。

代码清单12-2 游戏者

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 GamePlayer implements IGamePlayer {
 
private String name = "" ;
 
//通过构造函数传递名称
 
public GamePlayer(String _name){
 
this .name = _name;
 
}
 
//打怪,最期望的就是杀老怪
 
public void killBoss() {
 
System. out .println( this .name + "在打怪!" );
 
}
 
//进游戏之前你肯定要登录吧,这是一个必要条件
 
public void login(String user, String password) {
 
System. out .println( "登录名为" +user + " 的用户 " + this .name + "登录成功!" );
 
}
 
//升级,升级有很多方法,花钱买是一种,做任务也是一种
 
public void upgrade() {
 
System. out .println( this .name + " 又升了一级!" );
 
}
 
}

      在实现类中通过构造函数传递进来玩家姓名,方便进行后期的调试工作。我们通过一个场景类来模拟这样的游戏过程,如代码清单12-3所示。

代码清单12-3 场景类

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 GamePlayer implements IGamePlayer {
 
private String name = "" ;
 
//通过构造函数传递名称
 
public GamePlayer(String _name){
 
this .name = _name;
 
}
 
//打怪,最期望的就是杀老怪
 
public void killBoss() {
 
System. out .println( this .name + "在打怪!" );
 
}
 
//进游戏之前你肯定要登录吧,这是一个必要条件
 
public void login(String user, String password) {
 
System. out .println( "登录名为" +user + " 的用户 " + this .name + "登录成功!" );
 
}
 
//升级,升级有很多方法,花钱买是一种,做任务也是一种
 
public void upgrade() {
 
System. out .println( this .name + " 又升了一级!" );
 
}
 
}

      程序记录了游戏的开始时间和结束时间,同时也记录了在游戏过程中都需要做什么事情,运行结果如下:

开始时间是:2009-8-25 10:45

登录名为zhangSan 的用户 张三登录成功!

张三在打怪!

张三 又升了一级!

结束时间是:2009-8-26 03:40

      运行结果也是我们想要的,记录我这段时间的网游生涯。心理学家告诉我们,人类对于苦难的记忆比对喜悦的记忆要深刻,但是人类对于喜悦是“趋利”性的,每个人都想Happy,都不想让苦难靠近,要想获得幸福,苦难也是再所难免的,我们的网游生涯也是如此,游戏打时间长了,腰酸背痛、眼涩干枯、手臂酸麻,等等,也就是网络成瘾综合症都出来了,其结果就类似吃了那个“一日丧命散”,“筋脉逆流,胡思乱想,而致走火入魔”。那怎么办呢?我们想玩游戏,但又不想碰触到游戏中的烦恼?如何解决呢?

      有办法,现在游戏代练的公司非常多,我把自己的账号交给代练人员,由他们去帮我升级,去打怪,非常好的想法,我们来修改一下类图,如图12-2所示。

clip_image004

图12-2 游戏代练帮忙打怪

      在类图中增加了一个GamePlayerProxy类来代表游戏代练者,它也不能有作弊的方法呀,游戏代练者也是手动打怪呀,因此同样继承IGamePlayer接口,其实现如代码清单12-4所示。

代码清单12-4 代练者

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 GamePlayerProxy implements IGamePlayer {
 
private IGamePlayer gamePlayer = null ;
 
//通过构造函数传递要对谁进行代练
 
public GamePlayerProxy(IGamePlayer _gamePlayer){
 
this .gamePlayer = _gamePlayer;
 
}
 
//代练杀怪
 
public void killBoss() {
 
this .gamePlayer.killBoss();
 
}
 
//代练登录
 
public void login(String user, String password) {
 
this .gamePlayer.login(user, password);
 
}
 
//代练升级
 
public void upgrade() {
 
this .gamePlayer.upgrade();
 
}
 
}

      很简单,首先通过构造函数说明要代谁打怪升级,然后通过手动开始代用户打怪、升级。场景类Client代码也稍作改动,如代码清单12-5所示。

代码清单12-5 改进后的场景类

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 Client {
 
public static void main(String[] args) {
 
//定义一个痴迷的玩家
 
IGamePlayer player = new GamePlayer( "张三" );
 
//然后再定义一个代练者
 
IGamePlayer proxy = new GamePlayerProxy(player);
 
//开始打游戏,记下时间戳
 
System. out .println( "开始时间是:2009-8-25 10:45" );
 
proxy.login( "zhangSan" , "password" );
 
//开始杀怪
 
proxy.killBoss();
 
//升级
 
proxy.upgrade();
 
//记录结束游戏时间
 
System. out .println( "结束时间是:2009-8-26 03:40" );
 
}
 
}

      运行结果也完全相同,还是张三这个用户在打怪,运行结果如下:

开始时间是:2009-8-25 10:45

登录名为zhangSan 的用户 张三登录成功!

张三在打怪!

张三 又升了一级!

结束时间是:2009-8-26 03:40

      是的,没有任何改变,但是你有没有发觉,你的游戏已经在升级,有人在帮你干活了!终于升级到120级,然后基本上是本服无敌手,除了GM外,这个你可惹不起!这就是代理模式。

12.2 代理模式的定义

      代理模式(Proxy Pattern)是一个使用率非常高的模式,其定义如下:

      provide a surrogate or placeholder for another object to control access to it. 为其他对象提供一种代理以控制对这个对象的访问。

      代理模式的通用类图如图12-3所示。

clip_image006

图12-3 代理模式的通用类图

      代理模式也叫做委托模式,它是一项基本设计技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制,在一些著名开源软件中也经常见到它的身影,如Struts2的Form元素映射就采用了代理模式(准确的说是动态代理模式)。我们先看一下类图中的三个角色的定义:

  • Subject抽象主题角色

      抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。

  • RealSubject 具体主题角色

      也叫做被委托角色、被代理角色,它才是冤大头,是业务逻辑的具体执行者。

  • Proxy 代理主题角色

      也叫做委托类、代理类,它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

      我们首先来看Subject抽象主题类的通用源码,如代码清单12-6所示。

代码清单12-6 抽象主题类

1
2
3
4
5
6
7
public interface Subject {
 
//定义一个方法
 
public void request();
 
}

      在接口中我们定义了一个方法request来作为方法的代表,RealSubject对它进行实现,如代码清单12-7所示。

代码清单12-7 真实主题类

1
2
3
4
5
6
7
8
9
10
11
public class RealSubject implements Subject {
 
//实现方法
 
public void request() {
 
//业务逻辑处理
 
}
 
}

      RealSubject是一个正常的业务实现类,代理模式的核心就在代理类上,如代码清单12-8所示。

代码清单12-8 代理类

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
public class Proxy implements Subject {
 
//要代理哪个实现类
 
private Subject subject = null ;
 
//默认被代理者
 
public Proxy(){
 
this .subject = new Proxy();
 
}
 
//通过构造函数传递代理者
 
public Proxy(Object...objects ){
 
}
 
//实现接口中定义的方法
 
public void request() {
 
this .before();
 
this .subject.request();
 
this .after();
 
}
 
//预处理
 
private void before(){
 
//do something
 
}
 
//善后处理
 
private void after(){
 
//do something
 
}
 
}

      看到这里,大家别惊讶,为什么会出现before和after方法,继续看下去,这是一个“引子”,能够引出一个崭新的编程模式。

      一个代理类可以代理多个被委托者或被代理者,因此一个代理类具体代理哪个真实主题角色,是由场景类决定的,当然,最简单的情况就是一个主题类一个代理类,这是最简洁的代理模式。在通常情况下,一个接口只需要一个代理类就可以了,具体代理哪个实现类由高层模块来决定,也就是在代理类的构造函数中传递被代理者,例如我们可以在代理类Proxy中增加如代码清单12-9所示的构造函数。

代码清单12-9 代理的构造函数

1
2
3
4
5
public Proxy(Subject _subject){
 
this .subject = _subject;
 
}

      你要代理谁,产生该代理的实例,然后把被代理者传递进来,该模式在实际的项目应用中比较广泛。

12.3 代理模式的应用

12.3.1 代理模式的优点
  • 职责清晰

      真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

  • 高扩展性

      具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。

  • 智能化

      这在我们以上讲解中还没有体现出来,不过在我们以下的动态代理章节中你就会看到代理的智能化,读者有兴趣也可以看看Struts是如何把表单元素映射到对象上的。

12.3.2 代理模式的应用

我相信第一次接触到代理模式的读者肯定很郁闷,为什么要用代理呀,是的,为什么要用代理?想想现实世界吧,你为什么要找代理律师,你去打官司,为什么要找个律师?因为你不想参与中间过程的是是非非,只要完成自己的答辩就成,其他的比如事前调查、事后追查都由律师来搞定,这就是为了减轻你的负担。代理模式使用非常多,大家可以看看Spring AOP,这是一个非常典型的动态代理。

12.4 代理模式的扩展

12.4.1 普通代理

      在网络上代理服务器设置分为透明代理和普通代理,是什么意思呢?透明代理就是用户不用设置代理服务器地址,就可以直接访问,也就是说代理服务器对用户来说透明的,看不到,不用知道它存在的;普通代理则是需要用户自己设置代理服务器的IP地址,用户必须知道代理的存在。我们设计模式中的普通代理和强制代理也是类似的一种结构,普通代理就是我们要知道代理的存在,也就是类似的GamePlayerProxy这个类的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的,这样解释还是比较复杂,我们还是用实例来讲解。

      首先说普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色,这是比较简单的,我们以上面的例子作为扩展,我自己作为一个游戏玩家,我肯定自己不练级了,也就是场景类不能再直接new一个GamePlayer对象了,它必须由GampePlayerProxy来进行模拟场景,类图修改如图12-4所示。

clip_image008

图12-4 普通代理类图

      改动很小,仅仅修改了两个实现类的构造函数,GamePlayer的构造函数增加了_gamePlayer参数,而代理角色则只要传入代理者名字即可,而不需要说是替哪个对象做代理。GamePlayer类如代码清单12-10所示。

代码清单12-10 普通代理的游戏者

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
public class GamePlayer implements IGamePlayer {
 
private String name = "" ;
 
//构造函数限制谁能创建对象,并同时传递姓名
 
public GamePlayer(IGamePlayer _gamePlayer,String _name) throws Exception{
 
if (_gamePlayer == null ){
 
throw new Exception( "不能创建真是角色!" );
 
} else {
 
this .name = _name;
 
}
 
}
 
//打怪,最期望的就是杀老怪
 
public void killBoss() {
 
System. out .println( this .name + "在打怪!" );
 
}
 
//进游戏之前你肯定要登录吧,这是一个必要条件
 
public void login(String user, String password) {
 
System. out .println( "登录名为" +user + " 的用户 " + this .name + "登录成功!" );
 
}
 
//升级,升级有很多方法,花钱买是一种,做任务也是一种
 
public void upgrade() {
 
System. out .println( this .name + " 又升了一级!" );
 
}
 
}

      在构造函数中,传递进来一个IGamePlayer对象,检查谁能创建真实的角色,当然还可以有其他的限制,比如类名必须为Proxy类等等,读者可以根据实际情况进行扩展。GamePlayerProxy如代码清单12-11所示。

代码清单12-11 普通代理的代理者

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
public class GamePlayerProxy implements IGamePlayer {
 
private IGamePlayer gamePlayer = null ;
 
//通过构造函数传递要对谁进行代练
 
public GamePlayerProxy(String name){
 
try {
 
gamePlayer = new GamePlayer( this ,name);
 
} catch (Exception e) {
 
// TODO 异常处理
 
}
 
}
 
//代练杀怪
 
public void killBoss() {
 
this .gamePlayer.killBoss();
 
}
 
//代练登录
 
public void login(String user, String password) {
 
this .gamePlayer.login(user, password);
 
}
 
//代练升级
 
public void upgrade() {
 
this .gamePlayer.upgrade();
 
}
 
}

      仅仅修改了构造函数,传递进来一个代理者名称,即可进行代理,在这种改造下,系统更加简洁了,调用者只知道代理存在就可以,不用知道代理了谁。同时场景类也稍作改动,如代码清单12-12所示。

代码清单12-12 普通代理的场景类

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
public class GamePlayerProxy implements IGamePlayer {
 
private IGamePlayer gamePlayer = null ;
 
//通过构造函数传递要对谁进行代练
 
public GamePlayerProxy(String name){
 
try {
 
gamePlayer = new GamePlayer( this ,name);
 
} catch (Exception e) {
 
// TODO 异常处理
 
}
 
}
 
//代练杀怪
 
public void killBoss() {
 
this .gamePlayer.killBoss();
 
}
 
//代练登录
 
public void login(String user, String password) {
 
this .gamePlayer.login(user, password);
 
}
 
//代练升级
 
public void upgrade() {
 
this .gamePlayer.upgrade();
 
}
 
}

      运行结果完全相同。在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色爱怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合。当然,在实际的项目中,一般都是通过约定来禁止new一个真实的角色,也是一个非常好的方案。

      注意 普通代理模式的约束问题,尽量通过团队内的编程规范类约束,因为每一个主题类是可被重用的和可维护的,使用技术约束的方式对系统维护是一种非常不利的因素。

12.4.2 强制代理

      强制代理在设计模式中比较另类,为什么这么说呢?一般的思维都是通过代理找到真实的角色,但是强制代理却是要“强制”,你必须通过真实角色查找到代理角色,否则你不能访问,甭管你是通过代理类还是通过直接new一个主题角色类,都不能访问,只有通过真实角色指定的代理类才可以访问,也就是说由真实角色管理代理角色,这么说吧,高层模块new了一个真实角色的对象,返回的却是代理角色,这就好比是你和一个明星比较熟,相互认识,有件事情你需要向她确认一下,于是你就直接拨通了明星的电话:

      “喂,沙比呀,我要见一下XXX导演,你帮下忙了!”

      “不行呀衰哥,我这几天很忙呀,你找我的经纪人吧…”

      郁闷了吧,你是想直接绕过她的代理,谁知道返回的还是她的代理,这就是强制代理,你可以不用知道代理存在,但是你的所作所为还是需要代理为你提供。我们把上面的例子稍作修改就可以完成,如图12-5所示。

clip_image010

图12-5 强制代理类图

      在接口上增加了一个getProxy方法,真实角色GamePlayer可以指定一个自己的代理,除了代理外谁都不能访问。我们来看代码,先看IGamePlayer接口,如代码清单12-13所示。

代码清单12-13 强制代理的接口类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface IGamePlayer {
 
//登录游戏
 
public void login(String user,String password);
 
//杀怪,这是网络游戏的主要特色
 
public void killBoss();
 
//升级
 
public void upgrade();
 
//每个人都可以找一下自己的代理
 
public IGamePlayer getProxy();
 
}

      仅仅增加了一个getProxy方法,指定要访问自己必须通过哪个代理,实现类也要做适当的修改,先看真实角色GamePlayer,如代码清单12-14所示。

代码清单12-14 强制代理的真实角色

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
public class GamePlayer implements IGamePlayer {
 
private String name = "" ;
 
//我的代理是谁
 
private IGamePlayer proxy = null ;
 
public GamePlayer(String _name){
 
this .name = _name;
 
}
 
//找到自己的代理
 
public IGamePlayer getProxy(){
 
this .proxy = new GamePlayerProxy( this .name);
 
return this .proxy;
 
}
 
//打怪,最期望的就是杀老怪
 
public void killBoss() {
 
if ( this .isProxy()){
 
System. out .println( this .name + "在打怪!" );
 
} else {
 
System. out .println( "请使用指定的代理访问" );
 
}
 
}
 
//进游戏之前你肯定要登录吧,这是一个必要条件
 
public void login(String user, String password) {
 
if ( this .isProxy()){
 
System. out .println( "登录名为" +user + " 的用户 " + this .name + "登录成功!" );
 
} else {
 
System. out .println( "请使用指定的代理访问" );;
 
}
 
}
 
//升级,升级有很多方法,花钱买是一种,做任务也是一种
 
public void upgrade() {
 
if ( this .isProxy()){
 
System. out .println( this .name + " 又升了一级!" );
 
} else {
 
System. out .println( "请使用指定的代理访问" );
 
}
 
}
 
//校验是否是代理访问
 
private boolean isProxy(){
 
if ( this .proxy == null ){
 
return false ;
 
} else {
 
return true ;
 
}
 
}
 
}

      增加了一个私有方法,检查是否是自己指定的代理,是指定的代理则允许访问,否则不允许访问。我们再来看代理角色,如代码清单12-15所示。

代码清单12-15 强制代理的代理类

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
public class GamePlayerProxy implements IGamePlayer {
 
private IGamePlayer gamePlayer = null ;
 
//构造函数传递用户名
 
public GamePlayerProxy(IGamePlayer _gamePlayer){
 
this .gamePlayer = _gamePlayer;
 
}
 
//代练杀怪
 
public void killBoss() {
 
this .gamePlayer.killBoss();
 
}
 
//代练登录
 
public void login(String user, String password) {
 
this .gamePlayer.login(user, password);
 
}
 
//代练升级
 
public void upgrade() {
 
this .gamePlayer.upgrade();
 
}
 
//代理的代理暂时还没有,就是自己
 
public IGamePlayer getProxy(){
 
return this ;
 
}
 
}

      代理角色也可以再次被代理,这里我们就没有继续延伸下去了,查找代理的方法就返回自己的实例。代码都写完毕了,我们先按照常规的思路来运行一下,直接new一个真实角色,如代码清单12-16所示。

代码清单12-16 直接访问真实角色

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 Client {
 
public static void main(String[] args) {
 
//定义个游戏的角色
 
IGamePlayer player = new GamePlayer( "张三" );
 
//开始打游戏,记下时间戳
 
System. out .println( "开始时间是:2009-8-25 10:45" );
 
player.login( "zhangSan" , "password" );
 
//开始杀怪
 
player.killBoss();
 
//升级
 
player.upgrade();
 
//记录结束游戏时间
 
System. out .println( "结束时间是:2009-8-26 03:40" );
 
}
 
}

      想想看能运行吗?运行结果如下所示:

开始时间是:2009-8-25 10:45

请使用指定的代理访问

请使用指定的代理访问

请使用指定的代理访问

结束时间是:2009-8-26 03:40

      它要求你必须通过代理来访问,你想要直接访问它,门儿都没有,好,你要我通过代理来访问,那就生产一个代理,如代码清单12-17所示。

代码清单12-17 直接访问代理类

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 Client {
 
public static void main(String[] args) {
 
//定义个游戏的角色
 
IGamePlayer player = new GamePlayer( "张三" );
 
//然后再定义一个代练者
 
IGamePlayer proxy = new GamePlayerProxy(player);
 
//开始打游戏,记下时间戳
 
System. out .println( "开始时间是:2009-8-25 10:45" );
 
proxy.login( "zhangSan" , "password" );
 
//开始杀怪
 
proxy.killBoss();
 
//升级
 
proxy.upgrade();
 
//记录结束游戏时间
 
System. out .println( "结束时间是:2009-8-26 03:40" );
 
}
 
}

      这次能访问吗?还是不行,结果如下所示:

开始时间是:2009-8-25 10:45

请使用指定的代理访问

请使用指定的代理访问

请使用指定的代理访问

结束时间是:2009-8-26 03:40

      同样是不能访问,为什么呢?它不是真实角色指定的对象,这个代理对象是你自己new出来的,当然真实对象不认了,这就好比是那个明星,人家已经告诉你去找她的代理人了,你随便找个代理人能成吗?你必须去找她指定的代理才成!我们修改一下场景类,如代码清单12-18所示。

代码清单12-18 强制代理的场景类

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 Client {
 
public static void main(String[] args) {
 
//定义个游戏的角色
 
IGamePlayer player = new GamePlayer( "张三" );
 
//获得指定的代理
 
IGamePlayer proxy = player.getProxy();
 
//开始打游戏,记下时间戳
 
System. out .println( "开始时间是:2009-8-25 10:45" );
 
proxy.login( "zhangSan" , "password" );
 
//开始杀怪
 
proxy.killBoss();
 
//升级
 
proxy.upgrade();
 
//记录结束游戏时间
 
System. out .println( "结束时间是:2009-8-26 03:40" );
 
}
 
}

      运行结果如下:

开始时间是:2009-8-25 10:45

登录名为zhangSan 的用户 张三登录成功!

张三在打怪!

张三 又升了一级!

结束时间是:2009-8-26 03:40

      OK,可以正常访问代理了。强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色,高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来,代理的管理已经由真实角色自己完成。

12.4.3 代理是有个性的

      一个类可以实现多个接口,完成不同任务的整合,那也就是说代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤,例如游戏代理是需要收费的,升一级需要5元钱,这个计算功能就是代理类的个性,它应该在代理的接口中定义,如图12-6所示。

clip_image012

图12-6 代理类的个性

      增加了一个IProxy接口,其作用是计算代理的费用,否则代理公司不是亏死了,我们先来看IProxy接口,如代码清单12-19所示。

代码清单12-19 代理类的接口

1
2
3
4
5
6
7
public interface IProxy {
 
//计算费用
 
public void count();
 
}

      仅仅一个方法,非常简单,看GamePlayerProxy来的变化,如代码清单12-20所示。

代码清单12-20 代理类

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
public class GamePlayerProxy implements IGamePlayer,IProxy {
 
private IGamePlayer gamePlayer = null ;
 
//通过构造函数传递要对谁进行代练
 
public GamePlayerProxy(IGamePlayer _gamePlayer){
 
this .gamePlayer = _gamePlayer;
 
}
 
//代练杀怪
 
public void killBoss() {
 
this .gamePlayer.killBoss();
 
}
 
//代练登录
 
public void login(String user, String password) {
 
this .gamePlayer.login(user, password);
 
}
 
//代练升级
 
public void upgrade() {
 
this .gamePlayer.upgrade();
 
this .count();
 
}
 
//计算费用
 
public void count(){
 
System. out .println( "升级总费用是:150元" );
 
}
 
}

      实现了IProxy接口,同时在upgrade方法中调用该方法,完成费用结算,其他的类都没有任何改动,运行结果如下:

开始时间是:2009-8-25 10:45

登录名为zhangSan 的用户 张三登录成功!

张三在打怪!

张三 又升了一级!

升级总费用是:150元

结束时间是:2009-8-26 03:40

      好了,代理公司也赚钱了,我的游戏也升级了,皆大欢喜。代理类不仅仅是都可以有自己的运算方法,通常的情况下代理的职责并不一定单一,它可以组合其他的真实角色,也可以实现自己的职责,比如计算费用。代理类可以为真实角色预处理消息、过滤消息、消息转发、事后处理消息等功能,当然一个代理类,可以代理多个真实角色,并且真实角色之间可以有耦合关系,读者可以自行扩展一下。

12.4.4 虚拟代理

      虚拟代理(Virual Proxy)听着很复杂,其实非常简单,我们只要把代理模式的通用代码稍微修改一下就成为虚拟代理,修改后的代理类如代码清单12-21所示。

代码清单12-21 虚拟代理类

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 Proxy implements Subject {
 
//要代理哪个实现类
 
private Subject subject;
 
//实现接口中定义的方法
 
public void request() {
 
//判断一下真实主题是否初始化
 
if (subject == null ){
 
subject = new RealSubject();
 
}
 
subject.request();
 
}
 
}

      在需要的时候才初始化主题对象,可以避免被代理对象较多而引起的初始化缓慢的问题,它的缺点就是需要在每个方法中判断主题对象是否被创建,这就是虚拟代理,非常简单。

12.4.5 动态代理

      放在最后讲的一般都是压轴大戏,动态代理就是如此,上面的章节都是一个引子,动态代理才是重头戏。嘛是动态代理?动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理那一个对象,相对的来说,自己写代理类的方式就是静态代理。本章节的核心部分就在动态代理上,现在有一个非常流行的名称叫做:面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制,既然这么重要,我们就来看看动态代理是如何实现的,还是以打游戏为例,类图修改一下以实现动态代理,如图12-7所示。

clip_image014

图12-7 动态代理

      在类图中增加了一个InvocationHanlder接口和GamePlayIH类,作用就是产生一个对象的代理对象,其中InvocationHanlder是JDK提供的动态代理接口,对被代理类的方法进行代理。我们来看程序,接口保持不变,实现类也没有变化,请参考代码清单12-1、12-2所示。我们来看DynamicProxy类,如代码清单12-22所示。

代码清单12-22 动态代理类

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 class GamePlayIH implements InvocationHandler {
 
//被代理者
 
Class cls = null ;
 
//被代理的实例
 
Object obj = null ;
 
//我要代理谁
 
public GamePlayIH(Object _obj){
 
this .obj = _obj;
 
}
 
//调用被代理的方法
 
public Object invoke(Object proxy, Method method, Object[] args)
 
throws Throwable {
 
Object result = method.invoke( this .obj, args);
 
return result;
 
}
 
}

      其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。我们来详细讲解一下InvocationHanlder接口,动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”,那各位读者想想看,动态代理怎么才能实现被代理接口中的方法呢?默认情况下所有的方法返回值都是空的,是的,代理已经实现它了,但是没有任何的逻辑含义,那怎么办?好办,通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。

      我们接下来看看场景类,如代码清单12-23所示。

代码清单12-23 动态代理的场景类

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
public class Client {
 
public static void main(String[] args) throws Throwable {
 
//定义一个痴迷的玩家
 
IGamePlayer player = new GamePlayer( "张三" );
 
//定义一个handler
 
InvocationHandler handler = new GamePlayIH(player);
 
//开始打游戏,记下时间戳
 
System. out .println( "开始时间是:2009-8-25 10:45" );
 
//获得类的class loader
 
ClassLoader cl = player.getClass().getClassLoader();
 
//动态产生一个代理者
 
IGamePlayer proxy = (IGamePlayer)Proxy.newProxyInstance(cl, new Class[]{IGamePlayer. class },handler);
 
//登录
 
proxy.login( "zhangSan" , "password" );
 
//开始杀怪
 
proxy.killBoss();
 
//升级
 
proxy.upgrade();
 
//记录结束游戏时间
 
System. out .println( "结束时间是:2009-8-26 03:40" );
 
}
 
}

      很奇怪是吗?不要着急,学习是一个循序渐进的过程,继续看下去,我知道你的疑惑了。其运行结果如下:

开始时间是:2009-8-25 10:45

登录名为zhangSan 的用户 张三登录成功!

张三在打怪!

张三 又升了一级!

结束时间是:2009-8-26 03:40

      我们还是让代练者帮我们打游戏,但是我们既没有创建代理类,也没有实现IGamePlayer接口,这就是动态代理。别急,动态代理可不仅仅就这么多内容,还有更重要的,如果我们想在游戏登陆后发一个信息给我,防止账号被人盗用嘛,该怎么处理?直接修改被代理类GamePlayer?这不是一个好办法,好办法如代码清单12-24所示。

代码清单12-24 修正后的动态代理

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 GamePlayIH implements InvocationHandler {
 
//被代理者
 
Class cls = null ;
 
//被代理的实例
 
Object obj = null ;
 
//我要代理谁
 
public GamePlayIH(Object _obj){
 
this .obj = _obj;
 
}
 
//调用被代理的方法
 
public Object invoke(Object proxy, Method method, Object[] args)
 
throws Throwable {
 
Object result = method.invoke( this .obj, args);
 
//如果是登录方法,则发送信息
 
if (method.getName().equalsIgnoreCase( "login" )){
 
System. out .println( "有人在用我的账号登陆!" );
 
}
 
return result;
 
}
 
}

      看黑体部分,只要在代理中增加一个判断就可以决定是否要发送信息,运行结果如下:

开始时间是:2009-8-25 10:45

登录名为zhangSan 的用户 张三登录成功!

有人在用我的账号登陆!

张三在打怪!

张三 又升了一级!

结束时间是:2009-8-26 03:40

      That’s very nice。 有人用我的账号就发送一个信息,然后看看自己的账号是不是被人盗了,非常好的方法,这就是AOP编程,AOP编程没有使用什么新的技术,但是它对我们的设计、编码有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通过AOP的方式切过去。既然动态代理是如此的诱人,我们来看看通用动态代理模型,类图如图12-7所示。

clip_image016

图12-8 动态代理通用类图

      很简单,两条独立发展的线路,动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系,通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务,我们先来看Subject接口,如代码清单12-25所示。

代码清单12-25 抽象主题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface Subject {
 
//业务操作
 
public void doSomething(String str);
 
}
 
其中的doSomething是一个标示方法,可以有多个逻辑处理方法,实现类如代码清单12-25所示。
 
代码清单12-26 真实主题
 
public class RealSubject implements Subject {
 
//业务操作
 
public void doSomething(String str) {
 
System. out .println( "do something!---->" + str);
 
}
 
}

      重点是我们的MyInvocationHandler,如代码清单12-27所示。

代码清单12-27 动态代理的Handler类

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 MyInvocationHandler implements InvocationHandler {
 
//被代理的对象
 
private Object target = null ;
 
//通过构造函数传递一个对象
 
public MyInvocationHandler(Object _obj){
 
this .target = _obj;
 
}
 
//代理方法
 
public Object invoke(Object proxy, Method method, Object[] args)
 
throws Throwable {
 
//执行被代理的方法
 
return method.invoke( this .target, args);
 
}
 
}

      非常简单,所有通过动态代理实现的方法全部通过invokve方法调用。DynamicProxy代码如代码清单12-28所示。

代码清单12-28 动态代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DynamicProxy<T> {
 
public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){
 
//寻找JoinPoint连接点,AOP框架使用元数据定义
 
if ( true ){
 
//执行一个前置通知
 
( new BeforeAdvice()).exec();
 
}
 
//执行目标,并返回结果
 
return (T)Proxy.newProxyInstance(loader,interfaces, h);
 
}
 
}

      在这里插入了较多的AOP术语,在什么地方(连接点)执行什么行为(通知),我们在这里实现了一个简单的横切面编程,读者有经验的话可以看看AOP的配置文件就会明白这段代码的意义了。我们来看通知Advice,也就是我们要切入的类,比较简单,接口和实现如代码清单12-29所示。

代码清单12-29 通知接口及实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface IAdvice {
 
//通知只有一个方法,执行即可
 
public void exec();
 
}
 
public class BeforeAdvice implements IAdvice{
 
public void exec(){
 
System. out .println( "我是前置通知,我被执行了!" );
 
}
 
}

      最后就是看我们怎么调用了,如代码清单12-30所示。

代码清单12-30 动态代理的场景类

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) {
 
//定义一个主题
 
Subject subject = new RealSubject();
 
//定义一个Handler
 
InvocationHandler handler = new MyInvocationHandler(subject);
 
//定义主题的代理
 
Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(),handler);
 
//代理的行为
 
proxy.doSomething( "Finish" );
 
}
 
}

      运行结果如下所示:

我是前置通知,我被执行了!

do something!---->Finish

      好,所有的程序都看完了,我们回过头来看看程序是怎么实现的。在DynamicProxy类中,我们有这样的方法:

      this.obj = Proxy.newProxyInstance(c.getClassLoader(), c.getInterfaces(), new MyInvocationHandler(_obj));

      该方法是重新生成了一个对象,为什么要重新生成?你要使用代理呀,注意c.getInterfaces()这句话,这是非常有意思的一句话,是说查找到该类的所有接口,然后实现接口的所有方法,当然了,方法都是空的,由谁具体负责接管呢?是new MyInvocationHandler(_Obj)这个对象,于是清楚了:一个类的动态代理类是这样的一个类,由InvocationHandler的实现类实现所有的方法,由其invoke方法接管所有方法的实现,其动态调用过程如图12-9所示。

clip_image018

图12-9 动态代理调用过程示意图

      读者可能注意到我们以上的代码还有更进一步的扩展余地,那当然了,注意看DynamicProxy类,它是一个通用类,不具有业务意义,如果我们再产生一个实现类是不是就很有意义了呢?如代码清单12-31所示。

代码清单12-31 具体业务的动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SubjectDynamicProxy extends DynamicProxy{
 
public static <T> T newProxyInstance(Subject subject){
 
//获得ClassLoader
 
ClassLoader loader = subject.getClass().getClassLoader();
 
//获得接口数组
 
Class<?>[] classes = subject.getClass().getInterfaces();
 
//获得handler
 
InvocationHandler handler = new MyInvocationHandler(subject);
 
return newProxyInstance(loader, classes, handler);
 
}
 
}

      如此扩展以后,高层模块对代理的访问会更加简单,如代码清单12-32所示。

代码清单12-32 场景类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Client {
 
public static void main(String[] args) {
 
//定义一个主题
 
Subject subject = new RealSubject();
 
//定义主题的代理
 
Subject proxy = SubjectDynamicProxy.newProxyInstance(subject);
 
//代理的行为
 
proxy.doSomething( "Finish" );
 
}
 
}

      是不是更加简单了?可能读者就要提问了,这样与静态代理还有什么区别?都是需要实现一个代理类,有区别,注意看父类,动态代理的主要意图就是解决我们常说的“审计”问题,也就横切面编程,在不改变我们已有代码结构的情况下增强或控制对象的行为。

      注意 要实现动态代理的首要条件是:被代理类必须实现一个接口,回想一下刚刚的分析吧。当然了,现在也有很多技术如CGLIB可以实现不需要接口也可以实现动态代理的方式。

      再次说明,以上的动态代理是一个通用代理框架,如果你想设计自己的AOP框架,完全可以在此基础上扩展,我们设计的是一个通用代理,只要有一个接口,一个实现类,就可以使用该代理,完成代理的所有功效。

12.5 最佳实践

      代理模式应用的非常广泛,大到一个系统框架、企业平台,小到代码片段、事务处理,稍不留意就用到代理模式,可能该模式是大家接触最多的模式,而且有了AOP大家写代理就更加简单了,有类似Spring AOP和AspectJ这样非常优秀的工具,拿来主义即可,我们还自己写代理干嘛!不过,大家可以看看源代码,特别是调试时,只要看到类似$Proxy0这样的结构,你就应该知道这是一个动态代理了。

友情提醒,在学习AOP框架时,弄起初几个名词就成:切面(Aspect)、切入点(JoinPoint)、通知(Advice)、织入(Weave)就足够了,理解了这几个名词,你就可以对AOP游刃有余了!



FROM:  http://www.cnblogs.com/cbf4life/archive/2010/01/27/1657438.html



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

代理模式 【设计模式之禅作者】 的相关文章

  • Python爬虫批量访问突破访问限制封禁的方法

    坑述 数据采集常用的方法是写一个爬虫程序向网络服务器请求数据 通常是用HTML表单或其他网页文件 然后对数据进行解析 提取需要的信息 然而 批量访问时会有访问限制 更会认为频繁访问是恶意攻击 对ip进行封禁 导致我们的爬虫程序被终止 tim
  • 【css】动画:立方体相册

  • 热词科普--关于前后端分离的易懂解析

    目录 一 什么是前后端分离 二 为什么要前后端分离 未分离时期 工作方式 半分离时期 三 前后端分离的优点 四 前后端分离的缺点 五 什么场景下可以考虑前后端分离 六 基于Vue Springboot的前后端分离 Spring Boot 简
  • 小程序上传图片报uploadFile:fail parameter error: parameter.filePat…parameter.name should be String instead o

    小程序上传图片报uploadFile fail parameter error parameter filePat parameter name should be String instead of Array 阐述 原因是上传的name
  • nodejs + pkg+ puppeteer 路径问题以及 Passed function is not well-serializable 问题

    最近在使用 puppeteer 写自动化程序 发现几个问题在这里吧解决方案分享给大家 pkg 打包后的路径问题 具体路径详情看 https blog csdn net u012211003 article details 112872859
  • jQuery Dialog 弹出层对话框插件演示

    原文地址 http blog csdn net fer ba article details 7067352 dialog jquery function iframe class filter 目录 基本操作 默认的 html view
  • 【Java比较学习】重写equals方法的安全写法

    重写equals方法的正确打开方式 正文开始 Assassin 目录 重写equals方法的正确打开方式 1 什么是equals方法 1 1 equals方法 2 为什么要重写equals方法 2 1 举个例子吧 3 分析equals源码
  • linux运维ansible入门

    bilibili视频地址 ansible入门 介绍 无主无从 开箱即用 用完就走 只要能用ssh即可使用ansible 总结 分成两部分 主机 ansible 模块 主机清单 ssh playbook 完成复杂部署 使用yum安装 ansi
  • Vite和webpack区别

    Vite 和 Webpack 都是前端构建工具 Vite 是一个基于 ES modules 的构建工具 而 Webpack 则是一个 Web 应用程序的静态模块打包器 以下是 Vite 和 Webpack 之间的一些区别 构建方式 Vite
  • 微信小程序:wx.getUserInfo()获取用户信息报错

    在使用wx getUserInfo 来获取用户相关信息报错失败 为优化用户体验 使用 wx getUserInfo 接口直接弹出授权框的开发方式将逐步不再支持 从2018年4月30日开始 小程序与小游戏的体验版 开发版调用 wx getUs
  • 微服务架构的核心要点和实现原理

    摘要 本文中 我们将进一步理解微服务架构的核心要点和实现原理 为读者的实践提供微服务的设计模式 以期让微服务在读者正在工作的项目中起到积极的作用 微服务架构中职能团队的划分 传统单体架构将系统分成具有不同职责的层次 对应的项目管理也倾向于将
  • 2023备战金三银四,Python自动化软件测试面试宝典合集(六)

    马上就又到了程序员们躁动不安 蠢蠢欲动的季节 这不 金三银四已然到了家门口 元宵节一过后台就有不少人问我 现在外边大厂面试都问啥 想去大厂又怕面试挂 面试应该怎么准备 测试开发前景如何 面试 一个程序员成长之路永恒绕不过的话题 每每到这个时
  • 最小树形图的环收缩

    气死我了 最近学习关于最小树形图的问题 无论是CSDN还是博客园 真就是天下文章一大抄 图都用一模一样的 又复杂又高糊 我只是想搞懂存在环 这个环是怎么收缩的 结果给我看了屎一样图和文字表达 一气之下 写一个短文好还描述一下环是怎么收缩成点
  • AttributeError: ‘NoneType‘ object has no attribute ‘xxx‘v

    分析 在Python中 NoneType是一个类型 如同int float一样 如 gt gt gt a None gt gt gt type a
  • vi如何使用

    个人发现好多企业内部运维工程师 很少有人懂得VI的使用技巧 通常都是一顿按 上下左右键 搞得我好郁闷 我本人整理了一份VI的使用技巧分享下 希望大家正确 高效 去工作 Vi 1 Vi 10 进入Vi 以第10行为行首 2 Vi 空格 最后一
  • 以太坊学习笔记:私有链搭建操作指南

    原文链接 https my oschina net u 2349981 blog 865256 讲解的内容非常详细 熟悉搭建以太坊私有链的相关操作 学习了 摘要 详解以太坊私有链搭建过程以及一些常用的操作 虽然以太坊是一个公有链系统 但是我
  • 学算法,先从二分查找开始吧

    总纲 思路很简单 细节是魔鬼 分为三个常用场景 寻找一个数 寻找左侧边界 寻找右侧边界 最后给出力扣上的题目例子 还可以在GitHub上观看哦 AlgorithmNotes 基础框架 int binarySearch int nums in
  • MySQL系列11——索引效率测试

    MySQL索引效率测试 一 以mysql添加索引和不添加索引为例 测试数据的插入速度和查询速度 1 首先创建数据表和数据库 在cmd命令行中执行 create database TestDataSpeed charset utf8 use

随机推荐

  • onnxruntime安装与使用(附实践中发现的一些问题)

    关于onnxruntime的一些基本参考链接 onnxruntime官方文档 将pytorch模型转换为onnx模型并用onnxruntime进行推理 Pytorch官方文档 一 onnxruntime安装 1 使用CPU 如果只用CPU进
  • ice服务器回归系统,便携式的ICE中继服务器及其方法

    主权项 1 一种便携式的ICE中继服务器 该ICE中继服务器是分别与一网络终 端装置及一NAT路由器相连接 其特征在于 至少包括 一储存单元 用以储存依据ICE协议标准所提供的多个候选接入点 一第一输出入端口 与该网络终端装置相连接 用以接
  • Mysql中key 、primary key 、unique key 与index区别

    索引被用来快速找出在一个列上用一特定值的行 没有索引 MySQL不得不首先以第一条记录开始并然后读完整个表直到它找出相关的行 表越大 花费时间越多 如果表对于查询的列有一个索引 MySQL能快速到达一个位置去搜寻到数据文件的中间 没有必要考
  • 蚂蚁开放联盟链合约开发入门

    蚂蚁链简介 蚂蚁链包含多个产品 合约体验链 开放联盟链 联盟链 合约体验链 一条本地开发体验链 供您免费体验本地开发的全流程 网址 联盟链 可以创建或加入联盟 门槛较高 网址 开放联盟链 面向企业和开发者提供的 无需搭链 快速上链 接近公链
  • 菜鸟学Java public static void main(String[] args) 是什么意思?

    目录 1 经典程序解析 2 包里面的多个类 2 1 全限定名调用程序 2 2 包名的层数 2 3 类中main位置的选择 2 4 不同包中类的调用 3 void位置返回值 4 同一个包内的类调用 5 public位置选择 6 String
  • ES6:Promise详解

    ES6 Promise详解 1 概念 2 Promise有3种状态 3 Promise和async和await的区别 4 Promise API 5 Promise是用来解决两个问题的 6 Promise的三个缺点 7 Promise的两个
  • 性能测试工具有哪些?原理是什么?怎么选择适合的工具?

    前言 本篇文章主要简单总结下性能测试工具的原理以及如何选型 性能测试和功能测试不同 性能测试的执行是基本功能的重复和并发 需要模拟多用户 在性能测试执行时需要监控指标参数 同时性能测试的结果不是那么显而易见 需要对数据进行分析 这些特点决定
  • 【ROS】ROS 初学笔记

    ROS是什么
  • 万能密码原理和总结

    我们所常说的 or or 万能密码的原理是这样的 SQL语句sql select from user where username username and pass pass 当我们用户名和密码都填写 or or 提交的时候 即此时语句中
  • Kubernetes中的etcd访问

    前言 Kubernetes中的etcd访问 正常安装了k8s 没有特意去安装etcd 利用K8s中附带的etcd 感受一下etcd的读写操作 提示 以下是本篇文章正文内容 下面案例可供参考 一 etcd是什么 etcd是一个分布式的key
  • UE4换装系统(合并骨骼模型)

    前面那篇UE4换装系统https blog csdn net luomogenhaoqi article details 88350580 事实上每个身体模型还是各自渲染 现在介绍把每个身体模型合并输出一个模型 把Lod 材质 网格等合并
  • 软件工程第二版(判断题答案)

    判断题 第一章 软件就是程序 编写软件就是编写程序 软件危机的主要表现是软件需求增加 软件价格上升 软件工程学科出现的主要原因是软件危机的出现 软件工具的作用是为了延长软件产品的寿命 第二章 瀑布模型的最大优点是将软件开发的各个阶段划分得十
  • Notepad++ 配置 支持jquery、html、css、javascript、php代码提示

    官网下载 http notepad plus plus org 获取插件的方法 打开软件 窗口工具栏有有一个问号 点获取插件 我使用的插件 安装方法都是官方的方法 QuickText v0 2 1 zip 自定义缩写词 按快捷键后输出 定义
  • Java网络编程Socket(使用字节流)

    套接字 Socket 开发网络应用程序被广泛采用 以至于成为事实上的标准 Socket通信原理 通信的两端都要有Socket 是两台机器间通信的端点 网络通信其实就是Socket间的通信 Socket允许程序把网络连接当成一个流 数据在两个
  • java private 构造函数_JAVA private私有类的 默认构造函数 的生成过程

    如果一个类没有定义任何构造函数 则编译器将生成一个缺省的构造函数 该构造函数的访问修改符和类的访问修改符相同 例如 class test将生成test 构造函数 public class test将生成public test 构造函数 在使
  • JPA使用雪花算法生成主键ID

    实现方式 通过 GenericGenerator注解自定义主键生成策略 需要实现org hibernate id IdentifierGenerator接口 根据官网例子进行改造 官网链接 https docs jboss org hibe
  • Qt中多线程的使用(二)

    线程池 当线程的任务量比较大时 频繁创建和销毁线程会有很大的内存开销 此时使用QThread的方法就不合适 应该使用线程池QThreadPool QThread适用于常驻内存的任务 QThreadPool适用于不常驻内存 任务量比较大的情况
  • Element-UI官方文档阅读笔记(VUE)—持续更新中····

    前言 本人前端新手一枚 目前工作中接触Element UI较多 但其中很多组件布局什么的都不是很清楚 所以想稍微花点时间简单过一遍Element UI官方文档 并作以记录 其中有什么不对的地方 还请各位路过的大佬不吝赐教 以下内容按elem
  • Hive建表实例——定义serdeproperties属性

    创建table时 直接定义serdeproperties属性 create table wzhg c0 string c1 string c2 string row format serde org apache hadoop hive c
  • 代理模式 【设计模式之禅作者】

    代理模式 12 1 我是游戏至尊 2007年 感觉很无聊 于是就玩了一段时间的网络游戏 游戏名就不说了 要不就有做广告的嫌疑 反正就是打怪 升级 砍人 被人砍 然后继续打怪 升级 打怪 升级 我花了两个月的时间升级到80级 已经很有成就感了