仅将接口用于分类是不好的做法吗?


12

例如:

说我有课ABC。我有两个接口,我们称它们为IAnimalIDogIDog继承自IAnimalABIDogs,虽然C不是,但它是一个IAnimal

重要的部分是不IDog提供任何附加功能。它仅用于允许AB,但不C以作为参数传递给某些方法进行传递。

这是不好的做法吗?


8
IAnimal并且IDog可怕的同义反复的名字!

5
@JarrodRoberson虽然我倾向于同意,但是如果您在.Net世界中工作,则可能会受其束缚(或者,如果您以不同的方式进行操作,则会违反既定的惯例)。
Daniel B

2
@JarrodRoberson恕我直言MyProject.Data.IAnimalMyProject.Data.Animal更好比MyProject.Data.Interfaces.AnimalMyProject.Data.Implementations.Animal
麦克Koder

1
关键是,甚至没有必要担心它是否为InterfaceImplementation,无论是在重复前缀还是在名称空间中,无论哪种方式都是重言式,都违反了DRY。Dog这就是您应该关心的。PitBull extends Dog也不需要实现冗余,这个词extends告诉我所有我需要知道的内容,请阅读我在原始评论中发布的链接。

1
@贾罗德:别再向我抱怨了。向.NET开发团队投诉。如果我违反标准,我的开发人员会讨厌我的胆量。
肯德尔·弗雷

Answers:


13

没有任何成员或与另一个接口的成员完全相同的接口,而另一个接口是从相同接口(称为标记接口)继承的,并且您将其用作标记。

这不是一个坏习惯,但是如果您使用的语言支持接口,则可以使用属性(注释)替换该接口。

我可以自信地说这不是一个坏习惯,因为我在Orchard Project中看到了大量使用的“标记接口”模式

这是Orchard项目的示例

public interface ISingletonDependency : IDependency {}

/// <summary>
/// Base interface for services that may *only* be instantiated in a unit of work.
/// This interface is used to guarantee they are not accidentally referenced by a singleton dependency.
/// </summary>
public interface IUnitOfWorkDependency : IDependency {}

/// <summary>
/// Base interface for services that are instantiated per usage.
/// </summary>
public interface ITransientDependency : IDependency {}

请参考标记接口


属性/注释是否支持编译时检查(使用C#作为参考语言)?我知道如果对象没有属性,则可能引发异常,但是接口模式会在编译时捕获错误。
肯德尔·弗雷

如果您正在使用Marker Interface,那么您根本就没有编译检查的好处。您基本上是通过接口进行类型检查。
Freshblood 2012年

我很困惑。这种“标记接口”模式确实提供了编译时检查,因为您不能将传递给C需要的方法IDog
肯德尔·弗雷

如果您正在使用此模式,那么您将在进行反射工作。我的意思是您正在动态或静态地进行类型检查。我确定您的用例中有问题,但是我还没有看到您的使用方式。
Freshblood 2012年

让我像在评论Telastyn的答案中所做的那样描述它。例如,我有一个Kennel存储IDogs 列表的类。
肯德尔·弗雷

4

是的,这几乎是每种语言的不良做法。

类型不在那里编码数据。它们有助于证明您的数据可以到达所需的位置,并且可以像需要的那样运行。子类型的唯一原因是多态行为是否发生了变化(并非总是如此)。

由于没有输入,因此暗示IDog可以做什么。一个程序员想到一件事,另一位程序员想到另一件事,事情就崩溃了。或者,随着您需要更精细的控制,它所代表的功能也会增加。

[编辑:澄清]

我的意思是,您正在基于接口执行一些逻辑。为什么某些方法只能与狗一起使用,而其他方法则只能与任何动物一起使用?该差异是隐含的合同,因为没有实际成员提供差异。系统中没有实际数据。唯一的区别是界面(因此有“不键入”注释),并且程序员之间的含义不同。[/编辑]

某些语言提供的特征机制还不错,但是那些倾向于功能而不是完善。我与他们的经验很少,因此无法谈论那里的最佳做法。但是在C ++ / Java / C#中...不好。


我对你的中间两段感到困惑。“子类型”是什么意思?我正在使用接口,这些接口是合同,不是实现,我不确定您的注释如何应用。“不输入”是什么意思?“暗示”是什么意思?IDog可以执行IAnimal可以执行的所有操作,但不能保证能够执行更多操作。
肯德尔·弗雷

感谢您的澄清。在我的特定情况下,我并没有完全不同地使用这些接口。最好说一下我有一个Kennel存储IDogs 列表的类,这可能是最好的描述。
肯德尔·弗雷

@KendallFrey:要么是您退出接口(不好),要么是创建人为的区分,感觉到了抽象的错误。
Telastyn 2012年

2
@Telastyn-这样的界面的目的是提供元数据
Freshblood 2012年

1
@Telastyn:那么属性如何应用于编译时检查?在C#中,AFAIK属性只能在运行时使用。
肯德尔·弗雷

2

我只非常了解C#,所以一般来说我不能对OO编程这么说,但是作为一个C#示例,如果您需要对类进行分类,则显而易见的选项是接口,枚举属性或自定义属性。通常,不管别人怎么想,我都会选择界面。

if(animal is IDog)
{
}
if(animal.Type == AnimalType.Dog)
{
}
if(animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
{
}


var dogs1 = animals.OfType<IDog>();
var dogs2 = animals.Where(a => a.Type == AnimalType.Dog);
var dogs3 = animals.Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any());


var dogsFromDb1 = session.Query<IDog>();
var dogsFromDb2 = session.Query<Animal>().Where(a => a.Type == AnimalType.Dog);
var dogsFromDb3 = session.Query<Animal>().Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute),false).Any());


public void DoSomething(IDog dog)
{
}
public void DoSomething(Animal animal)
{
    if(animal.Type != AnimalType.Dog)
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}
public void DoSomething(Animal animal)
{
    if(!animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}

代码的最后一部分主要是我用来做什么的。我可以看到接口版本明显较短,并且在编译时进行了检查。
肯德尔·弗雷

我有一个类似的用例,但大多数.net开发人员强烈建议不
要这样做

-1

如果一个接口可以继承另一个接口而不添加新成员,那么如果该接口的所有实现都有望能够有效地执行前者的某些实现可能做不到的事情,那么使用该接口就没有问题。确实,这是一个很好的模式,应该在诸如.net Framework之类的东西中更多地使用IMHO,尤其是因为其类自然会满足更高派生的接口所隐含的约束的实现者可以表明,仅通过实现更高派生的接口即可,而无需更改任何成员实现代码或声明。

例如,假设我有以下接口:

接口IMaybeMutableFoo {
  Bar Thing {get; set;} //其他属性
  bool IsMutable;
  IImmutableFoo AsImmutable();
}
接口IImmutableFoo:IMaybeMutableFoo {};

的实现IImmutableFoo.AsImmutable()有望返回自己;一个可变的类实现IMaybeMutableFoo.AsImmutable()将返回一个包含数据快照的深度不可变的新对象。想要存储其中保存的数据快照的例程IMaybeMutableFoo可能会带来重载:

公共无效的StoreData(IImmutableFoo ThingToStore)
{
  //存储引用就足够了,因为ThingToStore是不可变的
}
公共无效的StoreData(IMaybeMutableFoo ThingToStore)
{
  StoreData(ThingToStore.AsImmutable()); //调用更具体的重载
}

如果编译器不知道要存储的是IImmutableFoo什么,它将调用AsImmutable()。如果该项已经是不可变的,则该函数应该快速返回,但是通过接口进行的函数调用仍然需要时间。如果编译器知道该项目已经是IImmutableFoo,则可以跳过不必要的函数调用。


Downvoter:想发表评论吗?有时候继承接口而没有添加任何新东西是很愚蠢的,但这并不意味着没有任何时候它是一种好技术(恕我直言,未得到充分利用)。
超级猫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.