Является ли функция интерфейсов главным образом для использования функций, не зная, как создается класс? - PullRequest
3 голосов
/ 27 июня 2009

Поскольку я понимаю, что интерфейсы - это контракты, я интерпретирую его как контрактное слово, то есть должно иметь то, что указано в интерфейсе (ex open, close, read, write для интерфейса, обрабатывающего файлы).

Но что мне трудно понять, так это то, что вам нужно иметь интерфейс, который говорит вам, на что вообще должен уметь класс, разве вы не знаете об этом уже после того, как написали это в спецификации интерфейса?

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

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

Я взял большинство своих предположений и интерпретаций из этого вопроса и этого поста vbforums

Ответы [ 10 ]

4 голосов
/ 27 июня 2009

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

Простой пример: списки в Java. Список - это интерфейс. Две распространенные реализации - ArrayList и LinkedList. Каждый ведет себя по-разному, но соблюдает один и тот же контракт. Под этим я имею в виду, что ArrayList имеет O (1) (постоянный) доступ, тогда как LinkedList имеет O (n) доступ.

Если вы еще не понимаете, что означают O (1) и O (n), я предлагаю вам взглянуть на Простое английское объяснение Большого O .

Причина, по которой вы делаете это даже в своем собственном коде (то есть в том, что не является или не будет публичным API), заключается в следующем:

  • облегчить модульное тестирование: вы можете макетировать интерфейс, тогда как вы не можете (или не можете легко) макетировать класс; и
  • , чтобы позволить вам позже изменить реализацию, не затрагивая вызывающий код.
3 голосов
/ 27 июня 2009

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

Например, допустим, у вас было приложение с графическим интерфейсом, в котором пользователи могли входить и выходить из системы. Когда пользователи выходят из системы, вы можете, скажем, изменить метку «В настоящий момент вошел в систему как« такой-то и такой-то »» и закрыть все видимые диалоговые окна.

Теперь у вас есть класс User с методом logOut, и всякий раз, когда вызывается logOut, вы хотите, чтобы все это происходило. Один из способов сделать это - использовать метод logOut для решения всех этих задач:

// Bad!
public void logOut() {
    userNameLabel.setText("Nobody is logged in");
    userProfileWindow.close();
}

Это не одобряется, потому что ваш класс User теперь тесно связан с вашим графическим интерфейсом. Было бы лучше, если бы класс User был тупее и не делал так много. Вместо того, чтобы закрывать userProfileWindow, он должен просто сказать userProfileWindow, что пользователь вышел из системы, и позволить userProfileWindow делать все, что он хочет (он хочет закрыть себя).

Способ сделать это - создать общий интерфейс UserListener с методом loggedOut, который вызывается классом User при выходе пользователя из системы. Любой, кто хочет знать, когда пользователь входит в систему и выходит из нее, затем реализует этот интерфейс.

public class User {
    // We'll keep a list of people who want to be notified about logouts. We don't know
    // who they are, and we don't care. Anybody who wants to be notified will be
    // notified.
    private static List<UserListener> listeners;

    public void addListener(UserListener listener) {
        listeners.add(listener);
    }

    // This will get called by... actually, the User class doesn't know who's calling
    // this or why. It might be a MainMenu object because the user selected the Log Out
    // option, or an InactivityTimer object that hasn't seen the mouse move in 15
    // minutes, who knows?
    public void logOut() {
        // Do whatever internal bookkeeping needs to be done.
        currentUser = null;

        // Now that the user is logged out, let everyone know!
        for (UserListener listener: listeners) {
            listener.loggedOut(this);
        }
    }
}

// Anybody who cares about logouts will implement this interface and call
// User.addListener.
public interface UserListener {
    // This is an abstract method. Each different type of listener will implement this
    // method and do whatever it is they need to do when the user logs out.
    void loggedOut(User user);
}

// Imagine this is a window that shows the user's name, password, e-mail address, etc.
// When the user logs out this window needs to take action, namely by closing itself so
// this information isn't viewable by other users. To get notified it implements the
// UserListener interface and registers itself with the User class. Now the User.logOut
// method will cause this window to close, even though the User.java source file has no
// mention whatsoever of UserProfileWindow.
public class UserProfileWindow implements UserListener {
    public UserProfileWindow() {
        // This is a good place to register ourselves as interested observers of logout
        // events.
        User.addListener(this);
    }

    // Here we provide our own implementation of the abstract loggedOut method.
    public void loggedOut(User user) {
        this.close();
    }
}

Порядок действий будет выглядеть следующим образом:

  1. Приложение запускается, и пользователь входит в систему. Она открывает ее UserProfileWindow.
  2. UserProfileWindow добавляет себя как UserListener.
  3. Пользователь бездействует и не касается клавиатуры или мыши в течение 15 минут.
  4. Воображаемый InactivityTimer класс уведомлений и звонков User.logOut.
  5. User.logOut обновляет модель, очищая переменную currentUser. Теперь, если кто-нибудь спросит, никто не вошел в систему.
  6. User.logOut проходит по списку слушателей, вызывая loggedOut() для каждого слушателя.
  7. Вызывается метод UserProfileWindow *1055*, который закрывает окно.

Это здорово, потому что этот класс User абсолютно ничего не знает о том, кому нужно знать о событиях выхода из системы. Он не знает, что метка имени пользователя должна быть обновлена, что окно профиля должно быть закрыто, ничего из этого. Если позже мы решим, что нужно выполнить больше действий, когда пользователь выходит из системы, класс User вообще не нужно изменять.

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

2 голосов
/ 27 июня 2009

Но что мне трудно понять, так это то, что вам нужно иметь интерфейс, который говорит вам, что должен вообще делать класс, разве вы не знаете об этом уже после того, как написали это в спецификации интерфейса?

Это также хорошо, когда вы пишете внешне доступный код. В этом случае автор кода не является пользователем Interface. Если вы доставляете библиотеку пользователям, вы можете задокументировать только Interface и позволить Class меняться в зависимости от контекста или развиваться с течением времени без изменения Interface.

1 голос
/ 27 июня 2009

Допустим, у вас есть два класса Car и Gorilla. Эти два класса не имеют ничего общего друг с другом. Но, скажем, у вас также есть класс, который может разрушать вещи. Вместо того, чтобы определять метод, который берет Car и разбивает его, а затем, имея отдельный метод, который берет Gorilla и разбивает его, вы создаете интерфейс под названием ICrushable ...

interface ICrushable
{
  void MakeCrushingSound();
}

Теперь ваша машина и ваш Gorilla могут использовать ICrushable, а ваш Car - ICrushable, и ваша дробилка может работать на ICrushable вместо Car и Gorilla ...

public class Crusher
{
  public void Crush(ICrushable target)
  {
    target.MakeCrushingSound();
  }
}

public class Car : ICrushable
{
    public void MakeCrushingSound()
    {
        Console.WriteLine("Crunch!");
    }
}

public class Gorilla : ICrushable
{
    public void MakeCrushingSound()
    {
        Console.WriteLine("Squish!!");
    }
}

static void Main(string[] args)
{
  ICrushable c = new Car();      // get the ICrushable-ness of a Car
  ICrushable g = new Gorilla();  // get the ICrushable-ness of a Gorilla

  Crusher.Crush(c);
  Crusher.Crush(g);
}

И Виола! У вас есть дробилка, которая может раздавить автомобили и получить "Хруст!" и может раздавить горилл и получить «Squish!». Без необходимости проходить через процесс нахождения отношений типов между Cars и Gorillas и с проверкой типов во время компиляции (вместо оператора переключения времени выполнения).

Теперь рассмотрим нечто менее глупое ... Класс, который можно сравнить (например, IComparable ). Класс определит, как вы сравниваете две вещи своего типа.

За комментарий: Хорошо, давайте сделаем так, чтобы мы могли отсортировать массив горилл. Во-первых, мы добавляем что-то для сортировки, скажем, по весу (пожалуйста, игнорируйте сомнительную бизнес-логику сортировки горилл по весу ... это здесь не актуально). Затем мы реализуем ICompararble ...

public class Gorilla : ICrushable, IComparable
{
    public int Weight
    {
        get;
        set;
    }

    public void MakeCrushingSound()
    {
        Console.WriteLine("Squish!!");
    }

    public int CompareTo(object obj)
    {
        if (!(obj is Gorilla))
        {
            throw (new ArgumentException());
        }

        var lhs = this;
        var rhs = obj as Gorilla;

        return (lhs.Weight.CompareTo(rhs.Weight));
    }

}

Обратите внимание, что мы "обошли" ограничение одиночного наследования , которое есть во многих языках. Нам разрешено реализовать столько интерфейсов, сколько мы захотим. Теперь, делая это, мы можем использовать функциональность, написанную более 10 лет назад для класса, который я только что написал сегодня ( Array.Sort , Array.BinarySearch ). Теперь мы можем написать следующий код ...

var gorillas = new Gorilla[] { new Gorilla() { Weight = 900 },
                               new Gorilla() { Weight = 800 },
                               new Gorilla() { Weight = 850 }
};

Array.Sort(gorillas);
var res = Array.BinarySearch(gorillas, 
    new Gorilla() { Weight = 850 });

Мои гориллы сортируются, и бинарный поиск находит гориллу с весом 850.

1 голос
/ 27 июня 2009

Предположим, вы пишете набор классов, которые реализуют оружие. У вас могут быть пистолет, винтовка и пулемет. Затем предположим, что вы решили использовать эти классы таким образом, чтобы выполнять действие fire () для каждого из этих орудий. Вы могли бы сделать это так:

private Pistol p01;
private Pistol p02;
private Rifle  r01;
private MachineGun mg01;

public void fireAll() {
    p01.fire();
    p02.fire();
    r01.fire();
    mg01.fire();
}

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

Давайте создадим интерфейс, который будет реализован каждым из вышеперечисленных орудий, назовем его Firearm. Теперь мы можем сделать это.

private Firearm[] firearms;

public void fireAll() {
    for (int i = 0; i < firearms.length; ++i) {
        firearms[i].fire();
    }
}

Это немного поддается изменениям, не правда ли?

0 голосов
/ 27 июня 2009

интерфейс уменьшает то, от чего зависит клиент (http://en.wikipedia.org/wiki/Dependency_inversion_principle)., он допускает несколько реализаций и возможность изменять реализации во время выполнения.

0 голосов
/ 27 июня 2009

Насколько я понимаю ваш вопрос, зачем нам нужны интерфейсы? право ? Ну, они нам не нужны:)

Например, в C ++, когда вы определяете шаблон ... скажем, фиктивная функция, которая выглядит как ::

template <typename T>
void fun(const T& anObjectOfAnyType)
{
    anyThing.anyFunction();
}

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

На самом деле это очень подвержено ошибкам. Причина в том, что если мы подключим тип, у которого нет функции anyFunction, мы получим ошибку, эта ошибка будет отличаться каждый раз, каждая строка, которую компилятор не может перевести, выдаст для нее ошибку. Вы получаете много ошибок за ТОЛЬКО пропущенную вещь! Новый тип не имеет необходимых функций для корректной работы с нашими развлечениями, например.

Теперь интерфейсы решают всю эту проблему, как? Если тип имеет требуемые функции, то он подходит, если нет, то компилятор выдаст ошибку, что тип не подходит.

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

Спасибо,

0 голосов
/ 27 июня 2009

Абстракция: Код, написанный для использования интерфейса, можно использовать повторно, и его никогда не нужно менять. В приведенном ниже случае подпрограмма будет работать с System.Array, System.ArrayList, System.Collection.CollectionBase, List T, поскольку все они реализуют IList. Существующий класс может легко реализовать интерфейс, даже если этот класс наследует другой класс. Вы можете даже написать свой класс для реализации IList для нас в подпрограмме. Или другая программа может также реализовать интерфейс для использования в подпункте.

public sub DoSomething (значение по умолчанию в качестве IList) конец суб

Вы также можете использовать несколько интерфейсов в классе, поэтому класс может быть как IList, так и IEnumerable, в большинстве языков вы можете наследовать один класс.

Я бы также посмотрел, как они используются в различных фреймворках.

0 голосов
/ 27 июня 2009

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

0 голосов
/ 27 июня 2009

Если вы когда-нибудь захотите вернуться к своему старому коду, вы поблагодарите себя за то, что создали несколько интерфейсов. Нет ничего более расстраивающего, чем желание реализовать новый тип чего-то существующего, только чтобы понять, что вы не помните, что должен был иметь новый объект.

В Java вы можете реализовать несколько интерфейсов, которые имитируют множественное наследование (объект с несколькими родительскими объектами). Вы можете расширить только один суперкласс.

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