刀个刀个刀刀 發表於 2022-2-4 19:12:00

Delphi接口的一些简单介绍

<p><span style="font-size: 15px">Delphi只能单继承,不像C++可以多继承,而接口则为Delphi提供了弹性、让类别能够宣告支持多接口,并加以实现的能力,同时避免因多重继承而可能产生的问题。</span></p>
<p><span style="font-size: 15px">简单的,把接口想成是一个对象拥有多个不同的基础类别。从宏观的角度来看,接口比类别支持了更多不同的面向对象程序设计的模式。实作接口的对象,可以视作它所实作它作的每个接口的多重型态,实际上以接口为基础的模式是很强的。接口比较偏重在于封装,并提供给类别之间一种比继承更宽松一点的连接。</span></p>
<p><span style="font-size: 15px"><strong>接口的使用</strong></span></p>
<p><span style="font-size: 15px">事实上,接口不算是类别,虽然接口可以重组类别,因为类别可以建立实体,但接口不行。接口可以被一个或多个类别加以实作,所有这些实体就可以算是支持了或者实作了该接口。</span></p>
<p><span style="font-size: 15px">接口有一些独特的功能:</span></p>
<p><span style="font-size: 15px">1.接口型别的变量会使用参考计数,跟类别型别的变量不同</span></p>
<p><span style="font-size: 15px">2.类别是从单一前代类别继承而来,但可以实作多个接口</span></p>
<p><span style="font-size: 15px">3.就像所有类别都是自TObject衍生而来,所有的接口都是从IInterfase衍生出来的,两者各归属于独立、正交的架构</span></p>
<p><span style="font-size: 15px">透过不成文的约定,接口的名称以I大写字母开头,跟类别的T大写字母是不同的。</span></p>
<p><strong><span style="font-size: 15px">接口的宣告</span></strong><span style="font-size: 15px">接口的定义就像类别的定义写法,这个定义包含一些方法,但这些方法不用实作,就像在一般类别里面的抽象方法一样。</span></p>
<p><span style="font-size: 15px">以下就是一个接口的定义:</span></p>
<p><span style="font-size: 15px">type</span></p>
<p><span style="font-size: 15px">&nbsp; ICanFly=interface</span></p>
<p><span style="font-size: 15px">&nbsp;&nbsp;&nbsp; function Fly:string;</span></p>
<p><span style="font-size: 15px">end;</span></p>
<p><span style="font-size: 15px">假设每个接口都直接或间接的从基础接口型别继承而来,相对的写法就变成了:</span></p>
<p><span style="font-size: 15px">type</span></p>
<p><span style="font-size: 15px">&nbsp; ICanFly=interface(IInterface)<br></span></p>
<p><span style="font-size: 15px">&nbsp;&nbsp;&nbsp; funciton Fly:string;</span></p>
<p><span style="font-size: 15px">end;</span></p>
<p><span style="font-size: 15px">接口宣告最后一个重点,对接口来说,部分的型别检查是动态进行的,系统要求每一个接口都要具备一个唯一的标识符,或者称为GUID,可以在IDE里面按下Ctrl+Shift+G,编辑环境就会自动帮我们产生一组了。</span></p>
<p><span style="font-size: 15px">type</span></p>
<p><span style="font-size: 15px">&nbsp; ICanFly=interface</span></p>
<p><span style="font-size: 15px">&nbsp;&nbsp;&nbsp; [{D7233EF2-B2DA-444A-9B49-09657417ADB7}]</span></p>
<p><span style="font-size: 15px">&nbsp;&nbsp;&nbsp; function&nbsp; Fly:string;</span></p>
<p><span style="font-size: 15px">end;</span></p>
<p><span style="font-size: 15px">实作界面</span></p>
<p><span style="font-size: 15px">任何类别都可以实作接口(个数不限),只要把它们列在基础类别后面,并且提供这些接口方法的执行程序代码即可:</span></p>
<p><span style="font-size: 15px">type</span></p>
<p><span style="font-size: 15px">&nbsp; TAirplane=class(...,ICanFly)</span></p>
<p><span style="font-size: 15px">&nbsp;&nbsp;&nbsp; function Fly:string;</span></p>
<p><span style="font-size: 15px">end;</span></p>
<p><span style="font-size: 15px">function TAirplane.Fly:string;</span></p>
<p><span style="font-size: 15px">begin</span></p>
<p><span style="font-size: 15px">&nbsp; //actual code</span></p>
<p><span style="font-size: 15px">end;</span></p>
<p><span style="font-size: 15px">当类别实作接口的时候,它必须提供该接口所有的方法(参数也必须要跟接口中该方法的宣告完全相同)的实作,所以在本例中的TAirplane类别就必须实作Fly方法,该方法必须回传一个字符串。假设这个接口也是从一个基础接口衍生而来,这个类别就必须实作出该接口,以及其基础接口的所有方法。</span></p>
<p><span style="font-size: 15px">注:当我们实作接口时,可以选择以静态或虚拟方法来实作。如果准备在衍生类别里面重载方法,使用虚拟方法就有意义。建议需要的时候,把接口的方法宣告为虚拟方法,这样可以保留未来的扩充弹性。</span></p>
<p><span style="font-size: 15px">现在已经定义了一个接口,以及用来实作它的类别。可以为这个类别建立实体,就把它当成一般类别来对待,可以这样写:</span></p>
<p><span style="font-size: 15px">var</span></p>
<p><span style="font-size: 15px">&nbsp; Airplane1:TAirplane;</span></p>
<p><span style="font-size: 15px">begin</span></p>
<p><span style="font-size: 15px">&nbsp; Airplane1:=TAirplane.create;</span></p>
<p><span style="font-size: 15px">&nbsp; try</span></p>
<p><span style="font-size: 15px">&nbsp;&nbsp;&nbsp; Airplane1.Fly;</span></p>
<p><span style="font-size: 15px">&nbsp; finally</span></p>
<p><span style="font-size: 15px">&nbsp;&nbsp;&nbsp; Airplane1.Free;</span></p>
<p><span style="font-size: 15px">&nbsp; end;</span></p>
<p><span style="font-size: 15px">end;</span></p>
<p><span style="font-size: 15px">然而也可以用接口作为型别来宣告变量,这会自动启动参考内存模式(会自动释放):</span></p>
<p><span style="font-size: 15px">var</span></p>
<p><span style="font-size: 15px">&nbsp; Flyer1:ICanFly;</span></p>
<p><span style="font-size: 15px">begin</span></p>
<p><span style="font-size: 15px">&nbsp; Flyer1:=TAirplane.Create;</span></p>
<p><span style="font-size: 15px">&nbsp; Flyer1.Fly;</span></p>
<p><span style="font-size: 15px">end;</span></p>
<p><span style="font-size: 15px">当我们把一个对象指派给接口变量之后,透过as这个特殊指令,运行时间会自动检查该对象是否有实作这个接口。可以把整个指令这样写:</span></p>
<p><span style="font-size: 15px">&nbsp; Flyer1:=TAirplane.create as ICanFly;</span></p>
<p>&nbsp;</p>
<p><span style="font-size: 15px">混合参考的错误</span></p>
<p><span style="font-size: 15px">使用对象的时候,我们应该只透过对象变量或只透过接口变量来存取对象。如果把两种作法混着用会打乱Object Pascal提供的参考计算器制,就可能导致非常难以发现的内存错误。实际上,如果我们决定要用接口,就应该只使用接口型别的变量。</span></p>
<p><span style="font-size: 15px">以下是一个可能发生比较不一样错误的案例。假设我们建立了一个接口,用一个类别来实作它,并且用一个全局过程用这个接口作为参数:</span></p>
<p><span style="font-size: 15px">type</span></p>
<p><span style="font-size: 15px">&nbsp; IMyInterface=interface</span></p>
<p><span style="font-size: 15px">&nbsp; &nbsp; ['{F7BEADFD-ED10-4048-BB0C-5B232CF3F272}']</span></p>
<p><span style="font-size: 15px">&nbsp; &nbsp; procedure Show;</span></p>
<p><span style="font-size: 15px">&nbsp; end;</span></p>
<p><span style="font-size: 15px">&nbsp; TMyIntfObject=class(TInterfacedObject,IMyInterface)</span></p>
<p><span style="font-size: 15px">&nbsp;&nbsp;&nbsp; public</span></p>
<p><span style="font-size: 15px">&nbsp;&nbsp;&nbsp;&nbsp; procedure Show;</span></p>
<p><span style="font-size: 15px">&nbsp; end;</span></p>
<p><span style="font-size: 15px">procedure ShowThat(anIntf:IMyInterface);</span></p>
<p><span style="font-size: 15px">begin</span></p>
<p><span style="font-size: 15px">&nbsp; anIntf.Show;</span></p>
<p><span style="font-size: 15px">end;</span></p>
<p><span style="font-size: 15px">这段程序看起来很直观,而且百分之百正确。可能发生的错误,只会来自与我们呼叫它的方法:</span></p>
<p><span style="font-size: 15px">procedure TForm1.btnMixClick(Sender:TObject);</span></p>
<p><span style="font-size: 15px">var</span></p>
<p><span style="font-size: 15px">&nbsp; anObj:TMyIntfObject;</span></p>
<p><span style="font-size: 15px">begin</span></p>
<p><span style="font-size: 15px">&nbsp; anObj:=TMyIntfObject.create;</span></p>
<p><span style="font-size: 15px">&nbsp; try</span></p>
<p><span style="font-size: 15px">&nbsp;&nbsp;&nbsp; showThat(anObj);</span></p>
<p><span style="font-size: 15px">&nbsp; finally</span></p>
<p><span style="font-size: 15px">&nbsp;&nbsp;&nbsp; anObj.Free;</span></p>
<p><span style="font-size: 15px">&nbsp; end;</span></p>
<p><span style="font-size: 15px">end;</span></p>
<p><span style="font-size: 15px">上面这段程序则是把一个一般的对象传给原本要求接口当参数的过程。假设这个对象没有支持该接口,编译程序也不会对这个过程的使用提出可能发生错误的警告讯息。在这里的问题,是内存管理的方式。一开始,对象的参考计数会被设为0,表示没有参数使用到它。在进入ShowThat过程的时候,参考计数会被加成1.这没问题,程序的呼叫也会正常发生。等程序执行结束的时候,参考计数会被递减为0,所以该对象也就会被正常释放。换句话说,当程序结束,回到呼叫该过程的程序区段时,anObj对象就会被释放,这实在相当尴尬。当我们执行这段程序时,则会发生内存错误(除非我们在支持ARC的行动装置上执行,那么这段程序就会执行的很顺畅)。</span></p>
<p><span style="font-size: 15px">有几个方法可以解决,我们可以手动增加参考计数的数字,并使用一些低阶的技巧。但实际的解法,就是不要把接口跟对象的参考混着用,并只使用接口对象来指向对象。</span></p>
<p><span style="font-size: 15px">procedure TForm1.btnIntfOnlyClick(Sender:TObject);</span></p>
<p><span style="font-size: 15px">var</span></p>
<p><span style="font-size: 15px">&nbsp; anObj:IMyInterface;</span></p>
<p><span style="font-size: 15px">begin</span></p>
<p><span style="font-size: 15px">&nbsp; anObj:=TMyIntfObject.create;</span></p>
<p><span style="font-size: 15px">&nbsp; ShowThat(anObj);</span></p>
<p><span style="font-size: 15px">end;</span></p>
<p><span style="font-size: 15px">在这个特例里,就使用了这个解决这个方案,但在许多其他的状况下,很难发现到正确的写法。再次强调,最终解决方案就是不要把不同型别的参考混在一起使用。</span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp; </p><br><br>
来源:https://www.cnblogs.com/AP0606436/p/15863298.html
頁: [1]
查看完整版本: Delphi接口的一些简单介绍