Путаница с интерфейсами и абстрактными классами в 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" Shape. Кроме того, абстрактный класс должен определять поведение и атрибуты, которые являются общими для всех подклассов, так что это идеально:

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

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;
    }
}

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

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

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

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

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

Даже если вы хотите, чтобы монстр разделял вампирическое поведение, всегда можно переопределить способ представления объектов. Если бы мы хотели новый тип вампирских монстров под названием «VeryMildVampire» и мы хотели создать похожего на вампира монстра под названием «Chupacabra», мы можем сделать это:

& APOS; Вампир & APOS; класс расширяет "Monster" реализует «Опасный», «Смертельный», «BloodSuckable».
& APOS; VeryMildVampire & APOS; класс расширяет «вампир» учебный класс
& APOS; Чупакабра & APOS; класс расширяет "Monster" реализует "BloodSuckable"

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

& APOS; VeryMildVampire & APOS; расширяет "монстр" реализует Опасный, Смертельный, Вампирический
& APOS; Чупакабра & APOS; расширяет "монстр" реализует Опасный, Вампирический

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

 Syed Priom16 нояб. 2014 г., 21:28
Вы объяснили проблему с необходимой ясностью. Цените усилия :)

Ответы на вопрос(7)

Это вопрос, который возникает очень часто, но единого «права» не существует. Ответ, который понравится всем.

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

Stick with a class (abstract/concrete) unless you are certain that you need an interface. If you do use interfaces, slice them into very specific functionality. If an interface contains more than a few methods, you're doing it wrong.

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

class Vampire extends Monster implements Dangerous, Lethal, BloodSuckable

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

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

 Carts09 июл. 2012 г., 08:11
Это интересно. Сколько обобщений происходит в реальном мире? Похоже, что для игр, таких как создание villian, обобщение очень поможет. Я предполагаю, что идея для вампиров состояла в том, что, если бы я хотел создать монстра нового типа, я мог легко выйти из класса монстров. Если два монстра, которые отличаются друг от друга, но имеют некоторые общие характеристики (например, монстры-вампиры и чупакабры), то будет ли хорошо создать интерфейс для вампиров? Это определенно будет лучше, чем создание кровососущих и других вампирических черт, как вы сказали.
 09 июл. 2012 г., 08:38
Я не хотел сказать «не обобщать» но скорее "не обобщайте слишком рано". Обычно я нахожу, что класс реализует максимум один или два интерфейса, и эти интерфейсы естественным образом развиваются по мере роста приложения. Всякий раз, когда я пытаюсь извлечь интерфейсы на ранних этапах, я редко использую эти интерфейсы, а потом они удаляются. Трудно просто взглянуть на пример и сказать: «О, это правильные интерфейсы». фактически не думая о том, как они будут использоваться.

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

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

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

 Carts09 июл. 2012 г., 08:04
Это тоже хороший критерий. Я не думал об этом.

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

Where to use abstract classes: С точки зрения ООП: если существует иерархия наследования, вам следует использовать абстрактный класс для моделирования вашего проекта.
enter image description here

Where to use interfaces: Когда вам нужно соединить разные контракты (не связанные классы), используя один общий контракт, вам следует использовать интерфейс. Давайте возьмем в качестве примера коллекционные рамки. enter image description here

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

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

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

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

У вас есть только абстрактные классы, когда у вас есть методы или переменные-члены, которые являются общими. Для вашего примера для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();
}

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

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

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

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

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

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

 Carts09 июл. 2012 г., 07:58
Хорошо, я сейчас вижу. В вашем примере вы не превратили бы Animal в интерфейс, потому что мы предполагаем, что у всех животных есть конечности, а поведение для получения # of limbs одинаково для всех подклассов животных, мы можем определить реализацию метода для него. Но мы не можем предположить, что все животные издают один и тот же шум, поэтому мы оставляем его невыполненным. Тем не менее, в примере с Shape мы предполагаем, что все фигуры имеют площадь, но способ вычисления площади различен для каждой фигуры. Поскольку мы не можем предполагать что-либо еще, чем Shape может поделиться, мы оставляем это как интерфейс.

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

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

В примере:

       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.

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

Abstract Class Allows you to leverage the power of using constructors and constructor overriding Restrict the class having multiple inheritance(This is particularly useful if you are designing a complicated API) Instance variables and method implementations Leverage the power of method super calling(Use super to call the parent abstract class's implementation) Interface Enables multiple inheritance - you can implement n number of interfaces Allows to represent only conceptual methods (No method bodies)

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

Runnable Observable

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

Number Graphics

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

Ваш ответ на вопрос