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) { }
}

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).

// 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;
  }
}

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.

// 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;
    }
}