C# 在这种情况下,我应该使用接口还是抽象类?

C# 在这种情况下,我应该使用接口还是抽象类?,c#,oop,interface,abstract-class,C#,Oop,Interface,Abstract Class,我有一个包含几个方法的类,其中一个是Save()方法。我希望有不同的Save实现:Save to DB,Save to File,等等。所以,假设这个类: MyClass Add() Remove() Save() 添加和删除实现对于所有人来说都是通用的。只有保存是不同的。在这种情况下,使用另存为抽象方法创建抽象类是否更合适?或者使用接口有什么好处?如果我要在这里使用接口,我需要将Add和Remove的实现复制到实现该接口的所有类中,对吗?在这里,仅仅有一个ISave接口和一个S

我有一个包含几个方法的类,其中一个是Save()方法。我希望有不同的Save实现:Save to DB,Save to File,等等。所以,假设这个类:

MyClass 
  Add()
  Remove()
  Save()
添加和删除实现对于所有人来说都是通用的。只有保存是不同的。在这种情况下,使用另存为抽象方法创建抽象类是否更合适?或者使用接口有什么好处?如果我要在这里使用接口,我需要将Add和Remove的实现复制到实现该接口的所有类中,对吗?在这里,仅仅有一个ISave接口和一个Save方法是没有意义的,因为我的所有实现类仍然需要定义Add和Remove

只是想更好地理解接口和抽象类的用法


我不确定编程语言是否会有所不同,但我使用的是C#。

我建议两者都使用。由于这两个派生类之间有许多共同的代码,所以继承层次结构是有意义的。然而,如果您提取类的接口并始终通过该接口访问它们,那么如果您发现需要一个具有不同添加、保存和删除方法的新类,您将有更多的灵活性。

在您的特定场景中,我建议您使用抽象类。根据MSDN,适用于您的场景的建议是:

  • 如果希望在组件的所有实现中提供通用的、实现的功能,请使用抽象类。抽象类允许您部分实现类,而接口不包含任何成员的实现
在抽象类中,您可以为“添加”和“删除”方法提供功能,并让实现者需要使用抽象重写Save方法(或在VB中使用MustOverride)


有关更多信息,请参阅全文。

查看策略设计模式:

您可以创建一个ISave接口,并为需要使用的每个保存方法创建该接口的一个实现。文件存储、数据库存储等


您可以将ISave参数添加到MyClass的构造函数中,然后根据要使用的保存方法将正确的实现传递给您的类。

两者都可以使用

首先,定义一个接口

public interface IMyInterface
{
    bool Add();
    bool Remove();
    bool Save();
}
然后定义一个抽象类

public abstract MyBaseClass : IMyInterface
{
    public bool Add() { ... }

    public bool Remove() { ... }

    public abstract bool Save();
}
并在实现MyBaseClass的类中实现
Save
变体

现在,当您需要传递对象时,在代码中的每个地方都使用IMyInterface

这也给了你测试东西的能力。 当“添加”和
Remove
点击数据库以从表中添加/删除内容时,测试变得很困难。 然后,您需要设置一个测试数据库环境,将测试指向该环境,并且您必须确保在运行测试时始终在干净的表上操作。 但是,如果您只想测试调用您的
Add
函数的某个对象的行为,而当
Add
返回
true
false
时,这些函数的行为会有所不同,该怎么办

public class Foo
{
    public int AddRange(MyBaseClass my, int count)
    {
        int retval = 0;
        while (count > 0)
        {
            if (my.Add())
                ++retval;
            --count;
        }
        return retval;
    }
}
这现在很难测试,因为
Add
绑定到
MyBaseClass
。它会访问数据库和所有在测试过程中可能不可靠的东西。但幸运的是,您使用的是接口。因此,将
AddRange
的签名更改为

public int AddRange(IMyInterface my, int count);
并定义您的测试

[TestMethod]
void AddRangeReturns0ForFailedAdd()
{
    var foo = new Foo();
    Assert.AreEqual(0, foo.AddRange(new Fail(), 0));
    Assert.AreEqual(0, foo.AddRange(new Fail(), 1));
    Assert.AreEqual(0, foo.AddRange(new Fail(), 100));
}

class Fail : IMyInterface
{
     public bool Add() { return false; }

     public bool Remove() { return false; }

     public bool Save() { return false; }
}

您可以将程序设计为非常有效地使用这两种方法。@Servy如果我选择了接口路线,我如何在不复制用于添加和删除的实现的情况下实现它?虽然这两种方法都可以工作,但C#并不真正支持接口上的具体方法。当需要时,您可以使用扩展方法来解决这个问题,但是如果您有一个具体的实现,通常是一个抽象类。也就是说,@dbc让抽象类实现一个接口的建议不错。@emodendroke这样抽象类就有了Add和Remove实现,并实现了Save接口,但是具体的类将是那些实现和实现ISave接口契约的类?这可能吗?@Prabhu,正如我从一开始就说的那样。我的全部前提是两者都不是特别好。在这种情况下,你会如何使用两者?如果我有一个Dmbyclass和FileMyClass类,它们将从MyClass抽象类继承,然后还实现一个ISave接口,该接口只有Save方法?@Prabhu使您的抽象类实现该接口。在这种情况下,当您使用该接口时,该接口为代码本身添加了零函数值。这实际上只是说明了一种完全不相关的做法,即为了测试的目的,让您编写的所有东西都通过一个接口实现它的整个公共API。取决于应用程序的测试方式,这可能是合适的,但与此处描述的问题无关。@esskar我最终同意Joel的答案(和Servy的评论),但感谢您的回答,因为它确实帮助我了解了其他方法。