SOLID
SOLID on 5 objekt-orienteeritud programmeerimise printsiipi, mis aitavad kirjutada koodi, mis on kergemini hallatav ja laiendatav. Iga täht selles lühendis tähistab ühte printsiipi:
S The Single-Responsibility Principle
O The Open/Closed Principle
L The Liskov Substitution Principle
I The Interface-Segregation Principle
D The Dependency-Inversion Principle
The Single-Responsibility Principle
A class should have only one reason to change.
Ühel klassil peaks olema üks konkreetne ülesanne.
// Book does only book tasks
public class Book {
private String text = "123 test";
public void replaceWordInText(String from, String to) {
text = text.replaceAll(from, to);
}
public boolean isWordInText(String word) {
return text.contains(word);
}
public static void main(String[] args) {
Book book = new Book();
Printer printer = new Printer();
book.replaceWordInText("test", "456");
book.isWordInText("test");
printer.printTextToConsole(book.text);
printer.printTextToAnotherMedium(book.text);
}
}
// Printer does only printing
class Printer {
void printTextToConsole(String text) {
System.out.println(text);
}
void printTextToAnotherMedium(String text) { }
}
Antud lähenemine teeb koodi loetavuse paremaks, kuna väiksematest hästi organiseeritud klassidest on kergem asju üles leida. Lisaks kuna klass keskendub ainult kindlatele asjadele, on selle testimine ka kergem, kuna peab vähem teste kirjutama.
The Open/Closed Principle
Entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
Klassid tuleb struktureerida selliselt, et neid saab uue funktsionaalsuse vajadusel mugavalt laiendada (open for extensions). Samas seda osa koodist, mis on juba kirjutatud, ei tohiks kunagi muuta (välja arvatud vigade parandamiseks) (closed for modifications). Erandiks on muidugi olemasolevate vigade parandamine.
// Old functionality not changed
public class OldCalculator {
int calculateFee(int fee) {
return fee * 5;
}
}
// New functionality added by extending old
class NewCalculator extends OldCalculator {
@Override
int calculateFee(int fee) {
return fee * 10;
}
}
Antud lähenemise eelis seisneb selles, et tulevikus on kergem uut funktsionaalsust lisada, samal ajal vanat funktsioneerivat koodi katki tegemata.
The Liskov Substitution Principle
Child classes should never break the parent class type definitions.
Alamklassid tuleb kirjeldada nii, et objekti kasutaja jaoks ei ole vahet, kui kasutusse antakse alamklassi tüüpi objekt. Kui alamklass teeb midagi sellist, mida ülemklassist ei eeldaks, siis see on selle printsiibi rikkumine.
// Solution is to redesign the model - Circle does not have width and height specified
public interface Shape {
void setWidth(int value);
void setHeight(int value);
}
class Rectangle implements Shape {
private int width;
private int height;
@Override
public void setWidth(int value) {
this.width = value;
}
@Override
public void setHeight(int value) {
this.height = value;
}
}
class Circle implements Shape {
private int radius;
@Override
public void setWidth(int value) { }
@Override
public void setHeight(int value) { }
}
The Interface-Segregation Principle
No client should be forced to depend on methods it does not use.
Liidese kasutaja ei pea sõltuma meetoditest, mida otseselt vaja ei lähe. Liidesed, kus on palju meetodeid, jagatakse väiksemateks.
// Bad - inflexible large interface, must implement all
interface BearKeeper {
void washTheBear();
void feedTheBear();
void petTheBear();
}
public class BearCarer implements BearKeeper {
@Override
public void washTheBear() { }
@Override
public void feedTheBear() { }
@Override
public void petTheBear() { }
}
// Good - flexible smaller interfaces, implement what you need
public interface BearCleaner {
void washTheBear();
}
interface BearFeeder {
void feedTheBear();
}
interface BearPetter {
void petTheBear();
}
class BearCarer implements BearCleaner, BearFeeder {
@Override
public void washTheBear() { }
@Override
public void feedTheBear() { }
}
The Dependency-Inversion Principle
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.
Tarkvarakomponendid peaksid sõltuma abstraktsioonidest, mitte konkreetsetest klassidest. Tulemusena on kood paindlikum (ei ole piiratud kasutama kindlat implementatsiooni) ning kergemini testitav.
// Bad - can only use concrete StandardMonitor and StandardKeyboard with Computer
class StandardKeyboard {}
class StandardMonitor {}
public class Computer {
private StandardKeyboard keyboard;
private StandardMonitor monitor;
public Computer() {
monitor = new StandardMonitor();
keyboard = new StandardKeyboard();
}
}
// Good - can use any keyboard and monitor with Computer
interface Keyboard { }
interface Monitor { }
class StandardKeyboard implements Keyboard { }
class StandardMonitor implements Monitor { }
public class Computer {
private final Keyboard keyboard;
private final Monitor monitor;
public Computer(Keyboard keyboard, Monitor monitor) {
this.keyboard = keyboard;
this.monitor = monitor;
}
}
Viiteid
https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
https://www.baeldung.com/solid-principles
Koodinäiteid: