Definicja
Zasada OCP (Open/Closed Principle) mówi, że klasy powinny być otwarte na rozszerzanie, ale zamknięte na modyfikacje. Oznacza to, że podczas projektowania klas należy zadbać o możliwość ich rozbudowy w przyszłości bez konieczności ingerowania w ich istniejący kod.
Wprowadzanie zmian do już istniejących klas wiąże się z ryzykiem – nie zawsze wiadomo, jak takie zmiany wpłyną na pozostałą część systemu. Dlatego zamiast modyfikować istniejące klasy, lepiej jest je rozszerzać.
W praktyce zasadę OCP można realizować za pomocą:
- dziedziczenia (tworząc podklasy z nowym zachowaniem),
- interfejsów (dzięki którym różne implementacje mogą być traktowane jednakowo),
- wzorów projektowych
Dzięki temu kod staje się bardziej elastyczny, łatwiejszy w utrzymaniu i odporny na błędy wynikające z nieprzemywanych modyfikacji.
Przykład programu przed wdrożeniem OCP
public class Ceo {
public void CalculateSalary() {
System.out.println("Pay Ceo");
}
public void ShowIdCard() {
System.out.println("Greet Ceo");
}
}
public class Programmer {
public void CalculateSalary() {
System.out.println("Pay Programmer");
}
public void ShowIdCard() {
System.out.println("Greet Programmer");
}
}
public class Finances {
public void CalculateSalaries(Object[] obiekty) {
for (int i = 0; i < obiekty.length; i++) {
if (obiekty[i] instanceof Ceo) {
Ceo ceo = (Ceo) obiekty[i];
ceo.CalculateSalary();
}
if (obiekty[i] instanceof Programmer) {
Programmer programmer = (Programmer) obiekty[i];
programmer.CalculateSalary();
}
}
}
}
public class Hr {
public void ShowIdCards(Object[] obiekty) {
for (int i = 0; i < obiekty.length; i++) {
if (obiekty[i] instanceof Ceo) {
Ceo ceo = (Ceo) obiekty[i];
ceo.ShowIdCard();
}
if (obiekty[i] instanceof Programmer) {
Programmer programmer = (Programmer) obiekty[i];
programmer.ShowIdCard();
}
}
}
}
public class Program {
public static void main(String[] args) {
Finances finances = new Finances();
Hr hr = new Hr();
Object[] obiekty = new Object[] {new Ceo(), new Programmer()};
finances.CalculateSalaries(obiekty);
hr.ShowIdCards(obiekty);
}
}
Gdy do aplikacji będzie trzeba w przyszłości dodać np. Księgowego utworzone w ten sposób klasy CalculateSalaries, ShowIdCard będzie trzeba zmodyfikować. Lepszym rozwiązaniem zgodnym z zasadą OCP jest zastosowanie np. implementacji z interfejsu. Rozwiązanie to pozwala na dodawanie kolejnych stanowisk pracy, gdy będzie taka potrzeba.
Kod po zastosowaniu OCP
public interface IEmployee {
void CalculateSalary();
void ShowIdCard();
}
public class Ceo implements IEmployee {
@Override
public void CalculateSalary() {
System.out.println("Pay Ceo");
}
@Override
public void ShowIdCard() {
System.out.println("Greet Ceo");
}
}
public class Programmer implements IEmployee {
@Override
public void CalculateSalary() {
System.out.println("Pay Programmer");
}
@Override
public void ShowIdCard() {
System.out.println("Greet Programmer");
}
}
public class Accountant implements IEmployee {
@Override
public void CalculateSalary() {
System.out.println("Pay Accountant");
}
@Override
public void ShowIdCard() {
System.out.println("Greet Accountant");
}
}
public class Finances {
public void CalculateSalaries(IEmployee[] employees) {
for (IEmployee employee : employees) {
employee.CalculateSalary();
}
}
}
public class Hr {
public void ShowIdCards(IEmployee[] employees) {
for(IEmployee employee : employees) {
employee.ShowIdCard();
}
}
}
public class Program {
public static void main(String[] args) {
Finances finances = new Finances();
Hr hr = new Hr();
IEmployee[] employees = new IEmployee[] {new Ceo(), new Programmer(),
new Accountant()};
finances.CalculateSalaries(employees);
hr.ShowIdCards(employees);
}
}
Podsumowanie
Zastosowanie zasady OCP sprawia, że kod jest bardziej odporny na błędy, łatwiej się rozwija i nie wymaga modyfikacji już przetestowanych klas. To podstawa skalowalnych i solidnych aplikacji.
