Listkov Substitution Principle
Ta zasada mówi, że w miejscu klasy bazowej można użyć dowolnej klasy pochodnej (zgodność wszystkich metod). Zasada ta jest niejako rozszerzeniem zasady Open/Close Principle pod względem zachowania.
Przykład złamania tej zasady:
public interface Character {
void go();
void run();
void fight();
}
public class Battlemage implements Character {
@Override
public void go() {
System.out.println("Idę w swoim lekkim pancerzu!");
}
@Override
public void run() {
System.out.println("Biegnę w swoim lekkim pancerzu!");
}
@Override
public void fight() {
System.out.println("Wyjmuję swój kostur i rzucam czary!");
}
}
public class Healer implements Character {
@Override
public void go() {
System.out.println("Idę w swojej szacie!");
}
@Override
public void run() {
System.out.println("Biegnę w swojej szacie!");
}
@Override
public void fight() {
throw new IllegalArgumentException();
}
}
public class Knight implements Character {
@Override
public void go() {
System.out.println("Idę w swoim ciężkim pancerzu!");
}
@Override
public void run() {
System.out.println("Biegnę w swoim ciężkim pancerzu!");
}
@Override
public void fight() {
System.out.println("Wyjmuje swój miecz i walczę!");
}
}
public class Thief implements Character {
@Override
public void go() {
System.out.println("Idę w swoim średnim pancerzu!");
}
@Override
public void run() {
System.out.println("Biegnę w swoim średnim pancerzu!");
}
@Override
public void fight() {
System.out.println("Rzucam swoimi zatrutymi sztyletami!");
}
}
public class ShowBattleInfo {
public void showInfo(Character[] characters){
for (Character character : characters) {
character.go();
character.run();
character.fight();
}
}
}
Mamy tu interface Character z trzema metodami go, run oraz fight, a także klasy które po niej dziedziczą. Wszystkie klasy oprócz klasy Healer walczą, bo zakładam że heal faktycznie będzie tylko leczył. Skoro jednak klasa Healer implementuje interface Character to w metodzie fight musimy rzucić odpowiednim wyjątkiem – co łamie zasadę.
Przykład zastosowania zasady:
W zasadzie jedną ze zmian jest rozłączenie interface Charakter na dwa: FightCharacter oraz MovingCharacter. Drugą zmianą jest implementacja odpowiednich interfejsów do odpowiednich klas. Zatem nasza klasa Healer będzie implementowała tylko interface MovingCharacter a reszta klas oba. Mamy również klasy ShowBattleInfo i ShowMoveInfo, które posłużą nam do wyświetlenia informacji. Pokaże kod interfejsów oraz klasy BattleMage oraz Heal
public interface FightCharacter {
void fight();
}
public interface MovingCharacter {
void go();
void run();
}
public class Battlemage implements FightCharacter, MovingCharacter{
@Override
public void fight() {
System.out.println("Wyjmuję swój kostur i rzucam czary!");
}
@Override
public void go() {
System.out.println("Idę w swoim lekkim pancerzu!");
}
@Override
public void run() {
System.out.println("Biegnę w swoim lekkim pancerzu!");
}
}
public class Healer implements MovingCharacter{
@Override
public void go() {
System.out.println("Idę w swojej szacie!");
}
@Override
public void run() {
System.out.println("Biegnę w swojej szacie!");
}
}
Zobaczmy jak wygląda main:
public class App {
public static void main(String[] args){
Knight knight = new Knight();
Thief thief = new Thief();
Battlemage battlemage = new Battlemage();
Healer healer = new Healer();
ShowMoveInfo showMoveInfo = new ShowMoveInfo();
ShowBattleInfo showBattleInfo = new ShowBattleInfo();
FightCharacter[] fightCharacter = {knight,thief,battlemage};
MovingCharacter[] movingCharacter = {knight,thief,battlemage,healer};
showMoveInfo.getMoveInfo(movingCharacter);
System.out.println();
showBattleInfo.getBattleInfo(fightCharacter);
}
}