Məzmuna keç
Educora
İrəli22 dəq18 / 18

Təmiz kod, SOLID və dizayn nümunələri

Oxunaqlı kod yazmağın qaydalarını, SOLID prinsiplərini və beş əsas dizayn nümunəsini — Singleton, Factory, Builder, Strategy, Observer — qısa Java nümunələri ilə öyrən.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Təmiz kodun qaydaları ilə mövcud metodu refaktorinq etmək
  • SOLID-in beş prinsipini nümunələrlə izah etmək
  • Singleton, Factory, Builder, Strategy və Observer nümunələrini tanımaq və yazmaq

Proqram bir dəfə yazılır, amma onlarla dəfə oxunur — komanda yoldaşları, ya da altı aydan sonra sən özün tərəfindən. İşləyən kod hələ yaxşı kod demək deyil: yaxşı kodu asan oxumaq, dəyişmək və test etmək olur. Bu dərsdə peşəkar Java proqramçılarının gündəlik qaydalarını — təmiz kod prinsiplərini, SOLID-i və ən çox işlənən beş dizayn nümunəsini öyrənəcəksən.

Təmiz kodun qaydaları

  • Mənalı adlar: c, r, calc yox, passedCount, average, averageOfPassed. Ad nə etdiyini deməlidir.
  • Kiçik metodlar: bir metod — bir iş. Metod ekrana sığmırsa, onu hissələrə böl.
  • Sehrli ədədlər yox: 50 və 1.1 əvəzinə PASS_MARK və BONUS_FACTOR sabitləri.
  • Dəyişməzlik: mümkün olan yerdə final sahələr və record-lar; dəyişməyən obyekt thread-lər arasında da təhlükəsizdir.
  • **null əvəzinə Optional**: nəticə olmaya bilərsə, metod Optional<Student> qaytarsın ki, çağıran bunu unutmasın.
  • İstisnaları «udma»: boş catch {} səhvi gizlədir; ya onu emal et, ya da yuxarı ötür.
İşləyir, amma oxunmur
public double calc(List<Integer> l, int t) {
    double r = 0;
    int c = 0;
    for (int i = 0; i < l.size(); i++) {
        if (l.get(i) != null) {
            if (l.get(i) >= 50) { r = r + l.get(i); c++; }
        }
    }
    if (t == 1) return c == 0 ? 0 : r / c * 1.1;
    return c == 0 ? 0 : r / c;
}
Eyni nəticə, aydın niyyət
private static final int PASS_MARK = 50;
private static final double BONUS_FACTOR = 1.1;

public double averageOfPassed(List<Integer> scores, boolean withBonus) {
    double average = scores.stream()
            .filter(Objects::nonNull)
            .filter(score -> score >= PASS_MARK)
            .mapToInt(Integer::intValue)
            .average()
            .orElse(0);
    return withBonus ? average * BONUS_FACTOR : average;
}
Hər iki metod eyni ədədləri qaytarır (yoxlanılıb), amma ikincisini izahsız başa düşmək olur

Birinci versiyada t == 1 nə deməkdir? 1.1 haradan gəlib? Oxucu hər sətri beynində icra etməli olur. İkinci versiyada ad, sabitlər və axın hər şeyi deyir, boolean withBonus isə sirli int t-ni əvəz edib. Diqqət et: məntiqi dəyişməmişik — bu, refaktorinqdir, əvvəlcədən yazılmış testlər isə (JUnit dərsi!) davranışın qorunduğuna zəmanət verir.

SOLID prinsipləri

SOLID — Robert Martinin məşhurlaşdırdığı beş obyektyönlü dizayn prinsipinin baş hərfləridir. Onlar kodu dəyişikliklərə davamlı edir: yeni tələb gələndə on yeri deyil, bir yeri dəyişirsən.

HərfPrinsipQısaca
SSingle ResponsibilitySinfin dəyişmək üçün yalnız bir səbəbi olmalıdır: kontroller HTTP ilə, servis qaydalarla, repozitori baza ilə məşğuldur.
OOpen/ClosedKod genişlənmə üçün açıq, dəyişiklik üçün qapalıdır: yeni qayda köhnə kodu redaktə etmədən yeni sinif kimi əlavə olunur.
LLiskov SubstitutionTörəmə sinif valideyn sinfin gözlənildiyi hər yerdə işləməli və gözləntiləri pozmamalıdır.
IInterface SegregationBir böyük interfeys əvəzinə bir neçə kiçik interfeys: sinif istifadə etmədiyi metodları reallaşdırmağa məcbur olmamalıdır.
DDependency InversionKonkret sinifdən yox, abstraksiyadan (interfeysdən) asılı ol — JUnit dərsində ReportService ScoreRepository interfeysindən asılı idi.

Dizayn nümunələri

Dizayn nümunəsi (design pattern) tez-tez rastlaşan problemin sınaqdan çıxmış həll sxemidir. 1994-cü ildə «Dördlər dəstəsi» (Gang of Four: Gamma, Helm, Johnson, Vlissides) 23 nümunəni bir kitabda topladı və onları üç qrupa böldü: yaradıcı (obyektlər necə yaradılır), struktur (obyektlər necə birləşdirilir) və davranış (obyektlər necə əməkdaşlıq edir). Aşağıda Java-da ən çox görəcəyin beş nümunə var.

Java
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        AppConfig.INSTANCE.set("school", "School No. 6, Baku");
        System.out.println(AppConfig.INSTANCE.get("school"));
        System.out.println(AppConfig.INSTANCE == AppConfig.valueOf("INSTANCE"));

        for (String type : List.of("email", "sms")) {
            Notifier notifier = Notifier.of(type);
            notifier.send("Aysel", "Your exam score is 95");
        }
    }
}

enum AppConfig {                                   // Singleton: exactly one instance
    INSTANCE;
    private final Map<String, String> values = new HashMap<>();
    void set(String key, String value) { values.put(key, value); }
    String get(String key) { return values.get(key); }
}

interface Notifier {
    void send(String to, String text);

    static Notifier of(String type) {              // Factory: hides which class is created
        return switch (type) {
            case "email" -> (to, text) -> System.out.println("Email to " + to + ": " + text);
            case "sms" -> (to, text) -> System.out.println("SMS to " + to + ": " + text);
            default -> throw new IllegalArgumentException("Unknown type: " + type);
        };
    }
}
Gözlənilən nəticə
School No. 6, Baku
true
Email to Aysel: Your exam score is 95
SMS to Aysel: Your exam score is 95
Singleton (enum ilə) və Factory (statik fabrik metodu)

Singleton sinifdən yalnız bir obyekt olmasını təmin edir. Java-da ən sadə və thread-lər üçün təhlükəsiz yol tək elementli enum-dur; Spring bean-ləri də susmaya görə singleton-dur. Factory isə obyekti new ilə yox, metodla yaradır və hansı sinfin yarandığını gizlədir: çağıran yalnız Notifier interfeysini tanıyır. JDK-da List.of(...), Integer.valueOf(...) və Path.of(...) fabrik metodlarıdır.

Java
public class Main {
    public static void main(String[] args) {
        Report full = new Report.Builder("Aysel")
                .subject("Math")
                .score(95)
                .comment("Excellent work")
                .build();
        Report minimal = new Report.Builder("Murad").build();
        System.out.println(full);
        System.out.println(minimal);
    }
}

record Report(String student, String subject, int score, String comment) {
    static class Builder {                          // Builder: readable step-by-step creation
        private final String student;
        private String subject = "General";
        private int score;
        private String comment = "";

        Builder(String student) { this.student = student; }
        Builder subject(String value) { subject = value; return this; }
        Builder score(int value) { score = value; return this; }
        Builder comment(String value) { comment = value; return this; }
        Report build() { return new Report(student, subject, score, comment); }
    }
}
Gözlənilən nəticə
Report[student=Aysel, subject=Math, score=95, comment=Excellent work]
Report[student=Murad, subject=General, score=0, comment=]
Builder: çoxlu, bəziləri məcburi olmayan parametrlər üçün

Konstruktorun dörd-beş parametri olanda new Report("Aysel", "Math", 95, "Excellent") çağırışında hansı qiymətin nə olduğunu anlamaq çətindir, məcburi olmayan parametrlər üçün isə çoxlu konstruktor lazım gəlir. Builder obyekti addım-addım, adlı metodlarla qurur və sonda build() hazır, dəyişməz obyekt qaytarır. Tanış nümunələr: StringBuilder, HttpClient.newBuilder(), Stream.builder().

Java
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        GradingRule strict = raw -> raw;                          // Strategy 1
        GradingRule withBonus = raw -> Math.min(100, raw + 5);    // Strategy 2

        GradeBoard board = new GradeBoard(withBonus);
        board.subscribe((name, score) -> System.out.println("[parent app] " + name + " got " + score));
        board.subscribe((name, score) -> System.out.println("[teacher log] " + name + ": " + score));

        board.addScore("Leyla", 88);
        board.setRule(strict);
        board.addScore("Elvin", 88);
    }
}

interface GradingRule { int apply(int raw); }
interface ScoreListener { void onScore(String name, int score); }

class GradeBoard {
    private GradingRule rule;
    private final List<ScoreListener> listeners = new ArrayList<>();

    GradeBoard(GradingRule rule) { this.rule = rule; }
    void setRule(GradingRule rule) { this.rule = rule; }
    void subscribe(ScoreListener listener) { listeners.add(listener); }

    void addScore(String name, int raw) {
        int score = rule.apply(raw);
        listeners.forEach(l -> l.onScore(name, score));          // Observer: notify everyone
    }
}
Gözlənilən nəticə
[parent app] Leyla got 93
[teacher log] Leyla: 93
[parent app] Elvin got 88
[teacher log] Elvin: 88
Strategy (dəyişdirilə bilən qiymətləndirmə qaydası) və Observer (abunəçilərə bildiriş)

Strategy alqoritmi kənardan ötürülə bilən obyektə çevirir: GradeBoard bonuslu və ya sərt qaydanın içində nə olduğunu bilmir, yeni qayda əlavə etmək üçün onu redaktə etmək lazım deyil — bu, SOLID-in «O» prinsipidir. Comparator də strategiyadır. Observer bir obyektin dəyişikliyini ona abunə olmuş bütün obyektlərə çatdırır: valideyn tətbiqi və müəllim jurnalı eyni hadisəni alır, GradeBoard isə onlar haqqında heç nə bilmir. Qrafik interfeysdəki düymə dinləyiciləri və Spring-in hadisələri də belə işləyir.

NümunəHansı problemi həll edirJDK-da və Spring-də
Singletonbir nüsxə kifayətdir: konfiqurasiya, keşRuntime.getRuntime()
Factoryhansı sinfin yaradıldığını gizlətməkList.of(), Path.of()
Builderçoxparametrli obyekti aydın qurmaqStringBuilder, HttpClient.newBuilder()
Strategyalqoritmi işləmə vaxtı dəyişməkComparator
Observerhadisə barədə çoxlu abunəçiyə xəbər verməkPropertyChangeListener, ApplicationEventPublisher

Əsas fikirlər

  • Təmiz kod: mənalı adlar, kiçik metodlar, sehrli ədədlər əvəzinə sabitlər, dəyişməzlik, Optional, boş catch yox.
  • Refaktorinq davranışı dəyişmədən quruluşu yaxşılaşdırır; testlər onu təhlükəsiz edir.
  • SOLID: bir məsuliyyət, redaktəsiz genişlənmə, törəmə siniflərin əvəzlənməsi, kiçik interfeyslər, abstraksiyadan asılılıq.
  • Yaradıcı nümunələr: Singleton (enum), Factory (of(...)), Builder (build()).
  • Davranış nümunələri: Strategy — dəyişdirilə bilən alqoritm (Comparator), Observer — abunəçilərə bildiriş.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
Java-da sadə və thread-lər üçün təhlükəsiz Singleton necə yazılır?