O - Open/Closed Principle
Sissejuhatus
A class should be open for extension, but closed for modification.
Klass peab olema avatud laiendustele, kuid suletud muudatustele. See on Open/Closed Principle (OCP) ehk avatud/suletud printsiibi põhimõte.
"Avatud laiendustele" tähendab, et klassile on võimalik uut käitumist lisada. "Suletud muudatustele" tähendab, et olemasolevat koodi ei tohiks muuta, et uut funktsionaalsust toetada.
Esialgselt see võib tunduda, et need laused räägivad üksteisele vastu. Tekib küsimus, et kuidas on võimalik lisada uut funktsionaalsust ilma vanat koodi muutmata? Vastus peitub polümorfismis ja liidestes.
Laiendamine muudatuste kaudu
Vaatleme veebipoe allahindlussüsteemi. Kliendid jagunevad erinevatesse kategooriatesse: tavakliendid, püsikliendid ja töötajad. Igale kategooriale rakendub erinev allahindlus:
class DiscountCalculator {
public double calculate(double price, String customerType) {
if (customerType.equals("regular")) {
return price;
} else if (customerType.equals("premium")) {
return price * 0.9; // 10% off
} else if (customerType.equals("employee")) {
return price * 0.7; // 30% off
}
return price;
}
}
Selline lähenemine toimib seni, kuni lisatakse uus klienditüüp - näiteks "student", millele kehtib 15%-ine allahindlus.
Selle lisamiseks tuleb avada DiscountCalculator klass, leida üles vastav meetod, sealt if/else harude ahel ja lisada sinna uus tingimus:
} else if (customerType.equals("student")) {
return price * 0.85;
}
Iga muudatus peab käima läbi calculate() meetodi.
Mida rohkem klienditüüpe lisandub, seda pikemaks ja hapramaks see meetod muutub.
Sagedased instanceof kontrollid ning pikad if/else või switch ahelad tüüpide nimede põhjal on tavaliselt märk sellest, et rikutakse avatud-suletud printsiipi.
Nagu mainitud polümorfismi peatükis, viitab tüübikontrollide tihe kasutamine sageli sellele, et käitumine peaks kuuluma tüübihierarhiasse endasse.
OCP rakendamine
Antud probleemi lahendaks allahindluse strateegia abstraheerimine:
interface DiscountPolicy {
double apply(double price);
}
Igale klienditüübile rakendatav allahindlus muutuks omaette klassiks, mis seda liidest teostab:
class RegularDiscount implements DiscountPolicy {
@Override
public double apply(double price) {
return price;
}
}
class PremiumDiscount implements DiscountPolicy {
@Override
public double apply(double price) {
return price * 0.9;
}
}
class EmployeeDiscount implements DiscountPolicy {
@Override
public double apply(double price) {
return price * 0.7;
}
}
DiscountCalculator sõltub nüüd ainult abstraktsioonist:
class DiscountCalculator {
public double calculate(double price, DiscountPolicy policy) {
return policy.apply(price);
}
}
Uue allahindlustüübi lisamiseks ei pea DiscountCalculator klassi muutma, vaja lihtsalt uus klass tekidada, mis laiendab DiscountPolicy liidest.
class StudentDiscount implements DiscountPolicy {
@Override
public double apply(double price) {
return price * 0.85;
}
}
Kasutamine
DiscountCalculator calculator = new DiscountCalculator();
System.out.println(calculator.calculate(100.0, new RegularDiscount())); // 100.0
System.out.println(calculator.calculate(100.0, new PremiumDiscount())); // 90.0
System.out.println(calculator.calculate(100.0, new StudentDiscount())); // 85.0
Meetodi välja kutsuja otsustab, millist implementatsiooni kasutada.
DiscountCalculator ei pea täpset implementatsiooni teadma, vaja on lihtsalt teada, et on võimalik mingisugust allahindlust rakendada.
OCP ei tähenda, et koodi ei muudeta kunagi. Veaparandused, turvalisusest tulenevad uuendused ja rakenduse disaini täiustused võivad kõik nõuda olemasoleva koodi muutmist. See printsiip keskendub uue funktsionaalsuse lisamisele. Kui võimalik, peaks vältima olemasoleva koodi muutmist selle saavutamiseks.