Понимание интерфейсов V классов - PullRequest
3 голосов
/ 16 октября 2019

Привет, я знаю, что это сложный вопрос, но после прочтения следующего поста В чем разница между интерфейсом и классом, и почему я должен использовать интерфейс, когда я могу реализовать методы непосредственно в классе? Я изо всех сил пытаюсь понять, почему интерфейс действительно должен использоваться. Извините за основной вопрос, но пока я получаю теорию как контракт между интерфейсом и классом, я не могу понять, насколько это полезно. Я знаю, что это может помочь вам легко создавать объекты, но я чувствую, что что-то упустил.

Я прочитал так много постов здесь и по всему Интернету о том, как использовать Интерфейс, но половину времени я чувствую себя хорошо, если вы создаете класс и наследуете его, разве это не будет делать то же самое? что мне здесь не хватает?

Ответы [ 3 ]

5 голосов
/ 16 октября 2019

Почему интерфейсы?

Вы водите машину? Если нет, то я предполагаю, что вы знаете, что влечет за собой вождение автомобиля (руль, акселератор, тормоз). Остальная часть ответа предполагает, что вы водите машину и у вас есть машина, которая отличается от моей.

Вы когда-нибудь водили мою машину? Нет. Но если бы у вас был доступ, вы могли бы водить мою машину, не изучая, как водить мою машину? Да.
То же самое относится и ко мне. Я никогда не водил вашу машину, но я смог бы водить ее без необходимости учиться водить ее.

Почему это так? Поскольку все автомобили имеют один и тот же интерфейс . Руль, акселератор справа, тормоз посередине. Нет двух одинаковых автомобилей, но они построены таким образом, что взаимодействие между водителем и любой машиной абсолютно одинаково.

Сравните это с истребителем F16. Умение водить машину не дает вам возможности управлять самолетом , поскольку его интерфейс отличается . У него нет руля, нет педали акселератора / тормоза.

Главное преимущество очевидно: водителям не нужно учиться водить каждую машину индивидуально.

Теперь, чтобы завершить аналогию, общая концепция автомобиля - это интерфейс, тогда как интерфейсконкретные машины это классы. Главное преимущество очевидно: вам не нужно писать собственный код для каждого подобного класса.


Практический пример

public class BMW 
{
    public SteeringWheel SteeringWheel { get; set; }
    public Pedal Accelerator { get; set; }
    public Pedal Brake { get; set; }
}

public class BMWDriver
{
    public void ParticipateInRace(BMW myBMW) 
    {
        myBMW.Accelerator.Press();

        myBMW.SteeringWheel.TurnLeft();
        myBMW.SteeringWheel.TurnRight();

        myBMW.Accelerator.Release();
        myBMW.Brake.Press();

        myBMW.Brake.Release();
    }
}

Только для этого драйверазнает, как водить BMW.

public class Audi 
{
    public SteeringWheel SteeringWheel { get; set; }
    public Pedal Accelerator { get; set; }
    public Pedal Brake { get; set; }
}

public class AudiDriver
{
    public void ParticipateInRace(Audi myAudi) 
    {
        myAudi.Accelerator.Press();

        myAudi.SteeringWheel.TurnLeft();
        myAudi.SteeringWheel.TurnRight();

        myAudi.Accelerator.Release();
        myAudi.Brake.Press();

        myAudi.Brake.Release();
    }
}

Этот водитель знает, как водить Audi.

Но на самом деле, водитель сможет водить любой автомобиль (у которого есть рулевое колесо и две педали).

Итак, как нам сказать компилятору, что можно использовать любой автомобиль? Мы даем им общее, что они оба разделяют: интерфейс.

public interface ICar
{
    SteeringWheel SteeringWheel { get; }
    Pedal Accelerator { get; }
    Pedal Brake { get; }
}

public class BMW : ICar { /* same as before */ }

public class Audi : ICar { /* same as before */ }

public class Driver
{
    public void ParticipateInRace(ICar anyCar)
    {
        anyCar.Accelerator.Press();

        anyCar.SteeringWheel.TurnLeft();
        anyCar.SteeringWheel.TurnRight();

        anyCar.Accelerator.Release();
        anyCar.Brake.Press();

        anyCar.Brake.Release();
    }
}

Теперь у нас есть более обобщенный Driver, который способен управлять любой машиной с рулевым колесом и двумяпедали.


Почему бы не наследовать?

Половина времени у меня все хорошо, если вы создаете класс и наследуете его, разве это не будет делать то же самое? что мне здесь не хватает?

В некоторых случаях наследование сработало бы. Однако наследование, как правило, является неполноценным решением, особенно когда вы получаете более сложные кодовые базы или более сложные архитектуры.

Не волнуйтесь, все разработчики когда-то любили наследование, а затем должны были научиться не использовать наследование как лекарство от всех болезней. Это часть обычного жизненного цикла разработчика:)

Одна из главных причин, по которой не может быть производным от нескольких классов, но вы можете реализовать несколько интерфейсов .

Допустим, у нас есть три вида спорта, которые можно делать

public class Runner 
{  
    public void Run() { /* running logic */ } 
}

public class Swimmer
{  
    public void Swim() { /* swimming logic */ } 
}

public class Cyclist
{  
    public void Cycle() { /* cycling logic */ } 
}

Теперь нам нужно создать специализированный вид спорта, который включает бег, например, баскетбол.

public class BasketBallPlayer : Runner 
{ 
    public void ThrowBall() { /* throwing logic */ }

    // Running is already inherited from Runner
}

Отлично, проблем пока нет. Но теперь нам нужно создать класс триатлета, который включает в себя все три вида спорта (бег, плавание, езда на велосипеде)

public class Triathlete: Runner, Swimmer, Cyclist { ... }

И это то, где компилятор ломается. Он не позволяет вам наследовать от нескольких базовых классов одновременно. Причины гораздо глубже, чем можно вникнуть в этот ответ, Google, если вы хотите узнать больше.

Однако, если бы мы использовали интерфейсы:

public interface IRunner
{
    void Run();
}

public interface ISwimmer
{
    void Swim();
}

public interface ICyclist
{
    void Cycle();
}

Тогда компилятор позволил быэто:

public class Triathlete: IRunner, ISwimmer, ICyclist 
{ 
    public void Run() { /* running logic */ } 

    public void Swim() { /* swimming logic */ } 

    public void Cycle() { /* cycling logic */ } 
}

И именно поэтому интерфейсы обычно побеждают наследование (среди прочих причин).

Есть и другие причины, почему интерфейсы, как правило, лучше, но это самая большая из них. Если вы хотите получить более подробное объяснение, воспользуйтесь им, но я не могу вникать во все это для ответа StackOverflow.

0 голосов
/ 16 октября 2019

Ну, по сути, и так мало слов, сколько я могу придумать для каждого случая:

  • Аннотация должна использоваться, когда у вас разные сущности, имеющие общее поведение.
  • Интерфейс должениспользоваться, когда вы хотите, чтобы разные сущности могли work с одним и тем же кодом

Например, вы бы использовали абстрактные, когда:

Вы хотите создать приложение, которое обрабатывает животных,Вы создаете абстрактный класс Animal, который содержит несколько полей NoLegs, HasTail, NoEyes. Вы создаете классы Dog, Cat и Panda, все они наследуются от Animal. Теперь у вас есть 3 класса, но с общим кодом, определенным в 1 другом классе, потому что все они имеют эти описательные черты.

Теперь для интерфейса: внутри службы в вашем проекте вы создаете метод с именем'StartRunning. Определение метода:

public void StartRunning(List<ICanRun> thingsThatRun)
{
    thingsThatRun.forEach(t => t.StartRunning());
}

Теперь вы возвращаетесь к своему абстрактному классу животных и объявляете, что он должен реализовывать интерфейс ICanRun, а компилятор заставит вас пойти и определить метод наDog, Cat и Panda классы. Самое замечательное в том, что:

  1. Вы определили свойства, которые являются общими для разных объектов только один раз. (количество глаз является характерной чертой при описании животных)
  2. Способ бега каждого животного различен. Ваш метод бега собак может «просто бежать прямо», в то время как метод бега вашей кошки может «искать ближайшую стену». и подняться ». Панда, вероятно, throw приведет к тому, что панды не будут работать.

Реальное волшебство интерфейса: потому что ваш метод StartRunning не учитывает классы, а скорее передает объекты, соответствующие ICanRun интерфейс, вы можете иметь другой класс, скажем Bike, который также реализует ICanRun и определяет метод StartRunning. Затем вы можете создать список, содержащий собаку, кошку и велосипед, и выбросить эти 3 несвязанных класса в один и тот же список и передать этот список в StartRunning, и все они начнут работать.

List<ICanRun> runableThings = new List<ICanRun>(){new Dog(), new Cat(), new Bike()};
StartRunning(runableThings);

Я надеюсь, что этот пример помогает

0 голосов
/ 16 октября 2019

один сценарий, в котором вы (создатель общего алгоритма) просто не знаете заранее реализацию.

Чтобы преобразовать их в реальные сценарии: FxCop + StyleCop оба используют шаблон посетителя для сканирования кода,Таким образом, создатели инструмента (FxCop) в этом примере имеют некоторый базовый код, который связан с некоторым пользовательским интерфейсом / интерфейсом командной строки и ожидает некоторые определенные свойства в результате сканирования, такие как серьезность / проблема и т. Д.

И в то время как FxCopПоставляется с правилами по умолчанию, вы, как конечный пользователь, также можете расширить эти правила по своему вкусу. Единственный способ сделать это для FxCop - это использовать polymorpishm Interfaces / Abstract Classes.

Таким образом, инструмент FxCop ожидает экземпляр правила, который обнаруживает что-то и сообщает об успехе или неудаче.

Но вашорганизация может иметь собственное правило, которое нужно только вам. Допустим, это что-то вроде: все наши пространства имен должны начинаться с myorg.mytool

Это пример того, где вы должны использовать абстракцию (не могу просто реализовать код в классе заранее), так как Microsoft не знаетчто-нибудь, в частности, о пользовательских правилах кода, которые вы применяете в своей организации.

Другой пример - это способ разделения кода домена и инфраструктуры в доменно-управляемом дизайне.

Итак, допустим, выесть приложение для сбора книг. Один, где вы можете получить книгу, все книги - по автору и т. Д.

После этого вы получите вызов типа домена, например BookRepository, где все ваши книги будут сохранены. Есть две стороны этого: 1. Домен, в котором размещена вся логика обработки книг, и 2. Код персистентности (IO / База данных или что-то еще).

Причина разделения этих двух заключается в том, что тогда логика домена (бизнеслогика) не запутывается с кодом персистентности. Код домена не хочет знать, как сохраняется книга. Он заботится только о том, что вы можете сделать с книгой (получить по автору, купить, продать и т. Д.).

Интерфейс в этом случае появляется, когда вы помещаете интерфейс в свой код домена, называемый чем-то вроде IBookRepository ивы продолжаете создавать весь код, который вам нужен, с помощью модульных тестов. На данный момент вас не волнует, как хранятся книги - вы просто заботитесь о том, чтобы они хранились. Затем другая команда или позже, вы можете погрузиться в детали о том, как книга восстановлена. В базе данных, в кэше или что-то еще. Персистентный код также может развиваться, не затрагивая код домена, который является неотъемлемой частью принципов непрерывного выпуска: как можно меньше обновлений. Другими словами, он позволяет отправлять обновления инфраструктуры, не затрагивая бизнес-код. Возможно, ваше приложение работает отлично, но вы не хотите обновлять драйверы базы данных.

Абстрактные классы - это нечто среднее между интерфейсами и классами, но их следует использовать аналогично интерфейсам / они близки по использованию к интерфейсам, чем классы.

** наконец, есть еще одна причина для использованияинтерфейсы, то есть интерфейс может рассматриваться как аспект, и вы можете применять несколько интерфейсов (аспектов) к одному классу (множественное наследование) с небольшим трением, тогда как размещение его в классах вынуждает вас выполнять одиночное наследование, которое может привести к большому и чрезмерно сложномуИерархии наследства.

Надеюсь, это вам немного поможет

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...