Java >> Java tutorial >  >> Java

Nærmere oplysninger om Liskov Substitution Princip Eksempel

I dette indlæg vil jeg dække detaljerne i Liskov Substitution Principle (LSP) med et eksempel. Dette er et nøgleprincip for at validere det objektorienterede design af dit system. Forhåbentlig vil du være i stand til at bruge dette i dit design og finde ud af, om der er nogen overtrædelser. Du kan lære mere om andre objektorienterede designprincipper. Lad os starte med det grundlæggende i Liskov Substitutionsprincippet først.

Liskov Substitutionsprincip (LSP)

Grundlæggende siger princippet, at hvis du i et objektorienteret program erstatter superklasseobjektreference med nogen af ​​dets underklasseobjekter, bør det ikke bryde programmet.

Wikipedias definition siger – Hvis S er en undertype af T, så kan objekter af type T erstattes med objekter af S uden at ændre nogen af ​​programmets ønskelige egenskaber.

LSP kommer i spil, når du har en super-sub klasse ELLER interface implementering type arv. Normalt, når du definerer en superklasse eller en grænseflade, er det en kontrakt. Ethvert nedarvet objekt fra denne superklasse eller grænsefladeimplementeringsklasse skal følge kontrakten. Enhver af de genstande, der ikke følger kontrakten, vil overtræde Liskov Substitutionsprincippet. Hvis du vil lære mere om objektorienteret design, så få dette kursus fra educative.

Lad os tage et enkelt kig, før vi ser nærmere på dette.


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
}

Hvis man ser på ovenstående klasser, er struds ikke en fugl. Teknisk set kan vi stadig implementere fluemetoden i strudsklassen, men det vil være uden implementering eller uden undtagelse. I dette tilfælde overtræder struds LSP.

Objektorienteret design kan krænke LSP under følgende omstændigheder:

  1. Hvis en underklasse returnerer et objekt, der er helt anderledes end det, superklassen returnerer.
  2. Hvis en underklasse kaster en undtagelse, der ikke er defineret i superklassen.
  3. Der er nogen bivirkninger i underklassemetoder, som ikke var en del af superklassedefinitionen.

Hvordan bryder programmører dette princip?

Nogle gange, hvis en programmør ender med at forlænge en superklasse uden helt at følge superklassens kontrakt, bliver en programmør nødt til at bruge instanceof tjek efter den nye underklasse. Hvis der er føjet flere lignende underklasser til koden, kan det øge kompleksiteten af ​​koden og overtræde LSP.

Supertype abstract har til hensigt at hjælpe programmører, men i stedet kan det ende med at hindre og tilføje flere fejl i koden. Derfor er det vigtigt for programmører at være forsigtige, når de opretter en ny underklasse til en superklasse.

Liskov substitutionsprincipeksempel

Lad os nu se på et eksempel i detaljer.

Mange banker tilbyder en basiskonto såvel som en premiumkonto. De opkræver også gebyrer for disse premiumkonti, mens grundlæggende konti er gratis. Så vi vil have en abstrakt klasse til at repræsentere BankAccount .

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

   public void deposit(double amount);

}

Klassen BankAccount har to metoder Udtræk og indbetaling.

Lad os derfor oprette en klasse, der repræsenterer en grundlæggende konto.


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

Nu er en premium-konto lidt anderledes. En kontohaver vil naturligvis stadig være i stand til at indbetale eller hæve fra denne konto. Men med hver transaktion optjener kontoindehaveren også belønningspoint.


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

Så langt så godt. Alt ser ok ud. Hvis du vil bruge den samme klasse BankAccount for at oprette en ny investeringskonto, som en kontohaver ikke kan hæve fra, ser det ud som nedenfor:


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

}

Selvom denne InvestmentAccount følger det meste af kontrakten af ​​BankAccount , implementerer den ikke withDraw metode og kaster en undtagelse, der ikke er i superklassen. Kort sagt overtræder denne underklasse LSP.

Hvordan undgår man at overtræde LSP i designet?

Så hvordan kan vi undgå at krænke LSP i vores ovenstående eksempel? Der er et par måder, hvorpå du kan undgå at overtræde Liskov Substitutionsprincippet. For det første skal superklassen indeholde de mest generiske oplysninger. Vi vil bruge nogle andre objektorienterede designprincipper til at lave designændringer i vores eksempel for at undgå at overtræde LSP.

  • Brug grænseflade i stedet for program.
  • Komposition over arv

Så nu kan vi rette BankAccount klasse ved at skabe en grænseflade, som klasser kan implementere.


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

Hvis vi nu opretter klasser, der implementerer denne grænseflade, kan vi også inkludere en InvestingAccount der ikke implementerer withDraw metode.

Hvis du har brugt Objektorienteret programmering, skal du have hørt både begreberne sammensætning og arv. Også i objektorienteret design er komposition frem for arv et almindeligt mønster. Man kan altid gøre objekter mere generiske. Brug af sammensætning frem for arv kan hjælpe med at undgå at overtræde LSP.

Kombiner objektorienterede designprincipper med det grundlæggende i distribueret systemdesign, og du vil være god til systemdesign.

Konklusion

I dette indlæg talte vi om Liskov Substitution Principle og dets eksempel, hvordan designere normalt overtræder dette princip, og hvordan vi kan undgå at overtræde det.


Java tag