适配器模式
适配器模式(Adapter Pattern) 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
优点
可以让任何两个没有关联的类一起运行。
提高了类的复用。
缺点
- 过多地使用适配器,会让系统非常零乱,不易整体进行把握。
不知道有没有人遇到过这种情况,同一种功能有好几套第三方控件可以实现,但是更换控件貌似成本很大,比如说:原本使用FastNet的NMFTP组件来传输文件,后来改用Indy组件。而这两个控件又毫无关联,诸如此类的事情,这种情况就可以真的可以考虑使用适配器模式
适配器模式有类的适配器模式和对象的适配器模式两种结构形式,下面的内容是摘自Delphi模式编程一书的内容
书是老书,但是内容个人感觉不错
# 类适配器模式
请原谅我的盗图行为
适配过程包含了以下参与者:
目标(ITarget):定义一个客户端使用的特定接口。(例如有一个看小电影的软件可以播放影音文件的功能)
客户(TClient):使用目标接口,与和目标接口一致的对象合作。(你正好使用这种功能(播放),但是这个软件不支持你的文件格式)
被适配者(TAdaptee):一个现存的需要匹配的接口。(幸运的是这个软件有一个对外接口可以支持对接实现你需要的文件格式)
适配器(TAdapter):负责将TAdaptee的接口转换成ITarget的接口。(碰巧你找到一个插件它实现了你这种文件格式的播放功能)适配器是一个具体的类,是本模式的核心所在
整体过程差不多就这样,因为书面化的问题不容易理解,但是删掉我又怕自己表达不准确,所以在后面举了个例子
# 代码实现
客户端测试代码
program AdapterPattern;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
AdapterBasicWithClassUnit in 'AdapterBasicWithClassUnit.pas';
procedure TestAdapterClass();
//相当于客户端
begin
//获取适配器,相当于要给手机充电要先拿到充电器
var Target := TAdapter.Create;
//发起请求
Target.Request();
end;
begin
try
TestAdapterClass();
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
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
类适配模式的核心代码
unit AdapterBasicWithClassUnit;
{适配器模式-类}
interface
type
ITarget = interface(IInterface)
['{6B9A4DB9-7DCB-4C6F-B1AD-D46E3194D5D3}']
{目标}
procedure Request;
end;
TAdaptee = class(TInterfacedObject)
{被适配类}
public
procedure SpecificRequest;
end;
TAdapter = class(TAdaptee, ITarget)
{适配器}
public
procedure Request;
end;
implementation
{ TAdapter }
procedure TAdapter.Request;
begin
Writeln('适配成功,调用适配器的方法');
SpecificRequest;
end;
{ TAdaptee }
procedure TAdaptee.SpecificRequest;
begin
Writeln('被适配类的源方法的代码');
end;
end.
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
上面代码是Delphi模式编程一书的代码,我只是做了注释说明。
细心的朋友可能会发现一个很有意思的事情。这里面的接口定义它使用了IInterface,而这和我以前的认知不太一样,我在录制第一季视频的时候也并不知道有这么一个类型。然后我以为自己学的东西有误,下面是这两个类型的官方文档
https://docwiki.embarcadero.com/Libraries/Sydney/en/System.IInterface
https://docwiki.embarcadero.com/Libraries/Sydney/en/System.IUnknown
在IUnknown的文档中有这么一段描述
IUnknown is a base interface with features that are important to C++ programmers and also programmers who utilize COM objects.
Note: In Delphi code,IUnknown is simply another name for IInterface. Descending from IUnknown instead of IInterface informs the Delphi compiler that the interface must be compatible with COM objects. Cross-platform applications can use interfaces descended from IUnknown, but must not use them to access COM objects, which are only available under Windows.
译文如下
IUnknown是一个基本接口,具有与C ++程序员的重要功能以及使用COM对象的程序员。
注意:在Delphi代码中,IUnknown只是IInterface的另一个名称。从IUnknown而不是IInterface下降会通知Delphi编译器该接口必须与COM对象兼容。跨平台应用程序可以使用从IUnknown派生的接口,但不能使用它们访问COM对象,因为COM对象仅在Windows下可用。
这段描述有点儿意思,乍看之下根本不知道说的是啥,不过大概的意思是IUnknown下的子类是可以做跨平台应用但是不能使用它做COM编程。
虽然第一季的时候使用了大漠插件,但是我还是不懂到底COM编程。编程的学习之路漫长而孤独,愿你我能够同行
# 对象适配器模式
类的适配器模式是一种静态的结构模式,而且使用多重继承关系连接到Adapter类,所以这种方式在Delphi中并没有对象的适配器模式应用广泛。
参与者和含义并没有什么变化
# 代码实现
客户端调用代码
procedure TestAdapterObject();
begin
//这样写的作用是为了区分不同单元类同名的情况
var Target := AdapterBasicWithObjectUnit.TAdapter.Create;
//发起请求
Target.Request();
end;
begin
try
TestAdapterObject();
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
具体代码单元
unit AdapterBasicWithObjectUnit;
interface
type
TTarget = class abstract
{目标,最终的目的,类似于播放功能}
public
procedure Request; virtual; abstract;
end;
TAdaptee = class(TObject)
{被适配类,具体的实现,类似于可以播放特定类型视频的插件}
public
procedure SpecificRequest;
end;
TAdapter = class(TTarget)
{适配器类,将目标和被适配类关联起来}
private
//和类的适配器写法不同的就是这里了
FAdaptee: TAdaptee;
public
constructor Create;
procedure Request; override;
end;
implementation
{ TAdaptee }
procedure TAdaptee.SpecificRequest;
begin
Writeln('我可以处理特殊的请求');
end;
{ TAdapter }
constructor TAdapter.Create;
begin
//关联
FAdaptee := TAdaptee.Create;
end;
procedure TAdapter.Request;
begin
//TODO 可以编写具体的功能代码
Writeln('适配成功,调用适配器的方法');
FAdaptee.SpecificRequest;
end;
end.
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
# 综合案例
适配器模式的应用场景很多,其中最经典(最常用)的例子就是电源适配器(充电器)的例子,家用电的电压为220V,而我们常用的电器电压不等,比如手机5V笔记本20V(只是举例可能有误差)通过电源适配器也就是咱们俗称的充电器可以将220V转换成不同的电压,这就是适配器的一种实现
我在菜鸟教程上看到一个例子,感觉挺不错的,多媒体适配播放,类图如下
AudioPlayer 可以播放 mp3 格式的音频文件。
AdvancedMediaPlayer 可以播放MP4和其他的影音格式的文件
通过适配器模式将MP3和影音文件的播放桥接起来实现支持多种格式的播放
播放MP3的功能可以看做是系统的原有功能,而MediaAdepter部分可以看作是外挂(扩展)出来的一部分功能
# 代码实现
客户端代码
program AdapterPattern;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
MediaAdapterUnit in 'MediaAdapterUnit.pas';
procedure MediaAdapter();
begin
var AudioPlayer := TAudioPlayer.Create();
AudioPlayer.play('MP3', '挪威的森林.mp3');
AudioPlayer.play('MP4', '挪威的森林.mp4');
AudioPlayer.play('vlc', '挪威的森林.vlc');
AudioPlayer.play('avi', '水手.avi');
end;
begin
try
MediaAdapter();
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
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
核心代码单元
unit MediaAdapterUnit;
interface
type
IAdvancedMediaPlayer = interface
{高级媒体播放器}
['{E2916008-25A7-4E34-8B80-0851A4A429C9}']
procedure PlayVlc(FileName: string);
procedure PlayMp4(FileName: string);
end;
TVlcPlayer = class(TInterfacedObject, IAdvancedMediaPlayer)
{高级播放器的具体实现}
public
procedure PlayVlc(FileName: string); overload;
procedure PlayMp4(FileName: string); overload;
end;
TMp4Player = class(TInterfacedObject, IAdvancedMediaPlayer)
{高级播放器的具体实现}
public
procedure PlayVlc(FileName: string); overload;
procedure PlayMp4(FileName: string); overload;
end;
IMediaPlayer = interface
{媒体播放器,对外的播放接口}
['{6053CA47-F7AF-418D-8814-3DC6810303E0}']
procedure Play(AudioType, FileName: string);
end;
TMediaAdapter = class(TInterfacedObject, IMediaPlayer)
{MediaPlayer 接口的适配器类}
private
{通过对象的形式关联高级播放器}
FAdvancedMediaPlayer: IAdvancedMediaPlayer;
public
procedure Play(AudioType, FileName: string); overload;
constructor Create(AudioType: string); overload;
end;
TAudioPlayer = class(TInterfacedObject, IMediaPlayer)
{音频播放器,默认只能播放MP3}
private
{关联适配器之后,具备高级播放器的功能}
FMediaAdapter: TMediaAdapter;
public
procedure Play(AudioType, FileName: string); overload;
end;
implementation
{ TAudioPlayer }
procedure TAudioPlayer.Play(AudioType, FileName: string);
begin
if 'MP3' = AudioType then
Writeln('默认支持的类型:MP3')
else if ('MP4' = AudioType) or ('vlc' = AudioType) then begin
FMediaAdapter := TMediaAdapter.Create(AudioType);
FMediaAdapter.Play(AudioType, FileName);
end
else
Writeln('该类型暂不支持')
end;
{ TMediaAdapter }
constructor TMediaAdapter.Create(AudioType: string);
begin
if (AudioType = 'MP4') then
FAdvancedMediaPlayer := TMp4Player.Create()
else if (AudioType = 'vlc') then
FAdvancedMediaPlayer := TVlcPlayer.Create()
end;
procedure TMediaAdapter.Play(AudioType, FileName: string);
begin
if ('MP4' = AudioType) then
FAdvancedMediaPlayer.PlayMp4(FileName)
else if ('vlc' = AudioType) then
FAdvancedMediaPlayer.PlayVlc(FileName)
end;
{ TVlcPlayer }
procedure TVlcPlayer.PlayMp4(FileName: string);
begin
//不支持此格式可用空实现
Writeln('不支持此格式可用空实现' + FileName);
end;
procedure TVlcPlayer.PlayVlc(FileName: string);
begin
Writeln('Vlc.PlayVlc播放器播放:' + FileName + #13);
end;
{ TMp4Player }
procedure TMp4Player.PlayMp4(FileName: string);
begin
Writeln('TMp4Player.PlayVlc播放器播放:' + FileName + #13);
end;
procedure TMp4Player.PlayVlc(FileName: string);
begin
//不支持此格式可用空实现
Writeln('不支持此格式可用空实现:' + FileName);
end;
end.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121