4. [TD]: Katmanlı mimariler
Anahtar kelimeler: çok katmanlı mimari, Spring, bağımlılık enjeksiyonu.
4.1. Introduction
Yapılanları hatırlayalım:
- ELECTIONS alıştırmasının 1. bölümünde hiçbir sınıf kullanılmamıştı. C dilinde yapacağımız gibi bir çözüm oluşturulmuştu.
- Alıştırmanın 2. bölümünde iki sınıf tanıtıldı:
- [ListeElectorale], bir aday listesinin özelliklerini (id, isim, oy, sandalye sayısı, eleme) temsil eder
- [ElectionsException]: Kontrol edilemeyen istisnalar sınıfı. Bu istisna türü, seçim uygulamasında her ölümcül hata oluştuğunda kullanılır. Kontrol edilemez bir istisnadır (c.a.d), yani geliştiricinin bunu bir try-catch ile yönetmesi gerekmez.
Seçim sonuçlarının hesaplanması, şimdiye kadar [MainElections] sınıfına ait bir yöntem olan [main]'e emanet edilmiştir
Önceki çözüm, üç klasik aşamayı içermektedir:
- verilerin alınması, satır 17-18
- çözümün hesaplanması, satır 19-20
- sonuçların görüntülenmesi ve/veya kalıcı hale getirilmesi, satır 21-22
Sadece 2. aşama gerçekten sabittir. 1. aşama değişebilir: veriler, incelenen örneklerde olduğu gibi klavyeden, bir metin dosyasından, bir grafik arayüzden, bir veritabanından, ağdan vb. gelebilir. Aynı şekilde, 3. aşamada sonuçları sunmanın da birçok yolu vardır: incelenen örneklerde olduğu gibi ekrana görüntülemek, bir dosyaya veya veritabanına kaydetmek, ağ üzerinden göndermek, ...
Daha genel bir bakışla, bir uygulama genellikle her biri açıkça tanımlanmış bir role sahip üç katman halinde modellenebilir:
![]() |
Bu mimariye, İngilizce "three tier architecture" teriminin çevirisi olan "üç katmanlı mimari" de denir. "Üç katman" terimi normalde her katmanın farklı bir makinede bulunduğu bir mimariyi ifade eder. Katmanlar aynı makinede olduğunda, mimari "üç katmanlı" bir mimari haline gelir.
- [metier] katmanı, uygulamanın iş kurallarını içeren katmandır. Seçim uygulamamızda bu kurallar, her bir listenin aldığı oy sayısı bilindiğinde, farklı listelerin kazandığı sandalye sayısını hesaplamayı sağlar. Bu katmanın çalışabilmesi için verilere ihtiyacı vardır. Örneğin seçim uygulamasında:
- her birinin adı ve oy sayısı ile birlikte listeler
- doldurulacak sandalye sayısı
- bir listenin elenmesine neden olan seçim barajı
Yukarıdaki şemada veriler iki kaynaktan gelebilir:
- dosyalara veya veritabanlarına önceden kaydedilmiş veriler için veri erişim katmanı veya [dao] (DAO = Veri Erişim Nesnesi). Burada liste isimleri, doldurulacak koltuk sayısı ve seçim barajı bu duruma örnek olarak verilebilir. Nitekim bu bilgiler seçimden önce bilinmektedir.
- Kullanıcı arayüz katmanı veya [ui] (UI = Kullanıcı Arayüzü), kullanıcı tarafından girilen veya kullanıcıya görüntülenen veriler içindir. Burada, ancak son anda belli olan liste oyları ve seçim sonuçlarının görüntülenmesi bu duruma örnek olabilir.
- Genel olarak, [dao] katmanı, kalıcı (dosyalar, veritabanları) veya geçici (ağ, sensörler, ...) verilere erişimi sağlar.
- [ui] katmanı ise, varsa kullanıcıyla etkileşimlerden sorumludur.
- Bu üç katman, Java arayüzlerinin kullanımı sayesinde birbirinden bağımsız hale getirilmiştir.
- Bu katmanları uygulama içinde bir araya getirmek için çeşitli yöntemler mevcuttur. Biz, "Spring" adlı bir araç kullanacağız. Şemada görüldüğü gibi, bu araç diğer katmanların kesitinden geçmektedir.
Daha önce geliştirilen [Elections] uygulamasını ele alıp ona 3 katmanlı bir mimari kazandıracağız. Bunun için, kalıcı verilerle ilgilenen [dao] katmanından başlayarak, [ui, metier, dao] katmanlarını sırayla inceleyeceğiz.
Öncelikle, [Elections] uygulamasının farklı katmanlarının arayüzlerini tanımlamamız gerekiyor.
4.2. [Elections] uygulamasının arayüzleri
Bir arayüzün, bir dizi yöntem imzasını tanımladığını hatırlayalım. Arayüzü uygulayan sınıflar, bu yöntemlere içerik kazandırır.
Uygulamamızın 3 katmanlı mimarisine geri dönelim:
![]() |
Bu tür bir mimaride, genellikle inisiyatif kullanıcıya aittir. Kullanıcı, [1]'te bir istek gönderir ve [8]'te bir yanıt alır. Buna istek-yanıt döngüsü denir. Seçim gecesi kazanılan sandalye sayısının hesaplanmasını örnek olarak ele alalım. Bu işlem birkaç aşamayı gerektirecektir:
- [ui] katmanı, kullanıcıdan her bir listenin aldığı oy sayısını sormak zorunda kalacaktır. Bunun için kullanıcıya yarıştaki listelerin adlarını sunması gerekecektir. Kullanıcı, her listenin karşısına oy sayısını yazıp koltuk sayısının hesaplanmasını talep etmekle yetinecektir.
- [ui] katmanı, liste isimlerine sahip değildir. Bu isimler, şemanın sağındaki veri kaynağında kayıtlıdır. Katman, bu isimleri almak için [2, 3, 4, 5, 6, 7] yolunu kullanacaktır. [2] işlemi listelerin talep edilmesidir; [7] işlemi ise bu talebe verilen yanıttır. Bu işlemler tamamlandıktan sonra, [8] aracılığıyla listeler kullanıcıya sunulabilir.
- Kullanıcı, her bir listenin aldığı oy sayısını [ui] katmanına iletecektir. Bu, yukarıdaki [1] işlemidir. Bu aşamada kullanıcı yalnızca [ui] katmanıyla etkileşime girer. Girilen verilerin geçerliliğini kontrol edecek olan da bu katmandır. Bu işlem tamamlandıktan sonra, kullanıcı her bir listenin kazandığı koltuk sayısının listesini talep edecektir.
- [ui] katmanı, koltuk sayısının hesaplanmasını iş katmanından talep edecektir. Bunun için kullanıcıdan aldığı verileri iş katmanına iletecektir. Bu, [2] işlemidir.
- [metier] katmanı, görevini başarıyla yerine getirebilmek için belirli bilgilere ihtiyaç duyar. İşlem (b) sayesinde listelere zaten sahiptir. Ayrıca, doldurulacak koltuk sayısı ve seçim barajı değerine de ihtiyacı vardır. Bu bilgileri, [3, 4, 5, 6] yolunu kullanarak [dao] katmanından isteyecektir. [3] ilk istektir ve [6] bu isteğe verilen yanıttır.
- İhtiyacı olan tüm verilere sahip olan [metier] katmanı, her bir listenin kazandığı sandalye sayısını hesaplar.
- [metier] katmanı artık (d) adımında [ui] katmanı tarafından yapılan talebe yanıt verebilir. Bu, [7] yoludur.
- [ui] katmanı, bu sonuçları kullanıcıya uygun bir biçimde sunmak üzere biçimlendirecek ve ardından sunacaktır. Bu, [8] yoludur.
- Bu sonuçların bir dosyada veya veritabanında saklanması gerektiğini düşünebiliriz. Bu işlem otomatik olarak yapılabilir. Bu durumda, (f) işleminden sonra, [metier] katmanı, [dao] katmanından sonuçları kaydetmesini isteyecektir. Bu durumda izlenecek yol [3, 4, 5, 6] olacaktır. Bu işlem, yalnızca kullanıcının talebi üzerine de gerçekleştirilebilir. Bu durumda, istek-yanıt döngüsü tarafından kullanılacak yol [1-8] olacaktır.
Bu açıklamadan, bir katmanın sağındaki katmanın kaynaklarını kullandığı, asla solundaki katmanın kaynaklarını kullanmadığı anlaşılmaktadır. İki bitişik katmanı ele alalım:
![]() |
[A] katmanı, [B] katmanına istekler gönderir. En basit durumlarda, bir katman tek bir sınıfla uygulanır. Bir uygulama zamanla gelişir. Dolayısıyla [B] katmanı, [B1, B2, ...] gibi farklı uygulama sınıflarına sahip olabilir. [B] katmanı, [dao] katmanı ise, bu katmanın bir dosyadan veri alan [B1] adlı bir ilk uygulaması olabilir. Birkaç yıl sonra, verileri bir veritabanına aktarmak istenebilir. Bu durumda, ikinci bir uygulama sınıfı olan [B2] oluşturulur. Eğer ilk uygulamada [A] katmanı doğrudan [B1] sınıfıyla çalışıyorsa, [A] katmanının kodunu kısmen yeniden yazmak zorunda kalırız. Örneğin, [A] katmanında aşağıdakine benzer bir kod yazdığımızı varsayalım:
- 1. satır: [B1] sınıfının bir örneği oluşturulur
- 3. satır: bu örneğe veriler istenir
Yeni uygulama sınıfı [B2]'in, [B1] sınıfıyla aynı imzaya sahip yöntemler kullandığını varsayarsak, tüm [B1]'leri [B2] olarak değiştirmek gerekecektir. Bu, yöntem imzalarına dikkat edilmemişse oldukça olasılığı düşük olan, çok elverişli bir durumdur. Uygulamada, [B1] ve [B2] sınıflarının yöntem imzalarının aynı olmaması sıkça görülür ve bu nedenle [A] katmanının büyük bir kısmının tamamen yeniden yazılması gerekir.
[A] ve [B] katmanları arasına bir arayüz yerleştirilirse durum iyileştirilebilir. Bu, [B] katmanının [A] katmanına sunduğu yöntem imzalarının bir arayüzde sabitlendiği anlamına gelir. Böylece önceki şema şu hale gelir:
![]() |
[A] katmanı artık doğrudan [B] katmanına değil, onun [IB] arayüzüne hitap etmektedir. Dolayısıyla, [A] katmanının kodunda, [B] katmanının [Bi] uygulama sınıfı, yalnızca [IB] arayüzünün uygulanması sırasında bir kez görünür. Böylece, kodda [IB] arayüzü kullanılır, uygulama sınıfı değil. Önceki kod şu şekilde olur:
- 1. satır: [IB] arayüzünü uygulayan bir [ib] örneği, [B1] sınıfının örneklenmesiyle oluşturulur
- 3. satır: [ib] örneğinden veriler istenir
Artık, [B] katmanının [B1] uygulamasını bir [B2] uygulamasıyla değiştirirsek ve bu iki uygulama da aynı [IB] arayüzüne uyuyorsa, o zaman [A] katmanının yalnızca 1. satırı değiştirilmelidir, başka hiçbir satır değiştirilmez. Bu, tek başına iki katman arasında arayüzlerin sistematik olarak kullanılmasını haklı kılan büyük bir avantajdır.
Daha da ileri gidip [A] katmanını [B] katmanından tamamen bağımsız hale getirebiliriz. Yukarıdaki kodda, 1. satır sorun teşkil ediyor çünkü [B1] sınıfına sabit bir şekilde atıfta bulunuyor. İdeal olan, [A] katmanının, herhangi bir sınıf adı belirtmek zorunda kalmadan [IB] arayüzünün bir uygulamasına sahip olabilmesidir. Bu, yukarıdaki şemamızla tutarlı olacaktır. Burada, [A] katmanının [IB] arayüzüne hitap ettiği görülüyor ve bu arayüzü uygulayan sınıfın adını bilmesinin ne gerekçesi olduğu anlaşılmıyor. Bu ayrıntı, [A] katmanı için yararlı değildir.
Spring çerçeve yapısı (http://www.springframework.org) bu sonucu elde etmemizi sağlar. Önceki mimari şu şekilde değişir:
![]() |
[Spring] enine katmanı, bir katmanın, sağındaki katmana ait uygulama sınıfının adını bilmek zorunda kalmadan yapılandırma yoluyla bu katmana bir referans almasını sağlayacaktır. Bu ad, Java kodunda değil, yapılandırma dosyalarında yer alacaktır. [A] katmanının Java kodu ise şu şekilde olacaktır:
- 1. satır: [B] katmanının [IB] arayüzünü uygulayan bir [ib] örneği. Bu örnek, bir yapılandırma dosyasında bulunan bilgilere dayanarak Spring tarafından oluşturulur. Spring, aşağıdakileri oluşturmaktan sorumlu olacaktır:
- [B] katmanını uygulayan [b] örneğini
- [A] katmanını uygulayan [a] örneği. Bu örnek başlatılacaktır. Yukarıdaki [ib] alanı, [B] katmanını uygulayan nesnenin [b] referansını değer olarak alacaktır
- 3. satır: [ib] örneğinden veriler istenir
Şimdi görüyoruz ki, B katmanının [B1] uygulama sınıfı, [A] katmanının kodunda hiçbir yerde görünmüyor. [B1] uygulaması, yeni bir uygulama olan [B2] ile değiştirildiğinde, [A] sınıfının kodunda hiçbir değişiklik olmayacaktır. Sadece Spring yapılandırma dosyalarında, [B1] yerine [B2]'i örneklendirecek şekilde değişiklik yapılacaktır.
Spring ve Java arayüzleri kombinasyonu, uygulama katmanlarını birbirinden bağımsız hale getirerek uygulama bakımında belirleyici bir iyileştirme sağlar. [Elections] uygulaması için de bu çözümü kullanacağız.
Uygulamamızın üç katmanlı mimarisine geri dönelim:
![]() |
Basit durumlarda, uygulamanın arayüzlerini keşfetmek için [metier] katmanından başlayabiliriz. Uygulamanın çalışması için verilere ihtiyacı vardır:
- Bu veriler, dosyalarda, veritabanlarında veya ağ üzerinden zaten mevcut olabilir. Bu veriler, [dao] katmanı tarafından sağlanır.
- henüz mevcut olmayan veriler. Bu durumda, veriler uygulama kullanıcısından elde eden [ui] katmanı tarafından sağlanır.
[dao] katmanı, [metier] katmanına hangi arayüzü sunmalıdır? Bu iki katman arasında hangi etkileşimler mümkündür? [dao] katmanı, [metier] katmanına aşağıdaki verileri sağlamalıdır:
- doldurulacak koltuk sayısı
- bir listenin elenmesine neden olan seçim barajı değeri
- liste isimleri
Bu bilgiler aslında seçimden önce bilinmektedir ve bu nedenle depolanabilir. [metier] -> [dao] yönünde, [metier] katmanı, [dao] katmanından seçim sonuçlarını, özellikle de farklı listelerin kazandığı koltuk sayısını kaydetmesini isteyebilir.
Bu bilgilerle, [dao] katmanının arayüzüne ilişkin ilk bir tanımlama yapılabilir:
public interface IElectionsDao {
public double getSeuilElectoral();
public int getNbSiegesAPourvoir();
public ListeElectorale[] getListesElectorales();
public void setListesElectorales(ListeElectorale[] listesElectorales);
}
- 1. satır: Arayüzün adı [IElectionsDao]'tir. Dört yöntem tanımlar:
- veri kaynağından gelen verileri okumak için üç yöntem: [getSeuilElectoral, getNbSiegesAPourvoir, getListesElectorales]. Bu üç yöntem, [metier] katmanının mevcut seçimi tanımlayan verileri almasını sağlayacaktır.
- veri kaynağına veri yazmak için bir yöntem: [setListesElectorales]. Bu yöntem, [metier] katmanının hesapladığı sonuçların kaydedilmesini talep etmesini sağlayacaktır.
Uygulamamızın üç katmanlı mimarisine geri dönelim:
![]() |
[metier] katmanı, [ui] katmanına hangi arayüzü sunmalıdır? Bu iki katman arasındaki olası etkileşimleri inceleyelim.
- [ui] katmanının görevi, kullanıcıdan yarışmadaki farklı listelere verilen oyları sormaktır. Bunun için liste sayısını bilmesi gerekir. Bu bilgiyi [metier] katmanından isteyebilir; bu katman da yarışan listelerin tablosunu [dao] katmanından isteyebilir. [metier] katmanı bu tabloya sahipse, bu tabloyu [ui] katmanına aktarabilir. Böylece bu katman, listelerin adlarına sahip olacak ve örneğin "A listesinin oy sayısı" gibi sorular sorarak kullanıcıya gönderdiği mesajları daha ayrıntılı hale getirebilecektir.
- [ui] katmanı tüm listelerin oylarını aldıktan sonra, [metier] katmanından sandalye sayısının hesaplanmasını isteyecektir. Bu katman hesaplamayı yapıp sonucu [ui] katmanına iletebilir.
- [ui] katmanı daha sonra bu sonuçları kullanıcıya sunabilir. Kullanıcı ayrıca bu sonuçların kaydedilmesini de talep edebilir.
- [ui] katmanı ayrıca, seçim barajı veya doldurulacak koltuk sayısı gibi ek bilgileri kullanıcıya sunmak isteyebilir.
Bu bilgilerle, [metier] katmanının arayüzü için ilk bir tanımlama yapılabilir:
public interface IElectionsMetier {
public ListeElectorale[] getListesElectorales();
public int getNbSiegesAPourvoir();
public double getSeuilElectoral();
public void recordResultats(ListeElectorale[] listesElectorales);
public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);
}
- 1. satır: Arayüzün adı [IElectionsMetier]'tir. Aşağıdaki yöntemleri tanımlar:
- 3. satır: [getListesElectorales] yöntemi, [ui] katmanının yarışan listelerin tablosunu almasını sağlar;
- satır 5: [getNbSiegesAPourvoir] yöntemi, doldurulacak koltuk sayısını elde etmeyi sağlar;
- 7. satır: [getSeuilElectoral] yöntemi, seçim barajını elde etmeyi sağlar;
- satır 11: [calculerSieges] yöntemi (satır 36), farklı listelerin oy sayıları belli olduktan sonra [ui] katmanının koltuk hesaplamasını talep etmesini sağlayacaktır. Parametre, koltukları ve elenen boole değeri hariç olmak üzere yarışan listelerin tablosudur. Dönüş sonucu, bu kez [sièges, elimine] alanları başlatılmış halde aynı tablodur;
- 9. satır: [recordResultats] yöntemi, [ui] katmanının sonuçların kaydedilmesini talep etmesini sağlayacaktır.
Not: Konumu nedeniyle, [métier] katmanı, [DAO] katmanındaki bazı yöntemleri devralarak bunları [UI] katmanına sunar. Bu yineleme nedeniyle, hem iş işlevlerini hem de veri erişimini tek bir katmanda bir araya getirmek cazip gelebilir. Bu tek katman bazen model olarak adlandırılır; MVC kısaltmasındaki M harfi (Model - Görünüm - Denetleyici) bunu ifade eder. MVC, web uygulamalarında yaygın olarak kullanılan bir tasarım kalıbıdır (design pattern).
[calculerSieges] yönteminin imzasını inceleyelim:
public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);
Yukarıda şöyle yazılmıştı: “Parametre, koltukları ve elenen boole değeri hariç, yarışan listelerin dizisidir. Sonuç ise aynı dizidir, ancak bu sefer [sièges, elimine] alanları da eklenmiştir.” Yöntemin imzası şu şekilde de olabilir:
public void calculerSieges(ListeElectorale[] listesElectorales);
[listesElectorales] parametresi bir nesne referansıdır; burada bir dizidir. Her bir eleman da sırasıyla bir nesne referansıdır; burada [ListeElectorale] türündedir. [calculerSieges] yöntemi, bu nesnelerin her birindeki [sieges, elimine] alanlarını değiştirecektir. Çağıran yöntem, aşağıdaki özelliklere sahip bir [listesElectorales] işaretçisine sahiptir:
- çağrıdan önce, [ListeElectorale] adlı bir nesne dizisinin referansıdır ve bu dizinin [sieges, elimine] alanları başlatılmamıştır;
- çağrıdan sonra, [ListeElectorale] adlı bir nesne dizisinin referansıdır (aynı referans) ve bu dizinin [sieges, elimine] alanları başlatılmıştır;
Öyleyse neden şu imza kullanılsın ki:
public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);
Bir arayüz yazarken, bunun iki farklı bağlamda kullanılabileceğini hatırlamak önemlidir: local ve distant. local bağlamında, çağıran yöntem ve çağrılan yöntem aynı JVM (Java Sanal Makinesi) içinde yürütülür:
![]() |
[ui] katmanı, [DAO] katmanındaki calculerSieges yöntemini çağırırsa, yönteme aktardığı [ListeElectorale[] listesElectorales] parametresine bir referans içerir ve bunu yönteme aktarır.
distant bağlamında, çağıran yöntem ve çağrılan yöntem farklı JVM'lerde yürütülür:
![]() |
Yukarıda, [ui] katmanı JVM 1'de, [métier] katmanı ise JVM 2'de, iki farklı makinede yürütülür. Bu iki katman birbiriyle doğrudan iletişim kurmaz. Aralarında, [1] iletişim katmanı olarak adlandıracağımız bir katman yer alır. Bu katman, bir [2] gönderim katmanı ve bir [3] alım katmanından oluşur. Geliştiricinin genellikle bu iletişim katmanlarını yazması gerekmez. Bunlar yazılım araçları tarafından otomatik olarak oluşturulur. [metier] katmanı, sanki [DAO] katmanıyla aynı JVM içinde çalışıyormuş gibi yazılır. Dolayısıyla kodda herhangi bir değişiklik yapılmaz.
[ui] katmanı ile [métier] katmanı arasındaki iletişim mekanizması şöyledir:
- [ui] katmanı, [métier] katmanının calculerSieges yöntemini çağırır ve ona [ListeElectorale[] listesElectorales1] parametresini ileterek;
- bu parametre aslında [2] gönderim katmanına aktarılır. Bu katman, referansını değil, listesElectorales1 parametresinin değerini ağ üzerinden iletecektir. Bu değerin tam biçimi, kullanılan iletişim protokolüne bağlıdır;
- [3] alıcı katmanı bu değeri alacak ve bu değerden yola çıkarak, [metier] katmanı tarafından gönderilen başlangıçtaki parametrenin bir kopyası olan [ListeElectorale[] listesElectorales2] nesnesini yeniden oluşturacaktır. Artık iki farklı JVM katmanında (içerik açısından) birbirinin aynısı iki nesne bulunmaktadır: listesElectorales1 ve listesElectorales2.
- Alıcı katman, listesElectorales2 nesnesini [métier] katmanındaki calculerSieges yöntemine aktaracak; bu yöntem de nesneyi veritabanında kalıcı hale getirecektir. Bu işlemden sonra, listesElectorales2 referansı, [sieges, elimine] alanları başlatılmış olan [ListeElectorale] nesnelerinden oluşan bir diziye işaret eder. . [ui] katmanının referans verdiği listesElectorales1 nesnesi için durum böyle değildir. [ui] katmanının listesElectorales2 nesnesine bir referans vermesini istiyorsak, bu nesneyi ona göndermemiz gerekir. Bu nedenle, [calculerSieges] yöntemi için aşağıdaki imzayı kullanmamız gerekir:
public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);
- Bu imza ile calculerSieges yöntemi, sonuç olarak listesElectorales2 referansını döndürür. Bu sonuç, [métier] katmanını çağıran alıcı katman [3]’e iletilir. Bu katman, listesElectorales2'in değerini (referansını değil) [2] gönderim katmanına döndürecektir;
- gönderim katmanı [2] bu değeri alacak ve bu değerden yola çıkarak bir [ListeElectorale[] listesElectorales3] nesnesini yeniden oluşturacaktır.
- [ListeElectorale[] listesElectorales3] nesnesi, tüm bu mekanizmayı [DAO] katmanındaki calculerSieges yöntemine yapılan çağrı ile başlatan [ui] katmanındaki yönteme işlenir;
Bu süreçte, [ListeElectorale] türündeki nesneler, [2] ve [3] katmanları arasında aktarılacaktır:
- [2] katmanı, bir [ListeElectorale] nesnesinin değerini [3] katmanına ilettiğinde, bu nesnenin serileştirildiği söylenir. Bu serileştirmenin tam biçimi, kullanılan iletişim protokolüne bağlıdır;
- [3] katmanı, bir [ListeElectorale] nesnesinin değerini yeniden bir [ListeElectorale] nesnesi oluşturmak amacıyla geri aldığında, bu nesnenin deserialize edildiği söylenir;
Bir nesnenin bu serileştirme/deserileştirme işlemine tabi tutulabilmesi için, bazı protokoller nesnenin [Serializable] arayüzünü uygulamasını gerektirir. Bu arayüz sadece bir işaretçidir. Uygulanması gereken herhangi bir yöntem yoktur. Bu nedenle, [ListeElectorale] sınıfı artık şu şekilde tanımlanacaktır:
public abstract class ListeElectorale implements Serializable {
private static final long serialVersionUID = 1L;
- 2. satırdaki alan zorunludur. Bu alanı olduğu gibi koruyabilir ve [Serializable] türündeki tüm sınıflar için kullanabilirsiniz.
4.3. İstisna sınıfı
[DAO] katmanının arayüzüne geri dönelim:
![]() |
public interface IElectionsDao {
public double getSeuilElectoral();
public int getNbSiegesAPourvoir();
public ListeElectorale[] getListesElectorales();
public void setListesElectorales(ListeElectorale[] listesElectorales);
}
Bu yöntemler bir veritabanıyla çalışır ve çeşitli hatalarla karşılaşabilir; örneğin, SGBD'in mevcut olmaması gibi. Bir yöntem yazarken, her zaman hata durumlarını öngörmek gerekir. Bu hatalar genellikle bir istisna ile bildirilir. 3.3. paragrafında [ElectionsException] sınıfıyla daha önce karşılaşmıştık. Bu sınıfı kullanmaya devam edeceğiz, ancak şu şekilde zenginleştireceğiz:
package ...;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// Elections uygulaması için istisna sınıfı
// istisna kontrolsüz
public class ElectionsException extends RuntimeException implements Serializable {
// seri ID
private static final long serialVersionUID = 1L;
// yerel alanlar
private int code;
private List<String> erreurs;
// yapıcılar
public ElectionsException() {
super();
}
public ElectionsException(int code, Throwable e) {
// üst
super(e);
// yerel
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message, Throwable e) {
// üst öğe
super(message,e);
// yerel
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message) {
// üst öğe
super(message);
// yerel
this.code = code;
List<String> erreurs = new ArrayList<>();
erreurs.add(message);
this.erreurs = erreurs;
}
public ElectionsException(int code, List<String> erreurs) {
// üst
super();
// yerel
this.code = code;
this.erreurs = erreurs;
}
// bir istisnaya ait hata mesajları listesi
private List<String> getErreursForException(Throwable th) {
// istisnanın hata mesajları listesi alınır
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// mesaj, yalnızca !=null ve boş değilse alınır
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// sonraki neden
cause = cause.getCause();
}
return erreurs;
}
// getter ve setter'lar
...
}
- 16-17. satırlar: [ElectionsException] türü şunları kapsar:
- bir hata kodu, 16. satır;
- bir hata mesajları listesi, satır 17;
Sınıf beş oluşturucu destekler:
- satır 20: ElectionsException()
- satır 24: ElectionsException(int code, Throwable e): ikinci parametre, [Exception] sınıfının üst sınıfı olan [Throwable] türündedir. Bu oluşturucu, e istisnasını bir hata koduyla kapsüllemeye olanak tanır. [Throwable] türü (ve dolayısıyla Exception türü), bir veya daha fazla istisnayı kapsüllemeye olanak tanır. Buradaki amaç şudur:
- meydana gelen bir istisnayı yakalamak (catch);
- onu yeni bir istisna içine kapsülleyerek bir mesajla zenginleştirmek;
- yeni istisnayı yeniden başlatmak;
Kapsülleme, 34. satırda [super(message,e)] komutuyla gerçekleştirilir. Bu kapsülleme süreci tekrarlanabilir ve ilk istisna çeşitli mesajlarla zenginleştirilebilir. Bu durumda bir istisna yığını olduğu söylenir. [private List<String> getErreursForException(Throwable th)] yöntemi, kapsüllenmiş istisnalara ilişkin çeşitli mesajları elde etmeyi sağlar:
- (devamı)
- (devam)
- kapsüllenmiş istisna, Throwable [Throwable].getCause() yöntemi ile elde edilir;
- bir istisna ile ilişkili mesaj, String [Throwable].getMessage() yöntemidir;
- (devam)
- 28-29. satırlar: [code, erreurs] alanları oluşturulur;
- 32. satır: public ElectionsException(int code, String message, Throwable e): Bu yapıcı, kapsayacağı istisnaya bir kod ve bir mesaj eklemesi dışında bir öncekiyle benzerdir;
- satır 40: public ElectionsException(int code, String message): istisna kapsülleme içermeyen oluşturucu;
- satır 50: public ElectionsException(int code, List<String> erreurs): istisna kapsülleme ve mesaj içermeyen yapıcı;
[ElectionsException] sınıfı şu şekilde kullanılabilir:
burada mesajın olup olmayacağı belirtilir. Oluşturulduktan sonra, [ElectionsException] istisnası yeni istisnaları kapsama amacı taşımaz. Yukarıda, bu istisna e1 istisnasını ve e1 istisnasının kapsadığı istisnaları kapsar. Bundan sonra yeni kapsamalar olmaz.
[ElectionsException] sınıfı şu şekilde de kullanılabilir:






