很抱歉向sich提出一个通用的问题,但是我一直在研究这些问题,并且除了头部编程之外传达什么成员必须在课堂上,我只是没有看到任何好处。
答案 0 :(得分:7)
面向对象编程有两个(基本)部分,给新手带来麻烦;第一个是inheritance
,第二个是composition
。这些是最难得到的;一旦你理解了其他一切就更容易了。
您所指的是composition
- 例如,课程的作用是什么?如果你继承了继承路由,它就来自一个抽象类(比如说Dog
是一个Animal
)。如果您使用组合,那么您正在制定合同(A Car HAS A Driver / Loan / Insurance)。任何实现您的接口的人都必须实现该接口的方法。
这允许松耦合;并且不会将你绑定到不适合的继承模型中。
继承适合的地方,使用它;但如果两个类之间的关系本质上是契约性的,或HAS-A
对IS-A
,那么使用接口来模拟该部分。
为什么要使用界面?
对于一个实际的例子,让我们跳进一个业务应用程序。如果你有一个存储库;您将要在存储库上方创建接口的层。这样,如果您必须以存储库的工作方式更改任何内容,则不会影响任何内容,因为它们都遵循相同的合同。
这是我们的存储库:
public interface IUserRepository
{
public void Save();
public void Delete(int id);
public bool Create(User user);
public User GetUserById(int id);
}
现在,我可以在类中实现该存储库:
public class UserRepository : IRepository
{
public void Save()
{
//Implement
}
public void Delete(int id)
{
//Implement
}
public bool Create(User user)
{
//Implement
}
public User GetUserById(int id)
{
//Implement
}
}
这将接口与调用它分开。我可以将这个类从Linq-To-SQL更改为内联SQL或存储过程,只要我实现了IUserRepository
接口,就没有人会更聪明;最重要的是,没有任何课程来自我的课程,可能会对我的变化感到生气。
继承与构成:最好的朋友
继承和组合旨在解决不同的问题。在每个适合的地方使用它们,并且在使用它们时存在完整的问题子集。
答案 1 :(得分:5)
我打算让乔治指出你现在可以使用界面而不是具体的类。这里的每个人似乎都明白界面是什么以及如何定义它们,但是大多数都未能以学生容易掌握的方式解释它们的关键点 - 大多数课程都没有指出,而是让你去掌握在吸管或为自己弄清楚所以我会尝试以一种不需要的方式拼出来。所以希望你不会想到“那么什么,它似乎浪费时间/精力/代码。”
public interface ICar
{
public bool EngineIsRunning{ get; }
public void StartEngine();
public void StopEngine();
public int NumberOfWheels{ get; }
public void Drive(string direction);
}
public class SportsCar : ICar
{
public SportsCar
{
Console.WriteLine("New sports car ready for action!");
}
public bool EngineIsRunning{ get; protected set; }
public void StartEngine()
{
if(!EngineIsRunning)
{
EngineIsRunning = true;
Console.WriteLine("Engine is started.");
}
else
Console.WriteLine("Engine is already running.");
}
public void StopEngine()
{
if(EngineIsRunning)
{
EngineIsRunning = false;
Console.WriteLine("Engine is stopped.");
}
else
Console.WriteLine("Engine is already stopped.");
}
public int NumberOfWheels
{
get
{
return 4;
}
}
public void Drive(string direction)
{
if (EngineIsRunning)
Console.WriteLine("Driving {0}", direction);
else
Console.WriteLine("You can only drive when the engine is running.");
}
}
public class CarFactory
{
public ICar BuildCar(string car)
{
switch case(car)
case "SportsCar" :
return Activator.CreateInstance("SportsCar");
default :
/* Return some other concrete class that implements ICar */
}
}
public class Program
{
/* Your car type would be defined in your app.config or some other
* mechanism that is application agnostic - perhaps by implicit
* reference of an existing DLL or something else. My point is that
* while I've hard coded the CarType as "SportsCar" in this example,
* in a real world application, the CarType would not be known at
* design time - only at runtime. */
string CarType = "SportsCar";
/* Now we tell the CarFactory to build us a car of whatever type we
* found from our outside configuration */
ICar car = CarFactory.BuildCar(CarType);
/* And without knowing what type of car it was, we work to the
* interface. The CarFactory could have returned any type of car,
* our application doesn't care. We know that any class returned
* from the CarFactory has the StartEngine(), StopEngine() and Drive()
* methods as well as the NumberOfWheels and EngineIsRunning
* properties. */
if (car != null)
{
car.StartEngine();
Console.WriteLine("Engine is running: {0}", car.EngineIsRunning);
if (car.EngineIsRunning)
{
car.Drive("Forward");
car.StopEngine();
}
}
}
如您所见,我们可以定义任何类型的汽车,只要该汽车实现了接口ICar,它就会拥有我们可以从主应用程序调用的预定义属性和方法。我们不需要知道什么类型的汽车 - 甚至是从CarFactory.BuildCar()方法返回的类的类型。它可以为我们所关心的所有人返回一个类型为“DragRacer”的实例,我们需要知道的是DragRacer实现了ICar,我们可以像往常一样继续生活。
在现实世界的应用程序中,想象一下IDataStore,我们的具体数据存储类提供对磁盘上数据存储的访问,或者在网络上,某些数据库,拇指驱动器,我们不关心什么 - 我们所关心的只是从我们的类工厂返回的具体类实现了接口IDataStore,我们可以调用方法和属性,而无需了解类的底层架构。
另一个现实世界的含义(至少对于.NET)是,如果编写跑车类的人对包含跑车实现的库进行更改并重新编译,并且您已经对其库进行了硬性引用你需要重新编译 - 如果你已经针对ICar编写了你的应用程序,你可以用他们的新版本替换DLL,你就可以正常进行。
答案 2 :(得分:2)
这样一个给定的类可以从多个源继承,同时仍然只从一个父类继承。
某些编程语言(C ++是经典示例)允许类从多个类继承;在这种情况下,不需要接口(一般来说,不存在。)
但是,当您最终使用Java或C#这样的语言时,不允许多重继承时,您需要一种不同的机制来允许类从多个源继承 - 也就是说,代表多个来源“是 - “关系。输入接口。
因此,它允许您完全定义接口 - 实现给定接口的类将实现给定的一组方法,而无需指定有关如何实际编写这些方法的任何内容。
答案 3 :(得分:1)
也许此资源很有用:When to Use Interfaces
答案 4 :(得分:1)
它允许您将实现与定义分开。
例如,我可以定义一个接口,我的代码的一部分被编码 - 只要它涉及它是在接口上调用成员。然后我可以按照自己的意愿交换实现 - 如果我想创建一个虚假版本的数据库访问组件,那么我可以。
接口是软件组件的基本构建块
答案 5 :(得分:0)
从Java的角度来看,您可以创建一个接口,告诉任何实现所述接口的类,“您必须实现这些方法”,但不要在层次结构中引入另一个类。
这是可取的,因为您可能希望保证某些机制存在,当您希望不同基础的对象具有相同的代码语义时(即在每个类中编码为适当的相同方法)出于某种目的,但您不能我想创建一个抽象类,这将限制你现在你不能继承另一个类。
只是一个想法...我只修补Java。我不是专家。请在下面看到我的想法。 2个不同的设备需要从我们的计算机接收消息。一个驻留在互联网上并使用http作为传输协议。另一个位于10英尺外,通过USB连接。
注意,这种语法是伪代码。
interface writeable { void open(); void write(); void close(); } class A : HTTP_CONNECTION implements writeable { //here, opening means opening an HTTP connection. //maybe writing means to assemble our message for a specific protocol on top of //HTTP //maybe closing means to terminate the connection } class B : USB_DEVICE implements writeable { //open means open a serial connection //write means write the same message as above, for a different protocol and device //close means to release USB object gracefully. }
答案 6 :(得分:0)
在Java中,接口允许您引用实现该接口的任何类。这与子类化类似,但有时您希望从完全不同的层次结构中引用类,就好像它们是相同的类型一样。
答案 7 :(得分:0)
接口在消费者和供应商之间创建层绝缘。这层绝缘材料可用于不同的东西。但总体而言,如果使用得当,它们会降低应用程序中的依赖关系密度(以及由此产生的复杂性)。
答案 8 :(得分:0)
我希望支持Electron的答案作为最有效的答案。
面向对象的编程有助于合同的声明。 类声明是合同。合同是类的承诺,根据类声明的类型/签名提供功能。在常见的oo语言中,每个班级都有公共和受保护的合同。
显然,我们都知道接口是一个空的未实现的类模板,可以允许伪装成一个类。但为什么有空的未履行的合同?
已实施的课程已自动完成所有合同。
抽象类是部分履行的合同。
一个班级通过其实施的特征自发地表现出一种个性,称其有资格获得某种职位描述。但是,它也可以投射不止一个人格来使自己有资格获得多个职位描述。
但为什么一辆汽车不能诚实地展示其完整的个性而不是躲在多重人格的帷幕之后呢?这是因为,希望以交通方式呈现自身的一类自行车,船或滑板不希望实现汽车的所有复杂性和限制。一艘船需要能够进行水上旅行,而汽车则不需要。那么为什么不给一辆汽车的所有功能 - 当然,对这样一个提议的回应是 - 你是开玩笑吗?
有时,我们只是希望宣布一份未履行的合同,而不必费心实施。一个完全没有实现的抽象类只是一个接口。也许,界面类似于您可以从固定商店购买的空白法律形式。
因此,在允许多重继承的环境中,当我们只希望声明其他人可以履行的未履行合同时,接口/完全抽象类是有用的。
在不允许多重继承的环境中,拥有接口是允许实现类投影多个个性的唯一方法。
考虑
interface Transportation
{
takePassengers();
gotoDestination(Destination d);
}
class Motorcar implements Transportation
{
cleanWindshiedl();
getOilChange();
doMillionsOtherThings();
...
takePassengers();
gotoDestination(Destination d);
}
class Kayak implements Transportation
{
paddle();
getCarriedAcrossRapids();
...
takePassengers();
gotoDestination(Destination d);
}
需要交通运输的活动必须对数以百万计的运输方式视而不见。因为它只是想打电话
Transportation.takePassengers or
Transportation.gotoDestination
因为它要求运输但是它已经完成。这是模块化思维和编程,因为我们不想将自己局限于汽车或皮划艇运输。如果我们限制所有我们所知道的运输,我们需要花费大量时间找出所有当前的运输技术,看看它是否符合我们的活动计划。
我们也不知道将来会开发一种名为AntiGravityCar的新型运输方式。在花了这么多时间不必要地适应我们可能知道的每种运输方式之后,我们发现我们的例程不允许我们使用AntiGravityCar。但是由于特定的合同是盲目的,除了它需要的任何技术之外,我们不仅不会浪费时间考虑各种传输的各种行为,而且任何实现传输接口的未来传输开发都可以简单地将自己包含在活动中更进一步。
答案 9 :(得分:0)
答案中没有一个提到关键词:可替代性。在任何需要后者的代码中,任何实现接口Foo
的对象都可以替换“实现Foo
的东西”。在许多框架中,一个对象必须对“你是什么类型的东西”这个问题给出一个单一的答案,并回答“你的类型是什么”;尽管如此,一种类型可以替代许多不同类型的东西可能是有帮助的。接口允许这样做。 VolkswagonBeetleConvertible
来自VolkswagonBeetle
,FordMustangConvertible
来自FordMustang
。 VolkswagonBeetleConvertible
和FordMustangConvertible
都会实现IOpenableTop
,即使类'父类型都没有。因此,所提到的两种派生类型可以代替“实现IOpenableTop
”的东西。