1. İerarxiyanın qurulması: abstraksiyadan detala
Abstraksiyaların və ierarxiyaların reallaşdırılması — kodu ümumi qaydalardan konkret detallara doğru strukturlamaq üsuludur. Əvvəlcə bütün obyektlərin nə bacarmalı olduğunu (abstraksiya) təsvir edirik, sonra isə hər bir konkret sinfin bunu necə etdiyini dəqiqləşdiririk.
Proqramlaşdırmada, həyatdakı kimi, hər şey suallardan başlayır. Məsələn: «Dairə ilə düzbucaqlının ortaq nəyi var?» Cavab: onların hər ikisi — fiqurlardır. Bəs fiqurların ortaq nəyi var? Adətən onların sahəsi olur və onları çəkmək olar.
Java-da bu, abstract-sinif vasitəsilə ifadə olunur:
public abstract class Shape {
public abstract double area();
public abstract void draw();
}
Burada biz deyirik:
- Hər bir fiqur sahəsini hesablamağı bacarmalıdır (area()).
- Hər bir fiqur çəkilməyi bacarmalıdır (draw()).
- Bunu dəqiq necə etdiyi — hələlik bizim işimiz deyil.
İndi konkret fiqurlar yaradaq:
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public void draw() {
System.out.println("Radiusu " + radius + " olan dairə çəkirik");
}
}
public class Rectangle extends Shape {
private double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
@Override
public void draw() {
System.out.println("Düzbucaqlı çəkirik " + width + "x" + height);
}
}
Nə etdik?
- Ümumi olanı abstrakt sinifə çıxardıq.
- Davranışı törəmə siniflərdə detallandırdıq.
Sxematik olaraq:
. Shape
/ \
Circle Rectangle
Cədvəl: harada nə reallaşdırılıb
| Sinif | area() | draw() | Öz sahələri |
|---|---|---|---|
| Shape | |
|
- |
| Circle | reallaşdırılıb | reallaşdırılıb | |
| Rectangle | reallaşdırılıb | reallaşdırılıb | |
2. Niyə bu rahatdır? (və ümumiyyətlə niyə işləyir)
Müxtəlif obyektlərlə işləmək üçün vahid interfeys
Tutaq ki, sizdə fiqurların kolleksiyası var:
Shape[] shapes = {
new Circle(5),
new Rectangle(3, 4),
new Circle(2.5)
};
Onları növü haqqında düşünmədən eyni cür dövr edə bilərsiniz:
for (Shape shape : shapes) {
shape.draw();
System.out.println("Sahə: " + shape.area());
}
Qoy JVM özü hansının dairə, hansının düzbucaqlı olduğunu müəyyən etsin! Bu, polimorfizmdir (artıq bu haqda danışmışıq və növbəti blokda daha ətraflı danışacağıq).
Asan genişlənmə
Üçbucaq əlavə etmək istədiniz? Sadəcə yazın (yeni tip — köhnə kodu dəyişmirik): Triangle extends Shape.
public class Triangle extends Shape {
private double base, height;
public Triangle(double base, double height) {
this.base = base;
this.height = height;
}
@Override
public double area() {
return 0.5 * base * height;
}
@Override
public void draw() {
System.out.println("Üçbucaq çəkirik: oturacaq " + base + ", hündürlük " + height);
}
}
Qalan bütün kodu (məsələn, fiqurlar siyahısında dövrü) dəyişmək lazım deyil.
Kod təkrarlanmasının qarşısını almaq
Əgər bütün fiqurlarda ümumi bir xüsusiyyət (məsələn, rəng) yaransa, onu abstrakt sinifə çıxarmaq rahatdır:
public abstract class Shape {
private String color = "qara";
public String getColor() { return color; }
public void setColor(String color) { this.color = color; }
public abstract double area();
public abstract void draw();
}
İndi istənilən törəmə — istər dairə, istər üçbucaq — rəngi «irsi» olaraq alacaq.
3. Təcrübə: mini qrafik redaktor hazırlayırıq
Gəlin hamısını bir yerə toplayaq. Təsəvvür edin ki, siz sadə bir qrafik redaktor hazırlayırsınız.
Abstrakt Figure sinfi
public abstract class Figure {
private String color = "black";
public String getColor() { return color; }
public void setColor(String color) { this.color = color; }
public abstract void draw();
public abstract void resize(double factor);
}
Konkret fiqurlar
public class Line extends Figure {
private double length;
public Line(double length) {
this.length = length;
}
@Override
public void draw() {
System.out.println("Uzunluğu " + length + " olan xətt, rəng " + getColor());
}
@Override
public void resize(double factor) {
length *= factor;
System.out.println("Xəttin yeni uzunluğu: " + length);
}
}
public class Ellipse extends Figure {
private double a, b;
public Ellipse(double a, double b) {
this.a = a;
this.b = b;
}
@Override
public void draw() {
System.out.println("Oxları " + a + " və " + b + " olan ellips, rəng " + getColor());
}
@Override
public void resize(double factor) {
a *= factor;
b *= factor;
System.out.println("Ellipsin yeni oxları: " + a + ", " + b);
}
}
Yeni alət — məsələn, Polygon — əlavə etmək istəyirsiniz? Sadəcə yeni sinif yaradın — redaktorun bütün kodu abstrakt Figure vasitəsilə işləyir.
Kodda istifadə
Figure[] figures = {
new Line(10),
new Ellipse(5, 3)
};
for (Figure figure : figures) {
figure.setColor("red");
figure.draw();
figure.resize(1.5);
}
Çıxış:
Uzunluğu 10.0 olan xətt, rəng red
Xəttin yeni uzunluğu: 15.0
Oxları 5.0 və 3.0 olan ellips, rəng red
Ellipsin yeni oxları: 7.5, 4.5
İerarxiyanın vizuallaşdırılması
. Figure
/ \
Line Ellipse
4. Təkrarı necə önləmək: ümumi sahələr və metodlar
Bəzən bütün törəmələrdə təkcə ümumi metodlar deyil, ümumi sahələr də olur (məsələn, mərkəzin koordinatları). Abstrakt sinif — bunun üçün ideal yerdir:
public abstract class Figure {
private double x, y; // mərkəzin koordinatları
public Figure(double x, double y) {
this.x = x;
this.y = y;
}
public void moveTo(double newX, double newY) {
x = newX;
y = newY;
System.out.println("Fiqur (" + x + ", " + y + ") nöqtəsinə köçürüldü");
}
public abstract void draw();
}
İndi Line və ya Ellipse kimi istənilən fiqur bu metodu yenidən reallaşdırmadan hərəkət edə bilər.
5. Daha bir nümunə: ödəniş sistemləri
Abstraksiya — təkcə fiqurlar üçün deyil! Təsəvvür edin ki, siz ödənişlərin işlənməsi üçün sistem yazırsınız.
Abstrakt Payment sinfi
public abstract class Payment {
public abstract void process();
}
Konkret reallaşdırmalar
public class CreditCardPayment extends Payment {
@Override
public void process() {
System.out.println("Kredit kartı ilə ödənişin emalı");
}
}
public class PaypalPayment extends Payment {
@Override
public void process() {
System.out.println("PayPal vasitəsilə ödənişin emalı");
}
}
İstifadə
Payment[] payments = {
new CreditCardPayment(),
new PaypalPayment()
};
for (Payment payment : payments) {
payment.process();
}
Çıxış:
Kredit kartı ilə ödənişin emalı
PayPal vasitəsilə ödənişin emalı
6. Bu yanaşmanın üstünlükləri
- Vahid interfeys: müxtəlif obyektlərlə eyni qaydada işləmək olur.
- Genişləndiriləbilənlik: yeni obyekt növlərinin əlavə olunması köhnə kodun yenidən yazılmasını tələb etmir.
- Minimum təkrar: ümumi olanlar baza abstrakt sinifə çıxarılıb.
- Elastiklik: detallara baş qoşmadan abstrakt tiplərin kolleksiyalarından istifadə etmək mümkündür.
7. Həyatdan nümunə: nəqliyyat
Abstraksiya təkcə dərsliklərdə deyil. Məsələn, nəqliyyatın idarə olunması üçün sistem layihələndirirsinizsə:
public abstract class Transport {
public abstract void move();
public abstract void fuelUp();
}
Konkret nəqliyyat növləri detalları reallaşdırır:
public class Car extends Transport {
@Override
public void move() {
System.out.println("Maşın yolda gedir");
}
@Override
public void fuelUp() {
System.out.println("Benzin doldururuq");
}
}
public class Bicycle extends Transport {
@Override
public void move() {
System.out.println("Velosiped pedalları fırladır");
}
@Override
public void fuelUp() {
System.out.println("Velosipedə yanacaq lazım deyil, yalnız velosipedçi üçün sendviçlər lazımdır!");
}
}
8. Faydalı sxem: abstraksiya ierarxiyasını necə qurmaq
[Abstrakt sinif]
|
[Konkret alt sinif]
|
[Daha da konkret alt sinif] (əgər lazımdırsa)
- Bütün ümumi olan — yuxarıda!
- Bütün unikal olan — aşağıda!
9. Abstraksiyaları və ierarxiyaları reallaşdırarkən tipik səhvlər
Səhv № 1: Alt siniflərdə kodun təkrarı.
Əgər birdən-birə hər bir alt sinifdə eyni sahələri və ya metodları yazdığınızı görsəniz — bu, onların abstrakt sinifə çıxarılmasının lazım olduğuna işarədir. Təkrarı azaldacaqsa, abstraksiyanı «genişləndirməkdən» çəkinməyin.
Səhv № 2: «Ümumidən xüsusiyə doğru» prinsipinin pozulması.
Bəzən yeni başlayanlar ierarxiyanı «detallardan» qurmağa başlayır və ümumini unudurlar. Nəticədə RedCircleWithShadow kimi ümumi struktura zəif uyğunlaşan qəribə siniflər yaranır. Həmişə əvvəl abstraksiyanı ayırın, sonra detalları.
Səhv № 3: Həddindən artıq dərin ierarxiyalar.
Əgər miraslanma zənciriniz 3–4 səviyyədən uzundursa, düşünün: bəlkə miras əvəzinə kompozisiya və ya interfeyslərdən istifadə etməyin vaxtıdır?
Səhv № 4: Aktual olmayan metodların məcburi reallaşdırılması.
Əgər abstrakt sinif bəzi törəmələr üçün aktual olmayan həddindən artıq çox abstrakt metod ehtiva edirsə, struktur yenidən gözdən keçirilməlidir. Məsələn, bütün nəqliyyat vasitələrinə fuelUp() metodu lazım deyil (velosiped üçün bu, mənasızdır).
Səhv № 5: Abstrakt siniflə interfeys arasında çaşqınlıq.
Abstrakt sinif — ümumi vəziyyət (state) və/və ya qismi reallaşdırma olduqda. İnterfeys — yalnız metodların mövcudluğunu «vəd etmək» lazım olduqda, lakin məlumat saxlamaq və davranışı reallaşdırmaq lazım olmadıqda. Zərurət olmadıqda bu yanaşmaları qarışdırmayın.
GO TO FULL VERSION