Подтвердить что ты не робот

Интерфейсы и путаница абстрактных классов в Java с примерами

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

Создание фигур кажется популярной отправной точкой. Скажем, мы хотим, чтобы моделировать 2D-фигуры. Мы знаем, что у каждой формы будет область. Какая разница между следующими двумя реализациями:

с интерфейсами:

public interface Shape {
    public double area();
}

public class Square implements Shape{
    private int length = 5;
    public Square(){...}

    public double area()
         return length * length;
    }
}

с абстрактным классом:

abstract class Shape {
    abstract public double area();
}

public class Square extends Shape {
    private length = 5;
    public Square(){...}

    public double area(){
        return length * length;
    }

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

Но теперь скажем, что мы хотим описать различные типы треугольников. Мы можем иметь равнобедренные, острые и прямоугольные треугольники. Для меня имеет смысл использовать наследование класса в этом случае. Использование определения "IS-A" : треугольник "Третий треугольник" IS-A. Треугольник "IS-A" . Кроме того, абстрактный класс должен определять поведение и атрибуты, которые являются общими во всех подклассах, поэтому это идеально:

с абстрактным классом

abstract Triangle extends Shape {
    private final int sides = 3;
}
class RightTriangle extends Triangle {
    private int base = 4;
    private int height = 5;

    public RightTriangle(){...}

    public double area() {
        return .5 * base * height
    }
}

Мы можем это сделать и с интерфейсами, с интерфейсом Triangle и Shape. Однако, в отличие от наследования классов (используя отношение "IS-A" для определения того, что должно быть подклассом), я не уверен, как использовать интерфейс. Я вижу два пути:

Первый способ:

  public interface Triangle {
      public final int sides = 3;
  }
  public class RightTriangle implements Triangle, Shape {
      private int base = 4;
      private int height = 5;

      public RightTriangle(){}
      public double area(){
          return .5 * height * base;
      }
  }

Второй способ:

public interface Triangle extends Shape {
     public final int sides = 3;
} 
public class RightTriangle implements Triangle {
    ....

    public double area(){
         return .5 * height * base;
    }
}

Мне кажется, что оба эти способа работают. Но когда вы будете использовать один путь над другим? И есть ли какие-либо преимущества в использовании интерфейсов над абстрактными классами для представления разных треугольников? Несмотря на то, что мы усложнили описание фигуры, использование интерфейса vs abstract class по-прежнему кажется эквивалентным.

Критический компонент для интерфейсов состоит в том, что он может определять поведение, которое может использоваться совместно с несвязанными классами. Таким образом, интерфейс Flyable будет присутствовать в классах Airplane, а также в Bird. Таким образом, в этом случае ясно, что подход интерфейса является предпочтительным.

Кроме того, для создания сложного интерфейса, расширяющего другой интерфейс: Когда следует игнорировать отношения "IS-A" при принятии решения о том, каким должен быть интерфейс? Возьмите этот пример: ССЫЛКА.

Почему "VeryBadVampire" должен быть классом, а "Vampire" - интерфейсом? "VeryBadVampire" IS-A "Vampire" , поэтому я понимаю, что "вампир" должен быть суперклассом (может быть, абстрактным классом). Класс "Вампир" может реализовать "Смертельный", чтобы сохранить свое смертоносное поведение. Кроме того, "вампир" IS-A "Monster", поэтому "монстр" должен быть классом. Класс Vampire также может реализовать интерфейс под названием "Dangerous", чтобы сохранить его опасное поведение. Если мы хотим создать новый монстр под названием BigRat, который опасен, но не смертелен, тогда мы можем создать класс BigRat, который расширяет "Monster" и реализует "Dangerous".

Не будет ли вышеприведенный результат таким же, как использование "Vampire" в качестве интерфейса (описано в ссылке)? Единственное различие, которое я вижу, заключается в том, что использование наследования классов и сохранение отношения "IS-A" устраняет много путаницы. Но это не соблюдается. В чем преимущество этого?

Даже если вы хотите, чтобы монстр делил вампирское поведение, всегда можно переопределить, как объекты представлены. Если бы нам нужен новый тип вампирского монстра под названием "VeryMildVampire", и мы хотели создать вампир-монстра под названием "Чупакабра", мы можем это сделать:

"Класс вампира" расширяет "орудия монстров" "Опасный", "Смертельный", "Кровавый щит"
Класс "VeryMildVampire" расширяет класс "Вампир"
Класс "Чупакабра" расширяет "орудия монстров" BloodSuckable

Но мы также можем это сделать:

'VeryMildVampire' расширяет возможности монстров. Опасный, смертельный, вампирский
"Чупакабра" расширяет орудия "Монстр" Опасный, вампирский

Второй способ создает интерфейс "Vampiric", чтобы мы могли легче определить связанный монстр, а не создавать пучок интерфейсов, которые определяют вампирическое поведение (как в первом примере). Но это нарушает отношения IS-A. Поэтому я смущен...

4b9b3361

Ответ 1

Помните базовую концепцию при использовании абстрактных классов или интерфейсов.

Абстрактные классы используются, когда класс к расширенному более тесно связан с классом, реализующим его, т.е. когда оба имеют отношение родитель-потомок.

В примере:

       abstract class Dog {}

       class Breed1 extends Dog {}

       class Breed2 extends Dog {}

Breed1 и Breed2 - оба типа собаки и имеют какое-то общее поведение как собака.

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

     interface Animal {
         void eat();
         void noise();
     }

     class Tiger implements Animal {}

     class Dog  implements Animal {}

Tiger и Dog - две разные категории, но обе едят и создают шумы, которые различны. Таким образом, они могут использовать питание и шум от Animal.

Ответ 2

Используйте абстрактный класс, если хотите сделать один или несколько методов абстрактными.

Если вы хотите сохранить абстракцию, используйте интерфейс.

Ответ 3

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

Абстрактный класс

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

Интерфейс

  • Включает множественное наследование - вы можете реализовать n количество интерфейсов
  • Позволяет представлять только концептуальные методы (без тел метода)

Обычно используют интерфейсы для предложения "-able" (как в функциональности). Например: -

  • Runnable
  • Observable

Используйте абстрактные классы для чего-то вроде is-a (формат эволюции). Например: -

  • Number
  • Graphics

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

Ответ 4

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

С интерфейсами вы можете иметь классы, реализующие несколько интерфейсов. Однако интерфейс не прочен, если вы хотите использовать его как API. После публикации интерфейса трудно изменить интерфейс, потому что он сломает коды других пользователей.

С абстрактным классом вы можете расширить только один класс. Тем не менее, абстрактный класс долговечен для API, потому что вы можете модифицировать его в более поздних версиях без нарушения кода других людей. Также с абстрактным классом вы можете иметь предопределенную реализацию. Например, в примере вашего треугольника для абстрактного класса у вас может быть метод countEdges(), который по умолчанию возвращает 3.

Ответ 5

Это вопрос, который возникает очень часто, но нет единого "правильного" ответа, который понравится всем.

Классы представляют is-a отношения и интерфейсы представляют поведение can-do. Я обычно придерживаюсь нескольких эмпирических правил:

  • Придерживайтесь класса (абстрактного/конкретного), если вы не уверены, что вам нужен интерфейс.
  • Если вы используете интерфейсы, нарежьте их на очень специфические функции. Если интерфейс содержит более нескольких методов, вы делаете это неправильно.

Кроме того, большинство примеров фигур и лиц (или вампиров, если на то пошло!) обычно являются плохими примерами моделей реального мира. "Правильный" ответ зависит от того, что требует ваше приложение. Например, вы упомянули:

class Vampire extends Monster implements Dangerous, Lethal, BloodSuckable

Действительно ли вашему приложению нужны все эти интерфейсы? Сколько существует различных типов Monster? У вас на самом деле есть классы, отличные от Vampire, которые реализуют BloodSuckable?

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

Ответ 6

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

Где использовать абстрактные классы: Что касается ООП, если есть иерархия наследования, тогда вы должны использовать абстрактный класс для моделирования вашего дизайна.
enter image description here

Где использовать интерфейсы: Когда вам нужно подключить разные контракты (не связанные классы), используя один общий контракт, вы должны использовать интерфейс. В качестве примера возьмем структуру Collection. enter image description here

Очередь, список, набор имеют разные структуры из их реализации. Но все же они имеют общие поведения, такие как add(), remove(). Таким образом, мы можем создать интерфейс под названием Collection, и мы объявили об общих поведениях в интерфейсе. Как вы видите, ArrayList реализует все поведение как из интерфейсов List, так и RandomAccess. Таким образом, мы можем легко добавлять новые контракты без изменения существующей логики. Это называется "кодированием интерфейса".

Ответ 7

Ваш пример формы хорош. Я смотрю на это так:

У вас есть только абстрактные классы, когда у вас есть методы или переменные-члены, которые являются общими. Для вашего примера для Shape у вас есть только один, нереализованный метод. В этом случае всегда используйте интерфейс.

Скажем, у вас был класс Animal. Каждое животное отслеживает, сколько у него конечностей.

public abstract class Animal
{
    private int limbs;
    public Animal(int limbs)
    {
        this.limbs = limbs;
    }

    public int getLimbCount()
    {
        return this.limbs;
    }

    public abstract String makeNoise();
}

Поскольку нам нужно следить за тем, сколько конечностей у каждого животного имеет смысл, имеет смысл иметь переменную-член в суперклассе. Но у каждого животного возникает другой тип шума.

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

Для вашего второго вопроса, вы должны спросить себя об этом.

Треугольник всегда будет формой?

Если это так, вам нужно, чтобы треугольник простирался от интерфейса Shape.

Итак, в заключение - с помощью вашего первого набора примеров кода выберите интерфейс. С помощью последнего набора выберите второй способ.