Oop 何时使用接口而不是抽象类,反之亦然?
这可能是一个通用的OOP问题。我想根据接口和抽象类的用法对它们进行一般性比较Oop 何时使用接口而不是抽象类,反之亦然?,oop,inheritance,interface,abstract-class,Oop,Inheritance,Interface,Abstract Class,这可能是一个通用的OOP问题。我想根据接口和抽象类的用法对它们进行一般性比较 什么时候想使用接口,什么时候想使用抽象类?如果你想提供一些基本的实现,那么就使用抽象类。在java中,你可以从一个(抽象)类继承来“提供”功能,你可以实现许多接口来“确保”功能抽象类可以具有共享状态或功能。接口只是提供状态或功能的承诺。一个好的抽象类将减少必须重写的代码量,因为它的功能或状态可以共享。接口没有要共享的定义信息这可能是一个非常困难的调用 我可以给出一个指针:一个对象可以实现很多接口,而对象只能继承一个基类
什么时候想使用接口,什么时候想使用抽象类?如果你想提供一些基本的实现,那么就使用抽象类。在java中,你可以从一个(抽象)类继承来“提供”功能,你可以实现许多接口来“确保”功能抽象类可以具有共享状态或功能。接口只是提供状态或功能的承诺。一个好的抽象类将减少必须重写的代码量,因为它的功能或状态可以共享。接口没有要共享的定义信息这可能是一个非常困难的调用
我可以给出一个指针:一个对象可以实现很多接口,而对象只能继承一个基类(在现代的OO语言中,像C++一样,我知道C++有多重继承性,但这不是皱眉吗?)
< P>抽象类可以实现。 接口没有实现,它只是定义了一种契约也可能存在一些与语言相关的差异:例如,C#没有多重继承,但可以在一个类中实现多个接口。答案因语言而异。例如,在Java中,一个类可以实现(继承)多个接口,但只能继承一个抽象类。因此,接口为您提供了更大的灵活性。但是,在C++中,这不是真的。 < P>纯基于继承,你将使用一个抽象的概念,在这里你定义了清晰的子代,抽象的关系(即,动物-CAT)和/或需要继承虚拟或非公共属性,特别是共享状态(接口不能支持)。
您应该尽可能地支持组合(通过依赖项注入)而不是继承,并且注意,作为契约的接口支持单元测试、关注点分离和(语言变化)多重继承,这是抽象所不能做到的。我写了一篇关于这方面的文章: 总结: 当我们谈论抽象类时,我们定义的是对象类型的特征;指定对象是什么
当我们谈论一个接口并定义我们承诺提供的功能时,我们谈论的是建立一个关于对象可以做什么的契约。就我个人而言,我几乎从来都不需要编写抽象类 大多数时候我看到抽象类被(mis)使用,这是因为抽象类的作者使用的是“模板方法”模式 “模板方法”的问题在于,它几乎总是有点可重入性,“派生”类不仅知道它正在实现的基类的“抽象”方法,还知道基类的公共方法,尽管大多数时候它不需要调用它们 (过于简化)例如: 因此,在这里,这个类的作者编写了一个通用算法,并打算让人们通过提供自己的“钩子”——在本例中是一个“比较”方法来“专门化”它 因此,预期用途如下:
class NameSorter : QuickSorter
{
public bool compare(object lhs, object rhs)
{
// etc.
}
}
问题在于,您不适当地将两个概念结合在一起:
interface IComparator
{
bool compare(object lhs, object rhs);
}
class QuickSorter
{
private readonly IComparator comparator;
public QuickSorter(IComparator comparator)
{
this.comparator = comparator;
}
public void Sort(object[] items)
{
// usual code but call comparator.Compare();
}
}
class NameComparator : IComparator
{
bool compare(object lhs, object rhs)
{
// same code as before;
}
}
现在请注意:我们只有接口,以及这些接口的具体实现。在实践中,您不需要任何其他东西来进行高级OO设计
为了“隐藏”我们已经通过使用“快速排序”类和“NameComparator”实现了“名称排序”,我们仍然可以在某个地方编写一个工厂方法:
ISorter CreateNameSorter()
{
return new QuickSorter(new NameComparator());
}
任何时候你有一个抽象类,你可以这样做。。。即使在基类和派生类之间存在自然的重新进入关系,使它们显式通常也是值得的
最后一个想法:我们在上面所做的就是使用“快速排序”函数和“名称比较”函数“组合”一个“名称排序”函数。。。在函数式编程语言中,这种编程风格变得更加自然,代码更少。如果你头脑中有了清晰的概念,什么时候做一件非常简单的事情 抽象类可以派生,而接口可以实现。这两者之间有些不同。派生抽象类时,派生类和基类之间的关系是“是”关系。e、 例如,狗是动物,羊是动物,这意味着派生类从基类继承了一些属性 而对于接口的实现,关系是“可以”。e、 例如,狗可以是间谍狗。狗可以是马戏团的狗。一只狗可以是一只赛狗。这意味着你要用某些方法来获取某些东西
我希望我能说清楚。一个有趣的地方是,当您需要向一组(相关或不相关的)对象添加额外的功能时,接口比抽象类更好。如果您不能给它们一个基本抽象类(例如,它们是
密封的
或已经有一个父类),您可以给它们一个虚拟(空)接口,然后简单地为该接口编写扩展方法<
ISorter CreateNameSorter()
{
return new QuickSorter(new NameComparator());
}
public abstract class BaseAnimal
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
}
public class Dog : BaseAnimal
{
public Dog() : base(4) { }
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
}
public static int CountAllLegs(List<BaseAnimal> animals)
{
int legCount = 0;
foreach (BaseAnimal animal in animals)
{
legCount += animal.NumberOfLegs;
}
return legCount;
}
public interface IMakeSound
{
void MakeSound();
}
public class Car : IMakeSound
{
public void MakeSound() => Console.WriteLine("Vroom!");
}
public class Vuvuzela : IMakeSound
{
public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!");
}
List<IMakeSound> soundMakers = new List<ImakeSound>();
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Vuvuzela());
foreach (IMakeSound soundMaker in soundMakers)
{
soundMaker.MakeSound();
}
public interface IMakeSound
{
void MakeSound();
}
public abstract class BaseAnimal : IMakeSound
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
public abstract void MakeSound();
}
public class Cat : BaseAnimal
{
public Cat() : base(4) { }
public override void MakeSound() => Console.WriteLine("Meow!");
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
public override void MakeSound() => Console.WriteLine("Hello, world!");
}