接口允许您创建代码,该代码定义实现它的类的方法.但是,您无法向这些方法添加任何代码.
抽象类允许您执行相同的操作,同时向方法添加代码.
现在,如果您可以使用抽象类实现相同的目标,为什么我们甚至需要接口的概念?
我被告知它与OO理论有关,从C++到Java,这就是PHP的OO基础.这个概念在Java中有用但在PHP中没有用吗?它只是一种避免在抽象类中乱丢占位符的方法吗?我错过了什么吗?
接口的整个要点是让您可以灵活地让您的类实现多个接口,但仍然不允许多重继承.从多个类继承的问题是多种多样的,其上的维基百科页面总结得很好.
接口是妥协.多重继承的大多数问题都不适用于抽象基类,因此现在大多数现代语言都禁用多重继承,但是调用抽象基类接口并允许类根据需要"实现"多个.
这个概念在面向对象编程中很有用.对我来说,我认为接口是合同.很长一段时间,我的班级和你的班级都同意这个方法签名合同我们可以"接口".至于抽象类,我认为更多的基类会删除一些方法,我需要填写详细信息.
如果已经有抽象类,为什么还需要一个接口? 防止多重继承(可能导致多个已知问题).
其中一个问题是:
"钻石问题"(有时被称为"死亡的致命钻石")是一种歧义,当两个B类和C类继承自A和D类继承自B和C时会产生歧义.如果A中有一个方法B和C已被覆盖,而D不会覆盖它,那么D继承的方法的哪个版本:B的那个版本,还是C的那个版本?
资料来源:https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem
为什么/何时使用界面?
一个例子......世界上所有的汽车都有相同的界面(方法)...... AccelerationPedalIsOnTheRight()
, BrakePedalISOnTheLeft()
. 想象一下,每个汽车品牌都会拥有与其他品牌不同的"方法".宝马将在右侧制动,而本田将在车轮的左侧制动.人们每次购买不同品牌的汽车时都必须了解这些"方法"是如何运作的.这就是为什么在多个"地方"拥有相同的界面是个好主意.
界面为您做什么(为什么有人会使用一个)?界面可以防止您犯"错误"(它确保所有实现特定接口的类都具有接口中的方法).
// Methods inside this interface must be implemented in all classes which implement this interface. interface IPersonService { public function Create($personObject); } class MySqlPerson implements IPersonService { public function Create($personObject) { // Create a new person in MySql database. } } class MongoPerson implements IPersonService { public function Create($personObject) { // Mongo database creates a new person differently then MySQL does. But the code outside of this method doesn't care how a person will be added to the database, all it has to know is that the method Create() has 1 parameter (the person object). } }
这样,该Create()
方法将始终以相同的方式使用.如果我们使用MySqlPerson
班级或MongoPerson
班级并不重要.我们使用方法的方式保持不变(界面保持不变).
例如,它将像这样使用(在我们的代码中的任何地方):
new MySqlPerson()->Create($personObject); new MongoPerson()->Create($personObject);
这样,这样的事情不可能发生:
new MySqlPerson()->Create($personObject) new MongoPerson()->Create($personsName, $personsAge);
记住一个界面并在任何地方使用相同的界面要比多个不同的界面容易得多.
这样,方法的内部Create()
对于不同的类可以是不同的,而不会影响调用此方法的"外部"代码.所有外部代码必须知道的是该方法Create()
有1个参数($personObject
),因为外部代码将使用/调用该方法.外部代码不关心方法内部发生了什么; 它只需要知道如何使用/调用它.
您也可以在没有界面的情况下执行此操作,但如果使用界面,则"更安全"(因为它可以防止您出错).该接口确保该方法Create()
在实现该接口的所有类中具有相同的签名(相同类型和相同数量的参数).这样你就可以确定实现IPersonService
接口的任何类都将拥有该方法Create()
(在本例中)并且只需要1个参数($personObject
)来调用/使用.
实现接口的类必须实现接口所具有的所有方法.
我希望我不会重复太多.
使用接口和抽象类之间的区别更多地与我的代码组织有关,而不是语言本身的强制执行.在为其他开发人员准备代码时,我经常使用它们,以便它们保持在预期的设计模式中.接口是一种"按合同设计",您的代码同意响应一组规定的API调用,这些调用可能来自您没有使用的代码.
虽然从抽象类继承是一种"是一种"关系,但这并不总是你想要的,而实现一个接口更像是一种"行为"关系.在某些情况下,这种差异可能非常显着.
例如,假设您有一个抽象类帐户,许多其他类可以从该帐户扩展(帐户类型等).它有一组特定的方法,仅适用于该类型组.但是,其中一些帐户子类实现了Versionable,Listable或Editable,因此可以将它们抛出到期望使用这些API的控制器中.控制器不关心它是什么类型的对象
相比之下,我还可以创建一个不从Account扩展的对象,比如一个User抽象类,并且仍然实现Listable和Editable,但不是Versionable,这在这里没有意义.
这样,我说FooUser子类不是一个帐户,但是DOES就像一个可编辑的对象.同样,BarAccount从Account扩展,但不是User子类,但实现了Editable,Listable和Versionable.
将可编辑,可列表和可版本化的所有这些API添加到抽象类本身不仅会混乱和丑陋,而且会复制Account和User中的公共接口,或强制我的User对象实现Versionable,可能只是为了抛出例外.
接口本质上是您可以创建的蓝图.它们定义了类必须具有的方法,但您可以在这些限制之外创建额外的方法.
我不确定你不能为方法添加代码是什么意思 - 因为你可以.您是将接口应用于抽象类还是扩展它的类?
应用于抽象类的接口中的方法需要在该抽象类中实现.但是,将该接口应用于扩展类,该方法只需要在扩展类中实现.我可能在这里错了 - 我不尽可能经常使用接口.
我一直认为接口是外部开发人员的模式,或者是额外的规则集,以确保事情是正确的.
您将在PHP中使用接口:
隐藏实现 - 为一类对象建立访问协议,更改底层实现,而不在您使用该对象的所有位置进行重构
检查类型 - 确保参数具有特定类型 $object instanceof MyInterface
在运行时强制执行参数检查
将多个行为实现到单个类中(构建复杂类型)
class Car实现EngineInterface,BodyInterface,SteeringInterface {
Car
对象现在start()
,stop()
(EngineInterface)或goRight()
,goLeft()
(转向界面)
和其他我现在想不到的事情
4号它可能是最难以用抽象类解决的用例.
来自Java思考:
一个接口说,"这就是所有实现这个特定接口的类都会是这样的."因此,任何使用特定接口的代码都知道可以为该接口调用哪些方法,就是这样.因此,该接口用于在类之间建立"协议".
接口不是作为类可以扩展的基础,而是作为所需函数的映射.
以下是使用抽象类不适合的接口的示例:
假设我有一个日历应用程序,允许用户从外部源导入日历数据.我会编写类来处理导入每种类型的数据源(ical,rss,atom,json)这些类中的每一个都会实现一个公共接口,以确保它们都具有我的应用程序获取数据所需的公共公共方法.
然后,当用户添加新的Feed时,我可以识别它的Feed类型,并使用为该类型开发的类来导入数据.为特定订阅源导入数据而编写的每个类都将具有完全不同的代码,除了需要实现允许我的应用程序使用它们的接口之外,类之间可能存在非常少的相似性.如果我要使用抽象类,我可以很容易地忽略这样一个事实,即我没有覆盖getEvents()方法,这会在这个实例中破坏我的应用程序,而使用接口不会让我的应用程序运行,如果有任何方法在接口中定义的实现它的类中不存在.我的应用程序不必关心它用于从Feed中获取数据的类,
为了更进一步,当我回到我的日历应用程序以添加另一个提要类型时,界面证明非常有用.使用ImportableFeed接口意味着我可以通过简单地添加实现此接口的新类来继续添加更多导入不同Feed类型的类.这允许我添加大量功能,而不必为我的核心应用程序添加不必要的批量,因为我的核心应用程序只依赖于接口需要的公共方法,只要我的新feed导入类实现ImportableFeed接口然后我我知道我可以放下它并继续前进.
这只是一个非常简单的开始.然后,我可以创建另一个接口,可以实现我的所有日历类,以提供特定于类处理的feed类型的更多功能.另一个很好的例子是验证饲料类型等的方法.
这超出了这个问题,但是因为我使用了上面的例子:如果以这种方式使用,接口会带来一系列问题.我发现自己需要确保从实现的方法返回的输出以匹配接口并实现这一点我使用读取PHPDoc块的IDE并将返回类型作为类型提示添加到接口的PHPDoc块中转换为实现它的具体类.我使用实现此接口的类的数据输出的类将至少知道它期望在此示例中返回一个数组:
没有太多空间来比较抽象类和接口.接口只是在实现时需要类具有一组公共接口的映射.
8> Outlaw Progr..:接口不仅仅是为了确保开发人员实现某些方法.这个想法是因为这些类保证有某些方法,即使你不知道类的实际类型,也可以使用这些方法.例:
interface Readable { String read(); } Listreadables; // dunno what these actually are, but we know they have read(); for(Readable reader : readables) System.out.println(reader.read()); 在许多情况下,提供基类(抽象与否)是没有意义的,因为实现变化很大,并且除了少数方法之外不共享任何共同点.
动态类型语言具有"鸭子打字"的概念,您不需要接口; 您可以自由地假设该对象具有您正在调用它的方法.这解决了静态类型语言中的问题,其中对象有一些方法(在我的示例中,read()),但没有实现接口.
9> Henrik Paul..:在我看来,接口应该优于非功能抽象类.我不会感到惊讶,因为那里只有一个性能上升,因为只有一个对象实例化,而不是解析两个,组合它们(虽然,我不能确定,我不熟悉内部工作OOP PHP).
确实,与Java相比,接口没那么有用/有意义.另一方面,PHP6将引入更多类型提示,包括返回值的类型提示.这应该为PHP接口增加一些价值.
tl; dr:interfaces定义了需要遵循的方法列表(思考API),而抽象类提供了一些基本/通用功能,子类可以根据特定需求进行优化.
10> Ross..:我不记得PHP在这方面是否有所不同,但是在Java中,您可以实现多个接口,但不能继承多个抽象类。我认为PHP的工作方式相同。
在PHP中,您可以通过用逗号分隔多个接口来应用多个接口(我认为,我认为这不是一个干净的解决方案)。
至于多个抽象类,则可以有多个抽象相互扩展(再次,我对此不太确定,但我想我之前已经看过)。您唯一不能扩展的是最后一堂课。