Zamieszanie interfejsów i klas abstrakcyjnych w Javie z przykładami

Mam problem ze zrozumieniem, kiedy używać interfejsu w przeciwieństwie do klasy abstrakcyjnej i odwrotnie. Poza tym jestem zdezorientowany, kiedy rozszerzyć interfejs o inny interfejs. Przepraszamy za długi post, ale jest to bardzo mylące.

Tworzenie kształtów wydaje się popularnym punktem wyjścia. Powiedzmy, że chcemy sposobu na modelowanie kształtów 2D. Wiemy, że każdy kształt będzie miał obszar. Jaka byłaby różnica między następującymi dwiema implementacjami:

z interfejsami:

public interface Shape {
    public double area();
}

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

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

z klasą abstrakcyjną:

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

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

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

Rozumiem, że klasy abstrakcyjne umożliwiają definiowanie zmiennych instancji i umożliwiają implementację metod, podczas gdy interfejs nie może tego robić. Ale w tym przypadku wydaje się, że te dwie implementacje są identyczne. Więc korzystanie z jednego jest w porządku?

Ale teraz powiedzmy, że chcemy opisać różne typy trójkątów. Możemy mieć trójkąty równoramienne, ostre i kątowe. Dla mnie sensowne jest użycie dziedziczenia klas w tym przypadku. Używając definicji „IS-A”: trójkąt prawy „IS-A”. Kształt trójkąta „IS-A”. Ponadto klasa abstrakcyjna powinna definiować zachowania i atrybuty, które są wspólne dla wszystkich podklas, więc jest to idealne:

z klasą abstrakcyjną

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

Możemy to zrobić również za pomocą interfejsów, ponieważ Trójkąt i Kształt są interfejsami. Jednak w przeciwieństwie do dziedziczenia klasowego (przy użyciu relacji „IS-A”, aby zdefiniować, co powinno być podklasą), nie jestem pewien, jak korzystać z interfejsu. Widzę dwa sposoby:

Pierwszy sposób:

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

Drugi sposób:

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

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

Wydaje mi się, że oba te sposoby działają. Ale kiedy użyłbyś jednej drogi nad drugą? Czy są jakieś zalety korzystania z interfejsów nad abstrakcyjnymi klasami w celu reprezentowania różnych trójkątów? Mimo że skomplikowaliśmy opis kształtu, użycie interfejsu vs klasa abstrakcyjna wciąż wydaje się być równoważne.

Krytycznym komponentem interfejsów jest to, że może definiować zachowania, które mogą być współużytkowane przez niepowiązane klasy. Tak więc interfejs Flyable byłby obecny w klasach Samolot i Ptak. W tym przypadku jasne jest, że preferowane jest podejście interfejsowe.

Ponadto, aby rozwinąć mylący interfejs rozszerzający inny interfejs: Kiedy relacja „IS-A” powinna być ignorowana przy podejmowaniu decyzji o tym, co powinno być interfejsem? Weź ten przykład:POŁĄCZYĆ.

Dlaczego „VeryBadVampire” ma być klasą, a „Wampir” interfejsem? „VeryBadVampire” IS-A „Vampire”, więc rozumiem, że „Vampire” powinien być superklasą (może klasą abstrakcyjną). Klasa „Wampir” może zaimplementować „Zabójczą”, aby zachować śmiertelne zachowanie. Ponadto „Vampire” IS-A „Monster”, więc „Monster” powinien być również klasą. Klasa „Wampir” może również implementować interfejs o nazwie „Niebezpieczny”, aby zachować jego niebezpieczne zachowanie. Jeśli chcemy stworzyć nowego potwora o nazwie „BigRat”, który jest niebezpieczny, ale nie zabójczy, możemy stworzyć klasę „BigRat”, która rozszerza „Monster” i implementuje „Niebezpieczne”.

Czy powyższe nie osiągnęłoby takiej samej wydajności jak użycie „Wampira” jako interfejsu (opisanego w linku)? Jedyną różnicą, jaką widzę, jest to, że używanie dziedziczenia klasowego i zachowanie związku „IS-A” oczyszcza wiele zamieszania. Jednak nie jest to przestrzegane. Jaka jest zaleta takiego działania?

Nawet jeśli chcesz, aby potwór dzielił zachowanie wampiryczne, zawsze możesz przedefiniować sposób reprezentacji obiektów. Jeśli chcemy nowego typu potwora o wampirze zwanego „VeryMildVampire” i chcieliśmy stworzyć potwora wampira o nazwie „Chupacabra”, możemy to zrobić:

Klasa „Vampire” rozszerza implementację „Monster” „Dangerous”, „Lethal”, „BloodSuckable”
Klasa „VeryMildVampire” rozszerza klasę „Vampire”
Klasa „Chupacabra” rozszerza „Monster” implementuje „BloodSuckable”

Ale możemy również to zrobić:

„VeryMildVampire” rozszerza narzędzia „Monster” Dangerous, Lethal, Vampiric
„Chupacabra” rozszerza narzędzia „Monster” Dangerous, Vampiric

Drugi sposób tworzy interfejs „Vampiric”, abyśmy mogli łatwiej zdefiniować pokrewnego potwora, zamiast tworzyć kilka interfejsów definiujących zachowania wampiryczne (jak w pierwszym przykładzie). Ale to zrywa relację IS-A. Więc jestem zdezorientowany ...

questionAnswers(7)

yourAnswerToTheQuestion