Java >> Java Tutorial >  >> Java

Einzelheiten des Beispiels für das Liskov-Substitutionsprinzip

In diesem Beitrag werde ich die Details des Liskov-Substitutionsprinzips (LSP) anhand eines Beispiels behandeln. Dies ist ein Schlüsselprinzip, um das objektorientierte Design Ihres Systems zu validieren. Hoffentlich können Sie dies in Ihrem Design verwenden und herausfinden, ob es Verstöße gibt. Sie können mehr über andere objektorientierte Entwurfsprinzipien erfahren. Beginnen wir zuerst mit den Grundlagen des Liskov-Substitutionsprinzips.

Liskov-Substitutionsprinzip (LSP)

Grundsätzlich besagt das Prinzip, dass, wenn Sie in einem objektorientierten Programm die Objektreferenz der Oberklasse durch eines ihrer Unterklassenobjekte ersetzen, das Programm nicht beschädigt werden sollte.

Die Wikipedia-Definition besagt:Wenn S ein Untertyp von T ist, dann können die Objekte des Typs T durch Objekte von S ersetzt werden, ohne dass die wünschenswerten Eigenschaften des Programms geändert werden.

LSP kommt ins Spiel, wenn Sie eine Super-Sub-Klasse oder eine Schnittstellenimplementierungsart der Vererbung haben. Wenn Sie eine Superklasse oder eine Schnittstelle definieren, handelt es sich normalerweise um einen Vertrag. Jedes geerbte Objekt von dieser Oberklasse oder Schnittstellenimplementierungsklasse muss dem Vertrag folgen. Alle Objekte, die dem Vertrag nicht folgen, verletzen das Liskov-Substitutionsprinzip. Wenn Sie mehr über objektorientiertes Design erfahren möchten, besorgen Sie sich diesen Kurs von educative.

Lassen Sie uns einen einfachen Blick darauf werfen, bevor wir uns das im Detail ansehen.


public class Bird
{
    void fly()
    {
       // Fly function for bird
    }
}

public class Parrot extends Bird
{
    @Override
    void fly()
    {

    }
}

public class Ostrich extends Bird
{
   // can't implement fly since Ostrich doesn't fly
}

Wenn Sie sich die oben genannten Klassen ansehen, ist Strauß kein Vogel. Technisch gesehen können wir die fly-Methode immer noch in der Ostrich-Klasse implementieren, aber ohne Implementierung oder Auslösen einer Ausnahme. In diesem Fall verstößt Ostrich gegen LSP.

Objektorientiertes Design kann unter folgenden Umständen gegen den LSP verstoßen:

  1. Wenn eine Unterklasse ein Objekt zurückgibt, das sich vollständig von dem unterscheidet, was die Oberklasse zurückgibt.
  2. Wenn eine Unterklasse eine Ausnahme auslöst, die nicht in der Oberklasse definiert ist.
  3. Es gibt Nebenwirkungen in Unterklassenmethoden, die nicht Teil der Oberklassendefinition waren.

Wie brechen Programmierer dieses Prinzip?

Manchmal, wenn ein Programmierer eine Superklasse erweitert, ohne den Vertrag der Superklasse vollständig zu befolgen, muss ein Programmierer instanceof verwenden Suchen Sie nach der neuen Unterklasse. Wenn dem Code weitere ähnliche Unterklassen hinzugefügt werden, kann dies die Komplexität des Codes erhöhen und gegen die LSP verstoßen.

Supertype abstract beabsichtigt, Programmierern zu helfen, aber stattdessen kann es am Ende hinderlich sein und mehr Fehler in den Code einfügen. Aus diesem Grund ist es wichtig, dass Programmierer vorsichtig sind, wenn sie eine neue Unterklasse einer Oberklasse erstellen.

Beispiel für das Liskov-Substitutionsprinzip

Sehen wir uns nun ein Beispiel im Detail an.

Viele Banken bieten sowohl ein Basiskonto als auch ein Premiumkonto an. Sie erheben auch Gebühren für diese Premium-Konten, während Basiskonten kostenlos sind. Wir werden also eine abstrakte Klasse haben, um BankAccount darzustellen .

public abstract class BankAccount
{
   public boolean withDraw(double amount);

   public void deposit(double amount);

}

Die Klasse BankAccount hat zwei Methoden mit Ziehung und Einzahlung.

Lassen Sie uns daher eine Klasse erstellen, die ein Basiskonto darstellt.


public class BasicAccount extends BankAccount
{
    private double balance;

    @Override
    public boolean withDraw(double amount)
    {
       if(balance > 0)
       {
           balance -= amount;
           if(balance < 0)
           {
              return false;
           }
           else 
           {
              return true;
           }
       }
       else
       {
          return false;
       } 
    }

    @Override
    public void deposit(double amount)
    {
       balance += amount;
    }
}

Nun, ein Premium-Konto ist ein wenig anders. Natürlich kann ein Kontoinhaber weiterhin Einzahlungen oder Abhebungen von diesem Konto vornehmen. Aber mit jeder Transaktion sammelt der Kontoinhaber auch Prämienpunkte.


public class PremiumAccount extends BankAccount
{
   private double balance;
   private double rewardPoints;

   @Override
   public boolean withDraw(double amount)
   {
      if(balance > 0)
       {
           balance -= amount;
           if(balance < 0)
           {
              return false;
           }
           else 
           {
              return true;
              updateRewardsPoints();
           }
       }
       else
       {
          return false;
       } 
   }
   
   @Override
   public void deposit(double amount)
   {
      this.balance += amount;
      updateRewardsPoints();
   }

   public void updateRewardsPoints()
   {
      this.rewardsPoints++;
   }
}

So weit, ist es gut. Alles sieht in Ordnung aus. Wenn Sie dieselbe Klasse von BankAccount verwenden möchten Um ein neues Anlagekonto zu erstellen, von dem ein Kontoinhaber nicht abheben kann, sieht es wie folgt aus:


public class InvestmentAccount extends BankAccount
{
   private double balance;

   @Override
   public boolean withDraw(double amount)
   {
      throw new Expcetion("Not supported");
   }
   
   @Override
   public void deposit(double amount)
   {
      this.balance += amount;
   }

}

Obwohl diese InvestmentAccount folgt größtenteils dem Vertrag von BankAccount , es implementiert withDraw nicht -Methode und löst eine Ausnahme aus, die sich nicht in der Oberklasse befindet. Kurz gesagt, diese Unterklasse verstößt gegen das LSP.

Wie vermeide ich die Verletzung von LSP im Design?

Wie können wir also vermeiden, in unserem obigen Beispiel gegen LSP zu verstoßen? Es gibt einige Möglichkeiten, die Verletzung des Liskov-Substitutionsprinzips zu vermeiden. Erstens sollte die Oberklasse die generischsten Informationen enthalten. Wir werden einige andere objektorientierte Designprinzipien verwenden, um Designänderungen in unserem Beispiel vorzunehmen, um eine Verletzung von LSP zu vermeiden.

  • Benutzeroberfläche statt Programm verwenden.
  • Komposition über Vererbung

Jetzt können wir also BankAccount reparieren Klasse durch Erstellen einer Schnittstelle, die Klassen implementieren können.


public interface IBankAccount
{
  boolean withDraw(double amount) throws InvalidTransactionException;
  void deposit(double amount) throws InvalidTransactionException;
}

Wenn wir nun Klassen erstellen, die diese Schnittstelle implementieren, können wir auch einen InvestingAccount einfügen das wird withDraw nicht implementieren Methode.

Wenn Sie objektorientierte Programmierung verwendet haben, müssen Sie sowohl die Begriffe Komposition als auch Vererbung gehört haben. Auch im objektorientierten Design ist Komposition über Vererbung ein gängiges Muster. Man kann Objekte immer generischer machen. Die Verwendung von Komposition über Vererbung kann helfen, die Verletzung von LSP zu vermeiden.

Kombinieren Sie die Prinzipien des objektorientierten Designs mit den Grundlagen des verteilten Systemdesigns und Sie werden gut im Systemdesign sein.

Schlussfolgerung

In diesem Beitrag haben wir über das Liskov-Substitutionsprinzip und sein Beispiel gesprochen, wie Designer normalerweise gegen dieses Prinzip verstoßen und wie wir es vermeiden können, es zu verletzen.


Java-Tag