1. Xüsusi hadisələr lazım olanda
Java-nın standart hadisələri, məsələn, düymənin sıxılması, siçanın hərəkəti və ya mətnin dəyişməsi — bunlar buzdağının yalnız görünən hissəsidir. Real tətbiqlərdə bu çərçivələrə sığmayan saysız situasiyalar yaranır. Məsələn, proqram internetdən məlumat yükləyə bilər və yükləmə bitəndə tətbiqin digər hissələrini xəbərdar etmək lazımdır. Oyunda oyunçu yeni bir nailiyyət əldə edə bilər və bunu bir neçə komponentə — məsələn, interfeysə və jurnallaşdırma sisteminə — eyni anda bildirmək gərəkdir. Biznes-tətbiqdə sifarişin vəziyyətinin dəyişməsi eyni vaxtda mühasibatlıq, anbar və istifadəçi üçün bildirişləri tətikləməlidir.
Belə halların hamısında öz hadisə və dinləyici tiplərinizi yaratmaq faydalıdır. Bu, komponentlərin qarşılıqlı fəaliyyəti üçün unikal ssenariləri təsvir etməyə və kodu daha çevik və səliqəli etməyə imkan verir.
İstifadəçi hadisəsinin quruluşu
Java-da öz hadisənizi yaratmaq üçün adətən üç hissə reallaşdırılır:
- Hadisə sinfi — adətən java.util.EventObject-in törəməsi. Hadisə haqqında məlumatları saxlayır (mənbə kimdir, hadisə ilə bağlı hansı məlumatlar var).
- Dinləyici interfeysi — məsələn, MyEventListener, hansı ki, java.util.EventListener-i genişləndirir və hadisənin işlənməsi üçün metodları müəyyənləşdirir.
- Abunə/ləğv mexanizmi — hadisə mənbəyində add...Listener/remove...Listener üçün metodlar və hadisə baş verəndə dinləyicilərin çağırılması (fire...).
Gəlin bunu addım-addım nəzərdən keçirək: məlumatları fayldan və ya şəbəkədən yükləyən tətbiq nümunəsində, yükləmə bitdikdə digərlərini xəbərdar etməliyik.
Hadisə sinfi
Yükləmənin tamamlandığı barədə məlumatı saxlayan hadisə sinfi yaradaq.
import java.util.EventObject;
// Hadisə sinfi: EventObject-dən miras alırıq
public class DataLoadedEvent extends EventObject {
private final String data; // Hadisə haqqında əlavə məlumat
public DataLoadedEvent(Object source, String data) {
super(source); // source — hadisəni yaradan obyekt
this.data = data;
}
public String getData() {
return data;
}
}
Burada source — hadisənin mənbə obyektidir (məsələn, məlumat yükləyicisi), data isə yüklənmiş məlumatların sətridir (bu, fayl yolu, JSON, nəticə və s. ola bilər).
Dinləyici interfeysi
Dinləyici interfeysini müəyyənləşdirək. Adətən o, EventListener-i (tipləşdirmə üçün marker interfeys) genişləndirir.
import java.util.EventListener;
// Hadisəmizin dinləyici interfeysi
public interface DataLoadedListener extends EventListener {
void dataLoaded(DataLoadedEvent event);
}
dataLoaded metodu hadisə baş verdikdə çağırılacaq.
Hadisə mənbəyi
Elə bir subyekt lazımdır ki, dinləyicilərin siyahısını saxlasın, onların qeydiyyatını/silməsini təmin etsin və hadisə baş verəndə xəbərdar etsin.
import java.util.ArrayList;
import java.util.List;
public class DataLoader {
private final List<DataLoadedListener> listeners = new ArrayList<>();
// Dinləyicinin qeydiyyatı
public void addDataLoadedListener(DataLoadedListener listener) {
listeners.add(listener);
}
// Dinləyicinin silinməsi
public void removeDataLoadedListener(DataLoadedListener listener) {
listeners.remove(listener);
}
// Hadisəni başlayan metod (məsələn, məlumatlar yükləndikdən sonra)
private void fireDataLoaded(String data) {
DataLoadedEvent event = new DataLoadedEvent(this, data);
// Bütün dinləyicilərə xəbər veririk
for (DataLoadedListener listener : listeners) {
listener.dataLoaded(event);
}
}
// Məlumatları “yükləyən” metod nümunəsi
public void loadData() {
// Yükləməni simulyasiya edirik (məsələn, fayldan və ya şəbəkədən)
String loadedData = "Bu, yüklənmiş məlumatdır!";
System.out.println("Məlumatlar yükləndi: " + loadedData);
// Bütün dinləyicilərə bildiririk
fireDataLoaded(loadedData);
}
}
Real həyatda loadData() metodu asinxron ola bilər, fayl oxuya, serverə müraciət edə və s., lakin nümunə üçün sadəcə yükləməni simulyasiya edirik.
2. Xüsusi hadisədən istifadə
Tutaq ki, yükləmənin tamamlanmasını bilmək istəyən bir komponentimiz var.
public class DataLoadedHandler implements DataLoadedListener {
@Override
public void dataLoaded(DataLoadedEvent event) {
System.out.println("Emalçı hadisəni qəbul etdi: " + event.getData());
}
}
Hər şeyi tətbiqin əsas sinfində birləşdirək:
public class Main {
public static void main(String[] args) {
DataLoader loader = new DataLoader();
DataLoadedHandler handler = new DataLoadedHandler();
// Dinləyicini qeydiyyatdan keçiririk
loader.addDataLoadedListener(handler);
// Məlumatların yüklənməsini başladırıq
loader.loadData();
}
}
Nə baş verəcək?
- DataLoader məlumatları yükləyir (simulyasiya).
- Yükləmədən sonra fireDataLoaded çağırılır, o hadisə obyektini yaradır və bütün dinləyiciləri xəbərdar edir.
- Bizim emalçı (DataLoadedHandler) hadisəni qəbul edir və mesaj çıxarır.
Çıxış nümunəsi:
Məlumatlar yükləndi: Bu, yüklənmiş məlumatdır!
Emalçı hadisəni qəbul etdi: Bu, yüklənmiş məlumatdır!
Anonim siniflər və lambda ifadələrinin istifadəsi
Hər dinləyici üçün ayrıca siniflər yaratmamaq üçün çox vaxt anonim siniflərdən və ya (Java 8-dən başlayaraq) lambda ifadələrindən istifadə olunur:
public class Main {
public static void main(String[] args) {
DataLoader loader = new DataLoader();
// Dinləyici lambda ifadəsi ilə
loader.addDataLoadedListener(event ->
System.out.println("Lambda-emalçı: " + event.getData())
);
loader.loadData();
}
}
Dinləyicilərin qeydiyyatı və silinməsi
Dinləyiciləri əlavə edib silmək mümkündür. Bu, yaddaşın idarə edilməsi və sızmaların qarşısının alınması üçün vacibdir (xüsusən də dinləyici — “ağır” obyektdirsə və ya artıq lazım deyilsə).
DataLoadedHandler handler = new DataLoadedHandler();
loader.addDataLoadedListener(handler);
// Daha sonra, emalçı artıq lazım deyilsə:
loader.removeDataLoadedListener(handler);
Əgər dinləyicini silməsəniz və hadisə mənbəyi uzunömürlüdürsə, dinləyici siyahıda qalacaq və zibil toplayıcı tərəfindən silinməyəcək — bu, yaddaş sızmasına səbəb ola bilər.
3. Təcrübə: mini-nümunə — klik sayğacı
Tədris tətbiqi çərçivəsində inkişaf etdirilə bilən kiçik bir nümunə edək. Tutaq ki, dəyərini artıran bir sayğac sinfimiz var və hər artırmada dinləyicilərə yeni dəyəri bildirir.
Hadisə sinfi
import java.util.EventObject;
public class CounterChangedEvent extends EventObject {
private final int newValue;
public CounterChangedEvent(Object source, int newValue) {
super(source);
this.newValue = newValue;
}
public int getNewValue() {
return newValue;
}
}
Dinləyici interfeysi
import java.util.EventListener;
public interface CounterChangedListener extends EventListener {
void counterChanged(CounterChangedEvent event);
}
Sayğac sinfi
import java.util.ArrayList;
import java.util.List;
public class Counter {
private int value = 0;
private final List<CounterChangedListener> listeners = new ArrayList<>();
public void addCounterChangedListener(CounterChangedListener listener) {
listeners.add(listener);
}
public void removeCounterChangedListener(CounterChangedListener listener) {
listeners.remove(listener);
}
public void increment() {
value++;
fireCounterChanged();
}
private void fireCounterChanged() {
CounterChangedEvent event = new CounterChangedEvent(this, value);
for (CounterChangedListener listener : listeners) {
listener.counterChanged(event);
}
}
}
İstifadə
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
// Dinləyicini lambda vasitəsilə qeydiyyatdan keçiririk
counter.addCounterChangedListener(event ->
System.out.println("Sayğac dəyişdi: " + event.getNewValue())
);
counter.increment(); // Sayğac dəyişdi: 1
counter.increment(); // Sayğac dəyişdi: 2
}
}
4. Xüsusi hadisələrin yaradılması və işlənməsində tipik səhvlər
Səhv №1: dinləyiciləri xəbərdar edən metodun çağırılmasını unutmaq. Hadisə yaradılır, lakin dinləyiciləri xəbərdar etməli olan metod (fireDataLoaded, fireCounterChanged) çağırılmır. Nəticədə dinləyicilər “susur”.
Səhv №2: dinləyici emalçılarında istisnalar. Dinləyicilərdən biri istisna atarsa, qalanları bildiriş ala bilməyə bilər. Yaxşı təcrübə — dinləyicilərin çağırışlarını try–catch ilə bürüməkdir ki, bir “pis” dinləyici digərlərinə mane olmasın.
Səhv №3: dinləyicilərin silinməməsi. Dinləyici daha lazım deyil, amma silinməyibsə, o, hadisələri almağa davam edəcək və yaddaşdan çıxarılmayacaq. Bu, yaddaş sızmalarına gətirib çıxara bilər — xüsusən mənbə uzunömürlü olanda və dinləyicilər çox olanda.
Səhv №4: iterasiya zamanı dinləyicilər siyahısının modifikasiyası. Hadisə emalında kimsə dinləyici əlavə edib çıxarırsa, bu, ConcurrentModificationException-a səbəb ola bilər. Təhlükəsiz yanaşma — əvvəlcə siyahını ayrıca massivə kopyalamaq, sonra isə nüsxə üzrə iterasiya etməkdir.
Səhv №5: emalçılarda uzun çəkən əməliyyatlar. Əgər emalçı nəyisə uzun müddət edir (fayl yüklənməsi, şəbəkə gözləməsi), interfeys “donub qala” bilər. Ağır tapşırıqları ayrı axına köçürün və ya asinxron mexanizmlərdən istifadə edin.
GO TO FULL VERSION