Neb*_*hom 253 .net c# interface
接口的原因真正让我无所适从.根据我的理解,这是一种解决C#中不存在的不存在的多继承的问题(或者我被告知).
我所看到的是,你预先定义了一些成员和函数,然后必须再次在类中重新定义.从而使界面变得多余.它只是感觉像句法...好吧,垃圾对我来说(请不要冒犯意思.垃圾就像无用的东西).
在下面给出的示例中,从堆栈溢出的不同C#接口线程中获取,我只是创建一个名为Pizza而不是接口的基类.
简单示例(取自不同的堆栈溢出贡献)
public interface IPizza
{
public void Order();
}
public class PepperoniPizza : IPizza
{
public void Order()
{
//Order Pepperoni pizza
}
}
public class HawaiiPizza : IPizza
{
public void Order()
{
//Order HawaiiPizza
}
}
Run Code Online (Sandbox Code Playgroud)
Kal*_*exx 432
没有人真正用简单的术语解释界面是如何有用的,所以我要试一试(并从Shamim的答案中窃取一些想法).
让我们来了解比萨饼订购服务的想法.您可以拥有多种类型的比萨饼,每个比萨饼的共同操作是在系统中准备订单.每个披萨都必须准备好,但每个披萨的准备不同.例如,当订购馅皮披萨时,系统可能必须验证餐厅可以获得的某些成分,并将那些不适合深盘比萨放在一边.
在代码中编写代码时,从技术上讲,您可以这样做
public class Pizza()
{
public void Prepare(PizzaType tp)
{
switch (tp)
{
case PizzaType.StuffedCrust:
// prepare stuffed crust ingredients in system
break;
case PizzaType.DeepDish:
// prepare deep dish ingredients in system
break;
//.... etc.
}
}
}
Run Code Online (Sandbox Code Playgroud)
然而,深盘比萨(在C#术语中)可能需要在Prepare()
方法中设置不同的属性而不是填充外壳,因此您最终会有很多可选属性,并且该类不能很好地扩展(如果添加新的披萨类型).
解决这个问题的正确方法是使用界面.界面声明所有比萨饼都可以准备,但每个比萨饼可以不同的方式准备.所以如果你有以下接口:
public interface IPizza
{
void Prepare();
}
public class StuffedCrustPizza : IPizza
{
public void Prepare()
{
// Set settings in system for stuffed crust preparations
}
}
public class DeepDishPizza : IPizza
{
public void Prepare()
{
// Set settings in system for deep dish preparations
}
}
Run Code Online (Sandbox Code Playgroud)
现在,您的订单处理代码无需确切知道为了处理原料而订购了哪些类型的比萨饼.它只有:
public PreparePizzas(IList<IPizza> pizzas)
{
foreach (IPizza pizza in pizzas)
pizza.Prepare();
}
Run Code Online (Sandbox Code Playgroud)
即使每种类型的比萨饼都有不同的准备,这部分代码也不必关心我们正在处理什么类型的比萨饼,它只是知道它被称为比萨饼,因此每次调用Prepare
都会自动准确地准备每个比萨饼根据其类型,即使该系列有多种类型的比萨饼.
Joe*_*oey 171
关键是界面代表合同.任何实现类必须具有的一组公共方法.从技术上讲,接口只管理语法,即有哪些方法,它们得到什么参数以及返回什么.通常它们也会封装语义,尽管只能通过文档来实现.
然后,您可以使用不同的接口实现并随意交换它们.在您的示例中,由于每个披萨实例都是一个,IPizza
您可以IPizza
在任何处理未知披萨类型的实例的地方使用.任何类型继承自的实例IPizza
都保证可订购,因为它有一个Order()
方法.
Python不是静态类型的,因此在运行时保留并查找类型.因此,您可以尝试Order()
在任何对象上调用方法.只要对象有这样的方法并且可能只是耸耸肩并且说"Meh."如果没有,那么运行时很开心.在C#中不是这样.编译器负责进行正确的调用,如果它只是一些随机object
的,编译器还不知道运行时的实例是否具有该方法.从编译器的角度来看,它无效,因为它无法验证它.(你可以用反射或dynamic
关键字来做这些事情,但是我觉得现在有点远了.)
还要注意,通常意义上的接口不一定必须是C#interface
,它也可以是抽象类,甚至是普通类(如果所有子类都需要共享一些公共代码,它可以派上用场 - 在大多数情况下但是,interface
足够了).
Pad*_*ddy 113
对我来说,只有当你停止将它们视为使你的代码更容易/更快写入的东西时,这一点才变得清晰 - 这不是它们的目的.它们有许多用途:
(这将失去比萨饼的类比,因为它不太容易想象出这个用途)
假设您在屏幕上制作一个简单的游戏,它将拥有您与之交互的生物.
答:通过在前端和后端实现之间引入松散耦合,它们可以使您的代码在将来更容易维护.
你可以写这个开始,因为只有巨魔:
// This is our back-end implementation of a troll
class Troll
{
void Walk(int distance)
{
//Implementation here
}
}
Run Code Online (Sandbox Code Playgroud)
前端:
function SpawnCreature()
{
Troll aTroll = new Troll();
aTroll.Walk(1);
}
Run Code Online (Sandbox Code Playgroud)
两周后,营销决定你也需要兽人,因为他们在推特上阅读它们,所以你必须做以下事情:
class Orc
{
void Walk(int distance)
{
//Implementation (orcs are faster than trolls)
}
}
Run Code Online (Sandbox Code Playgroud)
前端:
void SpawnCreature(creatureType)
{
switch(creatureType)
{
case Orc:
Orc anOrc = new Orc();
anORc.Walk();
case Troll:
Troll aTroll = new Troll();
aTroll.Walk();
}
}
Run Code Online (Sandbox Code Playgroud)
你可以看到这开始变得混乱.你可以在这里使用一个接口,这样你的前端就会被编写一次并且(这里是重要的一点)经过测试,然后你可以根据需要插入更多的后端项目:
interface ICreature
{
void Walk(int distance)
}
public class Troll : ICreature
public class Orc : ICreature
//etc
Run Code Online (Sandbox Code Playgroud)
前端是:
void SpawnCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
}
Run Code Online (Sandbox Code Playgroud)
前端现在只关心接口ICreature - 它并不关心巨魔或兽人的内部实现,而只是因为他们实现了ICreature.
从这个角度来看这一点时需要注意的一点是,您也可以轻松地使用抽象的生物类,从这个角度来看,这具有相同的效果.
你可以将创作提取出来给工厂:
public class CreatureFactory {
public ICreature GetCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
return creature;
}
}
Run Code Online (Sandbox Code Playgroud)
然后我们的前端将成为:
CreatureFactory _factory;
void SpawnCreature(creatureType)
{
ICreature creature = _factory.GetCreature(creatureType);
creature.Walk();
}
Run Code Online (Sandbox Code Playgroud)
前端现在甚至不需要引用Troll和Orc实现的库(提供工厂在一个单独的库中) - 它不需要知道它们.
B:假设你有一些功能,只有一些生物会在你的同质数据结构中拥有,例如
interface ICanTurnToStone
{
void TurnToStone();
}
public class Troll: ICreature, ICanTurnToStone
Run Code Online (Sandbox Code Playgroud)
前端可能是:
void SpawnCreatureInSunlight(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
if (creature is ICanTurnToStone)
{
(ICanTurnToStone)creature.TurnToStone();
}
}
Run Code Online (Sandbox Code Playgroud)
C:依赖注入的用法
当前端代码和后端实现之间存在非常松散的耦合时,大多数依赖注入框架更容易使用.如果我们采用上面的工厂示例并让我们的工厂实现一个接口:
public interface ICreatureFactory {
ICreature GetCreature(string creatureType);
}
Run Code Online (Sandbox Code Playgroud)
然后我们的前端可以通过构造函数(通常)注入(例如MVC API控制器):
public class CreatureController : Controller {
private readonly ICreatureFactory _factory;
public CreatureController(ICreatureFactory factory) {
_factory = factory;
}
public HttpResponseMessage TurnToStone(string creatureType) {
ICreature creature = _factory.GetCreature(creatureType);
creature.TurnToStone();
return Request.CreateResponse(HttpStatusCode.OK);
}
}
Run Code Online (Sandbox Code Playgroud)
使用我们的DI框架(例如Ninject或Autofac),我们可以设置它们,以便在构造函数中需要ICreatureFactory时,在运行时创建一个CreatureFactory实例 - 这使我们的代码变得简单.
这也意味着当我们为控制器编写单元测试时,我们可以提供一个模拟的ICreatureFactory(例如,如果具体实现需要DB访问,我们不希望我们的单元测试依赖于它)并轻松测试我们的控制器中的代码.
D:还有其他一些用途,例如你有两个项目A和B,由于"遗留"原因结构不合理,A有B的参考.
然后,您在B中找到需要调用A中已有方法的功能.当您获得循环引用时,无法使用具体实现来执行此操作.
您可以在B中声明一个接口,然后A中的类实现.您在B中的方法可以传递一个实现接口的类的实例,没有问题,即使具体对象是A中的类型.
Sha*_*fiz 33
以下是您重复解释的示例:
public interface IFood // not Pizza
{
public void Prepare();
}
public class Pizza : IFood
{
public void Prepare() // Not order for explanations sake
{
//Prepare Pizza
}
}
public class Burger : IFood
{
public void Prepare()
{
//Prepare Burger
}
}
Run Code Online (Sandbox Code Playgroud)
Ale*_*lek 26
上面的例子没有多大意义.您可以使用类完成上述所有示例(如果您希望它仅作为合约执行,则为抽象类):
public abstract class Food {
public abstract void Prepare();
}
public class Pizza : Food {
public override void Prepare() { /* Prepare pizza */ }
}
public class Burger : Food {
public override void Prepare() { /* Prepare Burger */ }
}
Run Code Online (Sandbox Code Playgroud)
您获得与界面相同的行为.你可以创建一个List<Food>
迭代,不知道哪个类位于顶层.
更恰当的例子是多重继承:
public abstract class MenuItem {
public string Name { get; set; }
public abstract void BringToTable();
}
// Notice Soda only inherits from MenuItem
public class Soda : MenuItem {
public override void BringToTable() { /* Bring soda to table */ }
}
// All food needs to be cooked (real food) so we add this
// feature to all food menu items
public interface IFood {
void Cook();
}
public class Pizza : MenuItem, IFood {
public override void BringToTable() { /* Bring pizza to table */ }
public void Cook() { /* Cook Pizza */ }
}
public class Burger : MenuItem, IFood {
public override void BringToTable() { /* Bring burger to table */ }
public void Cook() { /* Cook Burger */ }
}
Run Code Online (Sandbox Code Playgroud)
然后你可以使用它们,MenuItem
而不关心它们如何处理每个方法调用.
public class Waiter {
public void TakeOrder(IEnumerable<MenuItem> order)
{
// Cook first
// (all except soda because soda is not IFood)
foreach (var food in order.OfType<IFood>())
food.Cook();
// Bring them all to the table
// (everything, including soda, pizza and burger because they're all menu items)
foreach (var menuItem in order)
menuItem.BringToTable();
}
}
Run Code Online (Sandbox Code Playgroud)
BKS*_*eon 23
打个比方:所以我是建筑工地上的一名前辈.
商人们一直走在施工现场.我不知道谁会走过那些门.但我基本上告诉他们该怎么做.
上述方法的问题在于我必须:(i)知道谁在那扇门走路,并且根据它是谁,我必须告诉他们该做什么.这意味着我必须了解特定交易的一切.这种方法有相关的成本/收益:
这意味着如果木匠的代码从:更改BuildScaffolding()
为BuildScaffold()
(即稍微更改名称),那么我还必须更改调用类(即Foreperson
类) - 您将不得不对代码进行两次更改而不是(基本上) ) 只有一个.使用多态性,您(基本上)只需要进行一次更改即可获得相同的结果.
其次,你不必经常问:你是谁?好的,这个......你是谁?确实这样做.....多态 - 它干扰了代码,并且在某些情况下非常有效:
使用多态性,您可以轻松添加其他类别的商人,而无需更改任何现有代码.(即SOLID设计原则的第二个:开闭原理).
想象一下这样一个场景,无论是谁走进门,我都可以说:"工作()"并且他们尊重他们专注的工作:水管工会处理管道,电工会处理电线.
这种方法的好处是:(i)我不需要确切知道谁正在通过那扇门进入 - 我需要知道的是,他们将成为一种传统,他们可以做的工作,其次,(ii)我不需要知道有关该特定交易的任何信息.传统将照顾到这一点.
所以不是这样的:
If(electrician) then electrician.FixCablesAndElectricity()
if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks()
Run Code Online (Sandbox Code Playgroud)
我可以这样做:
ITradesman tradie = Tradesman.Factory(); // in reality i know it's a plumber, but in the real world you won't know who's on the other side of the tradie assignment.
tradie.Work(); // and then tradie will do the work of a plumber, or electrician etc. depending on what type of tradesman he is. The foreman doesn't need to know anything, apart from telling the anonymous tradie to get to Work()!!
Run Code Online (Sandbox Code Playgroud)
好处是,如果木匠等的具体工作要求发生变化,那么老人不需要改变他的代码 - 他不需要知道或关心.重要的是木匠知道Work()的含义.其次,如果一个新型的建筑工人来到工地,那么工头不需要知道任何关于贸易的事情 - 所有工头的关心是建筑工人(.eg Welder,Glazier,Tiler等)能否得到一些Work()完成.
界面允许您让人员完成他们所分配的工作,而无需了解他们究竟是谁或他们可以做什么的具体细节.这使您可以轻松添加新类型(交易)而无需更改现有代码(从技术上讲,您只需稍微更改一下),这就是OOP方法与更多功能编程方法的真正好处.
如果您不理解上述任何内容或者如果不清楚,请在评论中提出,我会尝试更好地回答.
bev*_*qua 11
Pizza示例很糟糕,因为您应该使用处理排序的抽象类,比如披萨应该覆盖比萨饼类型.
当您拥有共享属性时使用接口,但是您的类从不同的地方继承,或者您没有可以使用的任何公共代码.例如,这是用于可以处理的东西IDisposable
,你知道它会被处理掉,你只是不知道它处理后会发生什么.
接口只是一个契约,它告诉您对象可以执行的操作,参数以及期望的返回类型.
ang*_*son 10
考虑不控制或拥有基类的情况.
以视觉控件为例,在.NET for Winforms中,它们都是从基类Control继承的,它是在.NET框架中完全定义的.
我们假设您正在创建自定义控件.您希望构建新的按钮,文本框,列表视图,网格,诸如此类,并且您希望它们都具有您的控件集独有的某些功能.
例如,您可能需要一种常用的方法来处理主题,或者处理本地化的常用方法.
在这种情况下,您不能"只创建基类",因为如果您这样做,则必须重新实现与控件相关的所有内容.
相反,您将从Button,TextBox,ListView,GridView等下降并添加您的代码.
但这会带来一个问题,你现在如何识别哪些控件是"你的",你怎么能构建一些代码,说"对于我的表单上的所有控件,将主题设置为X".
输入接口.
接口是一种查看对象的方法,用于确定对象是否符合某个合同.
您将创建"YourButton",从Button继续,并添加对您需要的所有接口的支持.
这将允许您编写如下代码:
foreach (Control ctrl in Controls)
{
if (ctrl is IMyThemableControl)
((IMyThemableControl)ctrl).SetTheme(newTheme);
}
Run Code Online (Sandbox Code Playgroud)
没有接口就不可能做到这一点,相反,你必须编写如下代码:
foreach (Control ctrl in Controls)
{
if (ctrl is MyThemableButton)
((MyThemableButton)ctrl).SetTheme(newTheme);
else if (ctrl is MyThemableTextBox)
((MyThemableTextBox)ctrl).SetTheme(newTheme);
else if (ctrl is MyThemableGridView)
((MyThemableGridView)ctrl).SetTheme(newTheme);
else ....
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,您可以(也可能)只定义一个Pizza基类并从中继承.但是,接口允许您执行其他方式无法实现的事情有两个原因:
一个类可以实现多个接口.它只定义了类必须具有的功能.实现一系列接口意味着一个类可以在不同的地方实现多个功能.
可以在类或者调用者之间的hogher范围中定义接口.这意味着您可以分离功能,分离项目依赖项,并将功能保留在一个项目或类中,以及在其他地方实现此功能.
2的一个含义是您可以更改正在使用的类,只需要它实现适当的接口.
我在这个页面上搜索了“组合”这个词,但一次也没看到。这个答案是对上述答案的补充。
在面向对象的项目中使用接口的绝对关键原因之一是它们允许您支持组合而不是继承。通过实现接口,您可以将您的实现与应用于它们的各种算法分离。
德里克·巴纳斯 (Derek Banas) 撰写的这个极好的“装饰器模式”教程(有趣的是,它还以披萨为例)是一个值得说明的例子:
https://www.youtube.com/watch?v=j40kRwSm4VE
归档时间: |
|
查看次数: |
127236 次 |
最近记录: |