(Zasada otwarte zamknięte)
Jednostki oprogramowania (klasy, moduły, funkcje itp.) powinny być otwarte na rozszerzanie, ale zamknięte na modyfikacje.
Meyer Bertrand
rozszerzenia vs modyfikacje
Jeśli użytkownik poprosi o wprowadzenie zmian w aplikacji, programista nie powinien modyfikować istniejących już klas, tylko powinien rozszerzyć istniejącą aplikację o nową klasę.
Więc jeśli chcemy zmienić aplikację, powinniśmy mieć możliwość zrobienia tego tylko przez dodanie nowych klas i funkcjonalności. Nie powinniśmy modyfikować istniejącego już kodu.
</> Lepszy przykład niż wykład
✘ Błędny kod
Klasa Cat zajmuje się wydawaniem dźwięku kota natomiast klasa Dog wydawaniem dźwięku psa
public class Cat {
public void makeSound() {
System.out.println("Wydaję dźwięk kota.");
}
}
public class Dog {
public void makeSound() {
System.out.println("Wydaję dźwięk psa.");
}
}
Klasa Sound decyduje dźwięk którego zwierzęcia ma zostać wydany.
Taki kod narusza zasadę OCP, ponieważ gdybyśmy chcieli poszerzyć działanie programu wydawanie dźwięków innych zwierząt (koń, owca itd.), musielibyśmy zmodyfikować klasę Sound dopisując kolejne warunki dla kolejnych zwierząt.
public class Sound {
public static void makeSound(Object animal) {
if (animal instanceof Cat) {
((Cat) animal).makeSound();
} else if (animal instanceof Dog) {
((Dog) animal).makeSound();
} // else if (animal instanceof Horse) {...}; e.t.c
}
}
public class Main {
public static void main(String[] args) {
Sound sound = new Sound();
Object animal = new Cat();
Object animal1 = new Dog();
sound.makeSound(animal);
//sound.makeSound(animal1);
}
✔ Prawidłowy kod
Naruszenia zasad OCP możemy uniknąć, wykorzystując polimorfizm.
polimorfizm
(wielopostaciowość)
Pod obiekt danej klasy możemy przypisywać obiekty klas dziedziczących i podczas wywoływania tych samych metod ich zachowanie będzie inne (dostosowane do klasy dziedziczącej).
Czyli możemy utworzyć:
- klasę abstrakcyjną (klasa abstrakcyjna nie może być inicjalizowana, nie możemy tworzyć obiektu tej klasy. Inne klasy mogą dziedziczyć po niej i wykonywać gotowe metody oraz muszą nadpisywać jej metody abstrakcyjne)
- lub interfejs (jest kompletną klasą abstrakcyjną – wszystkie metody w nim są abstrakcyjne i publiczne)
Następnie do obiektu tej klasy abstrakcyjnej lub interfejsu przypiszemy obiekt klasy dziedziczącej, a metoda wywołana na takim obiekcie wykona się zgodnie z implementacją klasy dziedziczącej.
Tworzymy interfejs Animal z metodą wydającą dźwięk zwierzęcia.
public interface Animal {
void makeSound();
}
W klasach Cat i Dog wydających dźwięki konkretnych zwierząt implementujemy metodę interfejsu
public class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("Wydaję dźwięk kota korzystając z polimorfizmu.");
}
}
public class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Wydaję dźwięk psa korzystając z polimorfizmu.");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Cat();
Animal animal1 = new Dog();
Sound sound = new Sound();
sound.makeSound(animal);
//sound.makeSound(animal1);
}
}
Następnie w naszej klasie Sound zostanie wydany dźwięk zwierzęcia przekazanego do metody makeSound(Animal animal).
public class Sound {
public void makeSound(Animal animal) {
animal.makeSound();
}
}
Ewentualne tworzenie nowych metod wydających dźwięki kolejnych zwierząt, zrealizujemy poprzez rozszerzenie naszego programu o kolejne klasy (Horse, Sheep itd.) implementujące interfejs Animal bez modyfikowania klasy Sound.
