6. 3 katmanlı mimariler
6.1. Introduction
Vergi hesaplama uygulamasının son sürümüne geri dönelim:
using System;
namespace Chap3 {
class Program {
static void Main() {
// etkileşimli vergi hesaplama programı
// kullanıcı klavyeden üç veri girer: evli nbEnfants maaş
// program daha sonra ödenecek vergi tutarını gösterir
...
// bir nesne oluşturulur IImpot
IImpot impot = null;
try {
// bir nesne oluşturulur: IImpot
impot = new FileImpot("DataImpotInvalide.txt");
} catch (FileImpotException e) {
// hata mesajı görüntülenir
...
// programın durdurulması
Environment.Exit(1);
}
// sonsuz döngü
while (true) {
// vergi hesaplaması için parametreler isteniyor
Console.Write("Paramètres du calcul de l'Impot au format : Marié (o/n) NbEnfants Salaire ou rien pour arrêter :");
string paramètres = Console.ReadLine().Trim();
...
// parametreler doğru - vergi hesaplanıyor
Console.WriteLine("Impot=" + impot.calculer(marié == "o", nbEnfants, salaire) + " euros");
// sonraki vergi mükellefi
}//while
}
}
}
Önceki çözüm, programlamada klasik işlemleri içeriyordu:
- dosyalarda, veritabanlarında depolanan verilerin alınması, ... 12-21. satırlar
- kullanıcıyla etkileşim, satır 26 (girişler) ve 29 (görüntülemeler)
- iş mantığı algoritmasının kullanımı, satır 29
Uygulamada görüldüğü üzere, bu farklı işlemleri ayrı sınıflara ayırmak uygulamaların bakım kolaylığını artırmaktadır. Bu şekilde yapılandırılmış bir uygulamanın mimarisi şöyledir:
![]() |
Bu mimariye, İngilizce "three tier architecture" teriminin çevirisi olan "üç katmanlı mimari" denir. "Üç katman" terimi normalde her katmanın farklı bir makinede bulunduğu bir mimariyi ifade eder. Katmanlar aynı makinede bulunduğunda, mimari "üç katmanlı" bir mimari haline gelir.
- [metier] katmanı, uygulamanın iş kurallarını içeren katmandır. Vergi hesaplama uygulamamızda bu kurallar, bir vergi mükellefinin vergisini hesaplamayı sağlar. Bu katmanın çalışabilmesi için verilere ihtiyacı vardır:
- her yıl değişen vergi dilimleri
- vergi mükellefinin çocuk sayısı, medeni durumu ve yıllık maaşı
Yukarıdaki şemada, veriler iki kaynaktan gelebilir:
- verilere erişim katmanı veya [dao] (DAO = Veri Erişim Nesnesi), dosyalar veya veritabanlarında önceden kaydedilmiş veriler için. Uygulamanın önceki sürümünde yapıldığı gibi, burada vergi dilimleri için bu durum geçerli olabilir.
- 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 vergi mükellefinin çocuk sayısı, medeni durumu ve yıllık maaşı 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şimleri yönetir.
- Bu üç katman, arayüzlerin kullanımı sayesinde birbirinden bağımsız hale getirilmiştir.
Daha önce birkaç kez incelediğimiz [Impots] uygulamasını ele alarak 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.
Bundan önce, [Impots] uygulamasının farklı katmanlarının arayüzlerini tanımlamamız gerekiyor.
6.2. [Impots] 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. Bir vergi mükellefinin vergisinin hesaplanması örneğini ele alalım. Bu işlem birkaç aşamayı gerektirecektir:
- [ui] katmanı, kullanıcıdan çocuk sayısını, medeni durumunu ve yıllık maaşını sormak zorunda kalacaktır. Bu, yukarıdaki [1] işlemidir.
- Bu işlem tamamlandıktan sonra, [ui] katmanı, vergi hesaplamasını yapması için iş katmanına talepte bulunacaktır. Bunun için kullanıcıdan aldığı verileri iş katmanına iletecektir. Bu, [2] işlemidir.
- [metier] katmanı, işini başarıyla tamamlamak için belirli bilgilere ihtiyaç duyar: vergi dilimleri. Bu bilgileri, [3, 4, 5, 6] yolu üzerinden [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ı, vergiyi hesaplar.
- [metier] katmanı artık (b) adımında [ui] katmanının yaptığı talebe yanıt verebilir. Bu, [7] yoludur.
- [ui] katmanı bu sonuçları biçimlendirecek ve ardından kullanıcıya sunacaktır. Bu, [8] yoludur.
- Kullanıcının vergi simülasyonları yaptığını ve bunları kaydetmek istediğini varsayabiliriz. Bunu yapmak için [1-8] yolunu kullanacaktır.
Bu açıklamadan, bir katmanın sağındaki katmanın kaynaklarını kullandığı, asla solundaki katmanın kaynaklarını kullanmadığı anlaşılmaktadır. Birbirine bitişik iki 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ı olmayan, ç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] çapraz katmanı, bir katmanın, sağındaki katmanın 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, C# kodunda değil, yapılandırma dosyalarında yer alacaktır. [A] katmanının C# kodu ise şu şekli alı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ünmemektedir. [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]'in örneklenmesi için gerekli değişiklikler yapılacaktır.
Spring ve C# arayüzlerinin birleşimi, uygulama katmanlarını birbirinden bağımsız hale getirerek uygulama bakımında belirleyici bir iyileştirme sağlar. [Impots] uygulamasının yeni sürümü 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 alınan [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:
- vergi dilimleri
Uygulamamızda, [dao] katmanı mevcut verileri kullanır ancak yeni veriler oluşturmaz. [dao] katmanının arayüz tanımı şu şekilde olabilir:
using Entites;
namespace Dao {
public interface IImpotDao {
// vergi dilimleri
TrancheImpot[] TranchesImpot{get;}
}
}
- 3. satır: [dao] katmanı, [Dao] ad alanına yerleştirilecektir
- 6. satır: IImpotDao arayüzü, TranchesImpot özelliğini tanımlar; bu özellik, [métier] katmanına vergi dilimlerini sağlayacaktır.
- 1. satır: TrancheImpot yapısının tanımlandığı ad alanını içe aktarır:
namespace Entites {
// bir vergi dilimi
public struct TrancheImpot {
public decimal Limite { get; set; }
public decimal CoeffR { get; set; }
public decimal CoeffN { get; set; }
}
}
Uygulamamızın üç katmanlı mimarisine geri dönelim:
![]() |
[metier] katmanı, [ui] katmanına hangi arayüzü sunmalıdır? Bu iki katman arasındaki etkileşimleri hatırlayalım:
- [ui] katmanı, kullanıcıdan çocuk sayısını, medeni durumunu ve yıllık maaşını ister. Bu, yukarıdaki [1] işlemidir.
- Bu işlem tamamlandıktan sonra, [ui] katmanı, koltuk sayısını hesaplaması için iş katmanına talepte bulunur. Bunun için kullanıcıdan aldığı verileri iş katmanına iletir. Bu, [2] işlemidir.
[metier] katmanının arayüz tanımı şu şekilde olabilir:
namespace Metier {
interface IImpotMetier {
int CalculerImpot(bool marié, int nbEnfants, int salaire);
}
}
- 1. satır: [metier] katmanıyla ilgili her şey [Metier] ad alanına yerleştirilecektir.
- 2. satır: IImpotMetier arayüzü yalnızca bir yöntem tanımlar: bu yöntem, bir vergi mükellefinin medeni durumu, çocuk sayısı ve yıllık maaşı temel alınarak vergisini hesaplamaya yarar.
Bu katmanlı mimarinin ilk bir uygulamasını inceliyoruz.
6.3. Örnek uygulama - sürüm 4
6.3.1. Visual Studio projesi
Visual Studio projesi şu şekilde olacaktır:
![]() |
- [1]: [Entites] klasörü, katmanlar arasında ortak kullanılan nesneleri içerir [ui, metier, dao]: TrancheImpot yapısı, FileImpotException istisnası.
- [2]: [Dao] klasörü, [dao] katmanının sınıflarını ve arayüzlerini içerir. IImpotDao arayüzünün iki uygulamasını kullanacağız: 4.10. paragrafta incelenen HardwiredImpot sınıfı ve 5.8. paragrafta incelenen FileImpot sınıfı.
- [3]: [Metier] klasörü, [metier] katmanının sınıflarını ve arayüzlerini içerir
- [4]: [Ui] klasörü, [ui] katmanının sınıflarını içerir
- [5]: [DataImpot.txt] dosyası, [dao] katmanının FileImpot uygulaması tarafından kullanılan vergi dilimlerini içerir. [6] dosyası, projenin çalışma klasörüne otomatik olarak kopyalanacak şekilde yapılandırılmıştır.
6.3.2. Uygulamanın bileşenleri
Uygulamamızın 3 katmanlı mimarisine tekrar göz atalım:
![]() |
Katmanlar arasında ortak olan sınıflara entités adını veriyoruz. Bu genellikle [dao] katmanındaki verileri kapsülleyen sınıflar ve yapılar için geçerlidir. Bu varlıklar genellikle [ui] katmanına kadar uzanır.
Uygulamadaki varlıklar şunlardır:
TrancheImpot yapısı
namespace Entites {
// bir vergi dilimi
public struct TrancheImpot {
public decimal Limite { get; set; }
public decimal CoeffR { get; set; }
public decimal CoeffN { get; set; }
}
}
L' istisnası FileImpotException
using System;
namespace Entites {
public class FileImpotException : Exception {
// hata kodları
[Flags]
public enum CodeErreurs { Acces = 1, Ligne = 2, Champ1 = 4, Champ2 = 8, Champ3 = 16 };
// hata kodu
public CodeErreurs Code { get; set; }
// üreticiler
public FileImpotException() {
}
public FileImpotException(string message)
: base(message) {
}
public FileImpotException(string message, Exception e)
: base(message, e) {
}
}
}
Not: FileImpotException sınıfı, yalnızca [dao] katmanı FileImpot sınıfı tarafından uygulanmışsa kullanışlıdır.
6.3.3. [dao] katmanı
![]() |
[dao] katmanının arayüzünü hatırlayalım:
using Entites;
namespace Dao {
public interface IImpotDao {
// vergi dilimleri
TrancheImpot[] TranchesImpot{get;}
}
}
Bu arayüzü iki farklı şekilde uygulayacağız.
İlk olarak, 4.10. paragrafta incelenen HardwiredImpot sınıfı ile:
using System;
using Entites;
namespace Dao {
public class HardwiredImpot : IImpotDao {
// vergi hesaplaması için gerekli veri tabloları
decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };
// vergi dilimleri
public TrancheImpot[] TranchesImpot { get; private set; }
// oluşturucu
public HardwiredImpot() {
// vergi dilimleri tablosunun oluşturulması
TranchesImpot = new TrancheImpot[limites.Length];
// doldurma
for (int i = 0; i < TranchesImpot.Length; i++) {
TranchesImpot[i] = new TrancheImpot { Limite = limites[i], CoeffR = coeffR[i], CoeffN = coeffN[i] };
}
}
}// sınıf
}// ad alanı
- 5. satır: HardwiredImpot sınıfı, IImpotDao arayüzünü uygular
- 12. satır: IImpotDao arayüzünün TranchesImpot özelliğinin uygulanması. Bu özellik otomatik bir özelliktir. IImpotDao arayüzünün TranchesImpot özelliğinin get yöntemini uygular. Ayrıca, 15-22. satırlardaki yapıcı işlevinin vergi dilimleri dizisini başlatabilmesi için set adlı bir yöntem özel (yani sınıf içi) olarak tanımlanmıştır.
IImpotDao arayüzü, 5.8. paragrafta incelenen FileImpot sınıfı tarafından da uygulanacaktır:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Entites;
namespace Dao {
class FileImpot : IImpotDao {
// veri dosyası
public string FileName { get; set; }
// vergi dilimleri
public TrancheImpot[] TranchesImpot { get; private set; }
// yapıcı
public FileImpot(string fileName) {
// dosya adı kaydedilir
FileName = fileName;
// veriler
List<TrancheImpot> listTranchesImpot = new List<TrancheImpot>();
int numLigne = 1;
// istisna
FileImpotException fe = null;
// fileName dosyasının içeriğini satır satır okuma
Regex pattern = new Regex(@"s*:\s*");
// başlangıçta hata yok
FileImpotException.CodeErreurs code = 0;
try {
using (StreamReader input = new StreamReader(FileName)) {
while (!input.EndOfStream && code == 0) {
// geçerli satır
string ligne = input.ReadLine().Trim();
// boş satırlar atlanır
if (ligne == "")
continue;
// satır, aşağıdakilerle ayrılmış üç alana bölünmüştür:
string[] champsLigne = pattern.Split(ligne);
// 3 alan var mı?
if (champsLigne.Length != 3) {
code = FileImpotException.CodeErreurs.Ligne;
}
// 3 alanın dönüştürülmesi
decimal limite = 0, coeffR = 0, coeffN = 0;
if (code == 0) {
if (!Decimal.TryParse(champsLigne[0], out limite))
code = FileImpotException.CodeErreurs.Champ1;
if (!Decimal.TryParse(champsLigne[1], out coeffR))
code |= FileImpotException.CodeErreurs.Champ2;
if (!Decimal.TryParse(champsLigne[2], out coeffN))
code |= FileImpotException.CodeErreurs.Champ3;
;
}
// hata mı?
if (code != 0) {
// hatk not edilir
fe = new FileImpotException(String.Format("Ligne n° {0} incorrecte", numLigne)) { Code = code };
} else {
// yeni vergi dilimini kaydediyoruz
listTranchesImpot.Add(new TrancheImpot() { Limite = limite, CoeffR = coeffR, CoeffN = coeffN });
// sonraki satır
numLigne++;
}
}
}
} catch (Exception e) {
// hata kaydediliyor
fe = new FileImpotException(String.Format("Erreur lors de la lecture du fichier {0}", FileName), e) { Code = FileImpotException.CodeErreurs.Acces };
}
// hatayı bildirmek mi gerekiyor?
if (fe != null) {
// istisna tetiklenir
throw fe;
} else {
// listImpot listesini tranchesImpot tablosuna aktarılır
TranchesImpot = listTranchesImpot.ToArray();
}
}
}
}
- Bu kod, 5.8. paragrafta daha önce incelenmiştir.
- 14. satır: IImpotDao arayüzünün TranchesImpot yöntemi
- 76. satır: sınıf oluşturucusunda, oluşturucunun adını aldığı dosyadan vergi dilimlerinin başlatılması 17. satır.
6.3.4. [metier] katmanı
![]() |
Bu katmanın arayüzünü hatırlayalım:
namespace Metier {
public interface IImpotMetier {
int CalculerImpot(bool marié, int nbEnfants, int salaire);
}
}
Bu arayüzün ImpotMetier uygulaması şu şekildedir:
using Entites;
using Dao;
namespace Metier {
public class ImpotMetier : IImpotMetier {
// [dao] katmanı
private IImpotDao Dao { get; set; }
// vergi dilimleri
private TrancheImpot[] tranchesImpot;
// inşaat firması
public ImpotMetier(IImpotDao dao) {
// kaydetme
Dao = dao;
// vergi dilimleri
tranchesImpot = dao.TranchesImpot;
}
// vergi hesaplaması
public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
// pay sayısının hesaplanması
decimal nbParts;
if (marié)
nbParts = (decimal)nbEnfants / 2 + 2;
else
nbParts = (decimal)nbEnfants / 2 + 1;
if (nbEnfants >= 3)
nbParts += 0.5M;
// vergilendirilebilir gelir ve aile katsayısı hesaplaması
decimal revenu = 0.72M * salaire;
decimal QF = revenu / nbParts;
// vergi hesaplaması
tranchesImpot[tranchesImpot.Length - 1].Limite = QF + 1;
int i = 0;
while (QF > tranchesImpot[i].Limite)
i++;
// sonuç döndürme
return (int)(revenu * tranchesImpot[i].CoeffR - nbParts * tranchesImpot[i].CoeffN);
}//hesapla
}//sınıf
}
- 5. satır: [Metier] sınıfı, [IImpotMetier] arayüzünü uygular.
- 14-19. satırlar: [metier] katmanı, [dao] katmanıyla işbirliği yapmalıdır. Bu nedenle, IImpotDao arayüzünü uygulayan nesneye bir referans içermelidir. Bu nedenle bu referans, yapıcıya parametre olarak aktarılır.
- 16. satır: [dao] katmanına ait referans, 8. satırın özel alanında saklanır
- 18. satır: Bu referans kullanılarak, oluşturucu vergi dilimleri tablosunu ister ve bunun bir referansını 8. satırın özel özelliğinde saklar.
- 22-41. satırlar: IImpotMetier arayüzünün CalculerImpot yönteminin uygulanması. Bu uygulama, oluşturucu tarafından başlatılan vergi dilimleri tablosunu kullanır.
6.3.5. [ui] katmanı
![]() |
Sürüm 2 ve 3'teki kullanıcı etkileşimi sınıfları birbirine çok benziyordu. Sürüm 2'deki sınıf şu şekildeydi:
using System;
namespace Chap2 {
public class Program {
static void Main() {
...
// nesne oluşturma IImpot
IImpot impot = new HardwiredImpot();
// sonsuz döngü
while (true) {
...
}//while
}
}
}
ve sürüm 3'teki sınıf ise şöyleydi:
using System;
namespace Chap3 {
public class Program {
static void Main() {
...
// bir nesne oluşturma IImpot
IImpot impot = null;
try {
// IImpot nesnesinin oluşturulması
impot = new FileImpot("DataImpotInvalide.txt");
} catch (FileImpotException e) {
// hata mesajı görüntüleme
string msg = e.InnerException == null ? null : String.Format(", Exception d'origine : {0}", e.InnerException.Message);
Console.WriteLine("L'erreur suivante s'est produite : [Code={0},Message={1}{2}]", e.Code, e.Message, msg == null ? "" : msg);
// programın durdurulması
Environment.Exit(1);
}
// sonsuz döngü
while (true) {
...
}//while
}
}
}
Değişen tek şey, vergi hesaplamasını sağlayan IImpot türündeki nesnenin örneklenme şeklidir. Bu nesne, burada [métier] katmanımıza karşılık gelir.
[dao] sınıfı ile HardwiredImpot sınıfının kullanıldığı bir uygulamada, diyalog sınıfı şu şekildedir:
using System;
using Metier;
using Dao;
using Entites;
namespace Ui {
public class Dialogue2 {
static void Main() {
...
// katmanlar oluşturuluyor [metier et dao]
IImpotMetier metier = new ImpotMetier(new HardwiredImpot());
// sonsuz döngü
while (true) {
...
// parametreler doğru - vergi hesaplanıyor
Console.WriteLine("Impot=" + metier.CalculerImpot(marié == "o", nbEnfants, salaire) + " euros");
// bir sonraki vergi mükellefi
}//while
}
}
}
- 12. satır: [dao] ve [metier] katmanlarının örneklenmesi. [metier] katmanının [dao] katmanına ihtiyaç duyduğunu hatırlatırız.
- 18. satır: Vergiyi hesaplamak için [metier] katmanının kullanılması
[dao] sınıfı ile FileImpot sınıfının birlikte kullanıldığı bir uygulamada, diyalog sınıfı şu şekildedir:
using System;
using Metier;
using Dao;
using Entites;
namespace Ui {
public class Dialogue {
static void Main() {
...
// katmanlar oluşturuluyor [metier et dao]
IImpotMetier metier = null;
try {
// katman oluşturuluyor [metier]
metier = new ImpotMetier(new FileImpot("DataImpot.txt"));
} catch (FileImpotException e) {
// hata mesajı görüntüleniyor
string msg = e.InnerException == null ? null : String.Format(", Exception d'origine : {0}", e.InnerException.Message);
Console.WriteLine("L'erreur suivante s'est produite : [Code={0},Message={1}{2}]", e.Code, e.Message, msg == null ? "" : msg);
// program durduruldu
Environment.Exit(1);
}
// sonsuz döngü
while (true) {
...
// parametreler doğru - vergi hesaplanıyor
Console.WriteLine("Impot=" + metier.CalculerImpot(marié == "o", nbEnfants, salaire) + " euros");
// sonraki vergi mükellefi
}//while
}
}
}
- satır 11-21: [dao] ve [metier] katmanlarının örneklenmesi. [dao] katmanının örneklenmesi bir istisna oluşturabileceğinden, bu istisna yönetilir
- 26. satır: önceki sürümde olduğu gibi, vergiyi hesaplamak için [metier] katmanının kullanılması
6.3.6. Sonuç
Katmanlı mimari ve arayüzlerin kullanımı, uygulamamıza belirli bir esneklik kazandırmıştır. Bu esneklik, özellikle [ui] katmanının [dao] ve [métier] katmanlarını örneklendirme biçiminde ortaya çıkmaktadır:
// katmanlar oluşturuluyor [metier et dao]
IImpotMetier metier = new ImpotMetier(new HardwiredImpot());
bir durumda ve:
// katmanlar oluşturuluyor [metier et dao]
IImpotMetier metier = null;
try {
// [metier] katmanı oluşturuluyor
metier = new ImpotMetier(new FileImpot("DataImpot.txt"));
} catch (FileImpotException e) {
// hata mesajı görüntüleniyor
string msg = e.InnerException == null ? null : String.Format(", Exception d'origine : {0}", e.InnerException.Message);
Console.WriteLine("L'erreur suivante s'est produite : [Code={0},Message={1}{2}]", e.Code, e.Message, msg == null ? "" : msg);
// program durduruluyor
Environment.Exit(1);
}
diğerinde. Durum 2'deki istisna yönetimi hariç tutulursa, [dao] ve [metier] katmanlarının örneklenmesi her iki uygulamada da benzerdir. [dao] ve [metier] katmanları oluşturulduktan sonra, [ui] katmanının kodu her iki durumda da aynıdır. Bunun nedeni, [métier] katmanının, kendi uygulama sınıfı üzerinden değil, IImpotMetier arayüzü aracılığıyla işlenmesidir. Uygulamadaki [metier] katmanını veya [dao] katmanını arayüzlerini değiştirmeden değiştirmek, her zaman sadece [ui] katmanındaki yukarıdaki satırları değiştirmekle sonuçlanacaktır.
Bu mimarinin sağladığı esnekliğin bir başka örneği de [métier] katmanının uygulamasıdır:
using Entites;
using Dao;
namespace Metier {
public class ImpotMetier : IImpotMetier {
// katman [dao]
private IImpotDao Dao { get; set; }
// vergi dilimleri
private TrancheImpot[] tranchesImpot;
// üretici
public ImpotMetier(IImpotDao dao) {
// kaydetme
Dao = dao;
// vergi dilimleri
tranchesImpot = dao.TranchesImpot;
}
// vergi hesaplaması
public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
...
}//hesaplama
}//sınıf
}
- satırda, [métier] katmanının, [dao] katmanının arayüzüne yapılan bir referans temelinde oluşturulduğu görülmektedir. Dolayısıyla, [dao] katmanının arayüzüne yapılan bir referans, [métier] katmanı üzerinde hiçbir etki yaratmaz. Bu nedenle, [métier] katmanının tek uygulamamız, [dao] katmanının iki farklı uygulamasıyla herhangi bir değişiklik yapılmasına gerek kalmadan sorunsuz bir şekilde çalışabilmiştir.
6.4. Örnek uygulama - sürüm 5
![]() |
Bu yeni sürüm, bir önceki sürümü temel alarak aşağıdaki değişiklikleri içermektedir:
- [métier] ve [dao] katmanları, her biri bir DLL içinde kapsüllenmiş ve NUnit birim test çerçevesi ile test edilmiştir.
- Katmanların entegrasyonu Spring çerçeve ile sağlanmaktadır.
Büyük projelerde, aynı proje üzerinde birden fazla geliştirici çalışır. Katmanlı mimariler bu çalışma şeklini kolaylaştırır: katmanlar birbirleriyle iyi tanımlanmış arayüzler aracılığıyla iletişim kurduğundan, bir katman üzerinde çalışan bir geliştirici, diğer katmanlardaki diğer geliştiricilerin çalışmalarıyla ilgilenmek zorunda kalmaz. Herkesin sadece arayüzlere uyması yeterlidir.
Yukarıdaki örnekte, [métier] katmanının geliştiricisi, kendi katmanını test ederken [dao] katmanının bir uygulamasına ihtiyaç duyacaktır. Bu katman tamamlanana kadar, [dao] katmanının, IImpotDao arayüzüne uygun olduğu sürece sahte bir uygulamasını kullanabilir. Bu da katmanlı mimarinin bir avantajıdır: [dao] katmanındaki bir gecikme, [métier] katmanının test edilmesini engellemez. [dao] katmanının sahte uygulaması, gerçek [dao] katmanına kıyasla genellikle daha kolay uygulanabilmesi avantajına da sahiptir; zira gerçek katman, bir SGBD'in başlatılmasını, ağ bağlantılarının kurulmasını vb. gerektirebilir.
[dao] katmanı tamamlanıp test edildiğinde, [métier] katmanının geliştiricilerine kaynak kodu yerine bir DLL dosyası olarak sağlanacaktır. Sonuç olarak, uygulama genellikle bir .exe dosyası ([ui] katmanına ait olan) ve .dll sınıf kütüphaneleri (diğer katmanlar) şeklinde teslim edilir.
6.4.1. NUnit
Şimdiye kadar çeşitli uygulamalarımız için yapılan testler görsel kontrole dayanıyordu. Ekranda beklenen sonucun elde edilip edilmediğini kontrol ediyorduk. Yapılması gereken çok sayıda test olduğunda bu yöntem kullanılamaz. İnsanlar yorgunluğa maruz kalır ve testleri kontrol etme yetenekleri gün içinde azalır. Bu nedenle testler otomatikleştirilmeli ve hiçbir insan müdahalesine gerek kalmayacak şekilde tasarlanmalıdır.
Bir uygulama zamanla gelişir. Her gelişmede, uygulamanın “gerilemediğini”, c.a.d, yani ilk yazım aşamasında yapılan işlevsellik testlerini hâlâ geçebildiğini doğrulamak gerekir. Bu testlere “gerileme testi” denir. Biraz büyük bir uygulama, yüzlerce test gerektirebilir. Uygulamanın her sınıfındaki her yöntem test edilir. Buna birim testleri denir. Bu testler otomatikleştirilmemişse çok sayıda geliştiriciyi meşgul edebilir.
Testleri otomatikleştirmek için çeşitli araçlar geliştirilmiştir. Bunlardan biri NUnit adını taşır. Bu araç, [http://www.nunit.org] sitesinde mevcuttur:
![]() | ![]() |
Bu belge için yukarıdaki 2.4.6 sürümü kullanılmıştır (Mart 2008). Kurulum sırasında masaüstüne bir [1] simgesi yerleştirilir:
![]() |
[1] simgesine çift tıklamak, NUnit [2] grafik kullanıcı arayüzünü başlatır. Bu arayüz, testlerin otomasyonuna hiçbir şekilde yardımcı olmaz; çünkü yine görsel bir kontrole geri dönülür: test uzmanı, grafik arayüzde görüntülenen test sonuçlarını kontrol eder. Bununla birlikte, testler toplu iş araçlarıyla da çalıştırılabilir ve sonuçları XML dosyalarına kaydedilebilir. Geliştirme ekipleri bu yöntemi kullanır: testler gece başlatılır ve geliştiriciler sonuçları ertesi sabah alır.
Bir örnek üzerinden NUnit testlerinin çalışma prensibini inceleyelim. Öncelikle, Console Application türünde yeni bir C# projesi oluşturalım:
![]() |
[1]'te, projenin références'lerini görüyoruz. Bu referanslar, proje tarafından kullanılan sınıfları ve arayüzleri içeren DLL'lerdir. [1]'te gösterilenler, her yeni C# projesine varsayılan olarak eklenir. NUnit çerçevesinin sınıflarını ve arayüzlerini kullanabilmek için, projeye [2] adlı yeni bir referans eklememiz gerekir.
![]() |
Yukarıdaki .NET sekmesinde, [nunit.framework] bileşenini seçiyoruz. Yukarıdaki [nunit.*] bileşenleri, .NET ortamında varsayılan olarak bulunan bileşenler değildir. Bu bileşenler, daha önce yapılan NUnit çerçeve kurulumuyla buraya eklenmiştir. Referansın eklenmesi onaylandıktan sonra, bu referans proje referans listesinde [4] olarak görünür.
Uygulama oluşturulmadan önce, projenin [bin/Release] klasörü boştur. Oluşturma işleminden sonra (F6), [bin/Release] klasörünün artık boş olmadığı görülebilir:
![]() |
[6]'te, DLL ve [nunit.framework.dll]'in mevcut olduğu görülmektedir. Bu DLL dosyasının çalıştırma klasörüne kopyalanmasına neden olan, [nunit.framework] referansının eklenmesidir. Bu klasör, proje tarafından referans verilen sınıfları ve arayüzleri bulmak için CLR (Common Language Runtime) .NET tarafından taranacak klasörlerden biridir.
İlk test sınıfımız olan NUnit'i oluşturalım. Bunun için, varsayılan olarak oluşturulan [Program.cs] sınıfını siliyoruz ve ardından projeye yeni bir [Nunit1.cs] sınıfı ekliyoruz. Ayrıca, gereksiz [7] referanslarını da siliyoruz.
NUnit1 test sınıfı şu şekilde olacaktır:
using System;
using NUnit.Framework;
namespace NUnit {
[TestFixture]
public class NUnit1 {
public NUnit1() {
Console.WriteLine("constructeur");
}
[SetUp]
public void avant() {
Console.WriteLine("Setup");
}
[TearDown]
public void après() {
Console.WriteLine("TearDown");
}
[Test]
public void t1() {
Console.WriteLine("test1");
Assert.AreEqual(1, 1);
}
[Test]
public void t2() {
Console.WriteLine("test2");
Assert.AreEqual(1, 2, "1 n'est pas égal à 2");
}
}
}
- 6. satır: NUnit1 sınıfı public olmalıdır. Public anahtar sözcüğü, Visual Studio tarafından varsayılan olarak oluşturulmaz. Bunu eklemek gerekir.
- 5. satır: [TestFixture] özniteliği, bir NUnit özniteliğidir. Bu öznitelik, sınıfın bir test sınıfı olduğunu belirtir.
- 7-9. satırlar: Oluşturucu. Burada yalnızca ekrana bir mesaj yazmak için kullanılmaktadır. Ne zaman çalıştırıldığını görmek istiyoruz.
- 10. satır: [SetUp] özniteliği, her birim testinden önce çalıştırılan bir yöntemi tanımlar.
- 14. satır: [TearDown] özniteliği, her birim testinden sonra çalıştırılan bir yöntemi tanımlar.
- 18. satır: [Test] özniteliği, bir test yöntemini tanımlar. [Test] özniteliği ile işaretlenmiş her yöntem için, [SetUp] özniteliği ile işaretlenmiş yöntem testten önce, [TearDown] özniteliği ile işaretlenmiş yöntem ise testten sonra çalıştırılacaktır.
- 21. satır: NUnit çerçeve tarafından tanımlanan [Assert.*] yöntemlerinden biri. Aşağıdaki [Assert] yöntemleri bulunur:
- [Assert.AreEqual(expression1, expression2)]: İki ifadenin değerlerinin eşit olup olmadığını kontrol eder. Birçok ifade türü kabul edilir (int, string, float, double, decimal, ...). İki ifade eşit değilse, bir istisna atılır.
- [Assert.AreEqual(réel1, réel2, delta)]: iki gerçek sayının delta kadar farkla eşit olup olmadığını kontrol eder; c.a.d abs(gerçek1-gerçek2)<=delta. Örneğin, iki değerin 10-6 kadar farkla eşit olup olmadığını kontrol etmek için [Assert.AreEqual(réel1, réel2, 1E-6)] yazılabilir.
- [Assert.AreEqual(expression1, expression2, message)] ve [Assert.AreEqual(réel1, réel2, delta, message)], [Assert.AreEqual] yöntemi başarısız olduğunda atılan istisnaya eşleştirilecek hata mesajını belirlemeye olanak tanıyan varyantlardır.
- [Assert.IsNotNull(object)] ve [Assert.IsNotNull(object, message)]: object'in null'a eşit olmadığını doğrular.
- [Assert.IsNull(object)] ve [Assert.IsNull(object, message)]: object'in null olup olmadığını kontrol eder.
- [Assert.IsTrue(expression)] ve [Assert.IsTrue(expression, message)]: ifade'nin true değerine eşit olup olmadığını kontrol eder.
- [Assert.IsFalse(expression)] ve [Assert.IsFalse(expression, message)]: ifade'nin false değerine eşit olup olmadığını kontrol eder.
- [Assert.AreSame(object1, object2)] ve [Assert.AreSame(object1, object2, message)]: object1 ve object2 referanslarının aynı nesneyi işaret edip etmediğini kontrol eder.
- [Assert.AreNotSame(object1, object2)] ve [Assert.AreNotSame(object1, object2, message)]: object1 ve object2 referanslarının aynı nesneyi işaret etmediğini doğrular.
- 21. satır: doğrulama başarılı olmalıdır
- 26. satır: doğrulama başarısız olmalıdır
Projeyi, derleme sonucunda bir .exe dosyası yerine bir DLL dosyası üretecek şekilde yapılandıralım:
![]() |
- [1]'te: proje özellikleri
- [2, 3]'e: proje türü olarak [Class Library]'i (Sınıf Kütüphanesi) seçelim
- [4]'te: proje derleme işlemi, DLL (derleme) adlı bir dosya oluşturacak; bu dosyanın adı [Nunit.dll] olacaktır
Şimdi, test sınıfını çalıştırmak için NUnit'i kullanalım:
![]() |
- [1]'te: NUnit adlı bir proje açılıyor
- [2, 3]'te: C# projesinin oluşturulmasıyla üretilen DLL bin/Release/Nunit.dll dosyası yükleniyor
- [4]'e: DLL yüklendi
- [5]'e: test ağacı
- [6]: testler yürütülüyor
![]() |
- [7]'te: sonuçlar: t1 başarılı, t2 başarısız
- [8]'te: Kırmızı bir çubuk, test sınıfının genel olarak başarısız olduğunu gösterir
- [9]: başarısız testle ilgili hata mesajı
![]() |
- [11]'te: sonuç penceresinin farklı sekmeleri
- [12]: [Console.Out] sekmesi. Burada şunlar görülmektedir:
- yapıcı yalnızca bir kez çalıştırılmıştır
- [SetUp] yöntemi, her iki testten önce de çalıştırılmıştır
- [TearDown] yöntemi, her iki testten sonra da çalıştırılmıştır
Test edilecek yöntemleri belirlemek mümkündür:
![]() |
- [1]'te: her testin yanında bir onay kutusu görüntülenmesi istenir
- [2]'te: çalıştırılacak testlerin işaretlenmesi istenir
- [3]'te: testler çalıştırılır
Hataları düzeltmek için C# projesini düzeltip yeniden derlemek yeterlidir. NUnit, test ettiği DLL dosyasının değiştiğini algılar ve yenisini otomatik olarak yükler. Ardından testleri yeniden çalıştırmak yeterlidir.
Şu yeni test sınıfını ele alalım:
using System;
using NUnit.Framework;
namespace NUnit {
[TestFixture]
public class NUnit2 : AssertionHelper {
public NUnit2() {
Console.WriteLine("constructeur");
}
[SetUp]
public void avant() {
Console.WriteLine("Setup");
}
[TearDown]
public void après() {
Console.WriteLine("TearDown");
}
[Test]
public void t1() {
Console.WriteLine("test1");
Expect(1, EqualTo(1));
}
[Test]
public void t2() {
Console.WriteLine("test2");
Expect(1, EqualTo(2), "1 n'est pas égal à 2");
}
}
}
NUnit'in 2.4 sürümünden itibaren, 21. ve 26. satırlarda görüldüğü gibi yeni bir sözdizimi kullanılabilir hale gelmiştir. Bunun için test sınıfının AssertionHelper sınıfından türetilmesi gerekir (6. satır).
Eski ve yeni sözdizimi arasındaki (kapsamlı olmayan) karşılık şu şekildedir:
NUnit2 sınıfına aşağıdaki testi ekleyelim:
[Test]
public void t3() {
bool vrai = true, faux = false;
Expect(vrai, True);
Expect(faux, False);
Object obj1 = new Object(), obj2 = null, obj3=obj1;
Expect(obj1, Not.Null);
Expect(obj2, Null);
Expect(obj3, SameAs(obj1));
double d1 = 4.1, d2 = 6.4, d3 = d1;
Expect(d1, EqualTo(d3).Within(1e-6));
Expect(d1, Not.EqualTo(d2));
}
C# projesinden yeni DLL dosyasını (F6) oluşturursak, NUnit projesi şu hale gelir:
![]() |
- [1]'te: yeni test sınıfı [NUnit2] otomatik olarak algılandı
- [2]'te: NUnit2'in t3 testi yürütülüyor
- [3]: t3 testi başarıyla tamamlandı
NUnit hakkında daha fazla bilgi edinmek için NUnit yardım belgesine bakabilirsiniz:
![]() | ![]() |
6.4.2. Visual Studio çözümü
![]() |
Aşağıdaki Visual Studio çözümünü adım adım oluşturacağız:
![]() |
- [1]: ImpotsV5 çözümü, uygulamanın üç katmanının her biri için birer tane olmak üzere üç projeden oluşur
- [2]'te: [dao] katmanına ait [dao] projesi
- [3]'te: [metier] katmanına ait [metier] projesi
- [4]'e: [ui] katmanındaki [ui] projesi
ImpotsV5 çözümü şu şekilde oluşturulabilir:
1 ![]() | 234 ![]() | 5 ![]() |
- [1]'te: yeni bir proje oluşturun
- [2]: bir konsol uygulaması seçin
- [3]: [dao] projesini çağırma
- [4]: projeyi oluştur
- [5]: proje oluşturulduktan sonra kaydetme
![]() |
- [6]'te: proje için [dao] adını koruyun
- [7]: Projeyi ve çözümünü kaydetmek için bir klasör belirleyin
- [8]'te: Çözüme bir ad verin
- [9]'te: çözümün kendi klasörüne sahip olması gerektiğini belirtin
- [10]'te: Projeyi ve çözümünü kaydedin
- [11]: [dao] projesini ImpotsV5 çözümüne
![]() |
- [12]'te: ImpotsV5 çözüm klasörü. Bu klasör, [dao] klasörünün içindeki [dao] klasörünü içerir.
- [13] içinde: [dao] klasörünün içeriği
- [14]'e: ImpotsV5 çözümüne yeni bir proje eklenir
![]() |
- [15]'te: yeni projenin adı [metier]
- [16]: iki projeyi içeren çözüm
- [17]: 3. proje [ui] eklendikten sonraki çözüm
![]() |
- [18]'te: çözüm klasörü ve üç projenin klasörleri
- (Ctrl+F5) ile bir çözümü çalıştırdığımızda, aktif olan proje çalıştırılır. Çözümü derlerken (F6) de durum aynıdır. Etkin projenin adı, çözüm içinde [19] olarak kalın harflerle gösterilir.
- [20]'te: çözümün etkin projesini
- [21]: [metier] projesi artık çözümün aktif projesidir
6.4.3. [dao] katmanı
![]() |
![]() |
Proje referansları (projede [1]'e bakınız)
Testler için gerekli olan [nunit.framework] referansı ekleniyor [NUnit]
Varlıklar (bkz. projedeki [2])
[TrancheImpot] sınıfı önceki sürümlerinkidir. Önceki sürümdeki [FileImpotException] sınıfı, daha genel hale getirmek ve belirli bir [dao] katmanına bağlanmaması için [ImpotException] olarak yeniden adlandırılır:
using System;
namespace Entites {
public class ImpotException : Exception {
// hata kodu
public int Code { get; set; }
// üreticiler
public ImpotException() {
}
public ImpotException(string message)
: base(message) {
}
public ImpotException(string message, Exception e)
: base(message, e) {
}
}
}
[dao] katmanı (projede [3]'e bakınız)
[IImpotDao] arayüzü önceki sürümün arayüzüdür. Aynı durum [HardwiredImpot] sınıfı için de geçerlidir. [FileImpot] sınıfı, [FileImpotException] istisnasının [ImpotException] olarak değiştirilmesini dikkate alacak şekilde güncellenmiştir:
...
namespace Dao {
public class FileImpot : IImpotDao {
// hata kodları
[Flags]
public enum CodeErreurs { Acces = 1, Ligne = 2, Champ1 = 4, Champ2 = 8, Champ3 = 16 };
...
// üretici
public FileImpot(string fileName) {
// dosya adı kaydediliyor
FileName = fileName;
...
// başlangıçta hata yok
CodeErreurs code = 0;
try {
using (StreamReader input = new StreamReader(FileName)) {
while (!input.EndOfStream && code == 0) {
...
// hata mı?
if (code != 0) {
// hata kaydediliyor
fe = new ImpotException(String.Format("Ligne n° {0} incorrecte", numLigne)) { Code = (int)code };
} else {
...
}
}
}
} catch (Exception e) {
// hatkayı not ediyoruz
fe = new ImpotException(String.Format("Erreur lors de la lecture du fichier {0}", FileName), e) { Code = (int)CodeErreurs.Acces };
}
// bildirilmesi gereken bir hata mı?
...
}
}
}
- 8. satır: Daha önce [FileImpotException] sınıfında bulunan hata kodları, [FileImpot] sınıfına taşınmıştır. Bunlar, aslında [IImpotDao] arayüzünün bu uygulamasına özgü hata kodlarıdır.
- 26. ve 34. satırlar: Bir hatayı kapsüllemek için artık [FileImpotException] sınıfı değil, [ImpotException] sınıfı kullanılmaktadır.
[Test1] testi (projede [4]'e bakınız)
[Test1] sınıfı, yalnızca vergi dilimlerini ekranda görüntüler:
using System;
using Dao;
using Entites;
namespace Tests {
class Test1 {
static void Main() {
// [dao] katmanı oluşturuluyor
IImpotDao dao = null;
try {
// [dao] katmanı oluşturuluyor
dao = new FileImpot("DataImpot.txt");
} catch (ImpotException e) {
// hata görüntüleniyor
string msg = e.InnerException == null ? null : String.Format(", Exception d'origine : {0}", e.InnerException.Message);
Console.WriteLine("L'erreur suivante s'est produite : [Code={0},Message={1}{2}]", e.Code, e.Message, msg == null ? "" : msg);
// program durduruldu
Environment.Exit(1);
}
// vergi dilimlerinin görüntülenmesi
TrancheImpot[] tranchesImpot = dao.TranchesImpot;
foreach (TrancheImpot t in tranchesImpot) {
Console.WriteLine("{0}:{1}:{2}", t.Limite, t.CoeffR, t.CoeffN);
}
}
}
}
- 13. satır: [dao] katmanı, [FileImpot] sınıfı tarafından uygulanır
- satır 14: Ortaya çıkabilecek [ImpotException] türündeki istisna yönetilir.
Testler için gerekli olan [DataImpot.txt] dosyası, projenin çalışma klasörüne otomatik olarak kopyalanır (projedeki [5] dosyasına bakınız). [dao] projesinde, [Main] yöntemini içeren birkaç sınıf bulunacaktır. Bu durumda, kullanıcı Ctrl-F5 tuşlarıyla projenin çalıştırılmasını istediğinde, çalıştırılacak sınıfın açıkça belirtilmesi gerekir:
![]() |
- [1]'te: projenin özelliklerine erişin
- [2]: bunun bir konsol uygulaması olduğunu belirtmek
- [3]: çalıştırılacak sınıfı belirtmek
Yukarıdaki [Test1] sınıfının çalıştırılması şu sonuçları verir:
4962:0:0
8382:0,068:291,09
14753:0,191:1322,92
23888:0,283:2668,39
38868:0,374:4846,98
47932:0,426:6883,66
0:0,481:9505,54
[Test2] testi (projede [4]'e bakınız)
[Test2] sınıfı, [dao] katmanını [HardwiredImpot] sınıfıyla uygulayarak [Test1] sınıfıyla aynı işlevi görür. [Test1] sınıfının 13. satırı aşağıdakiyle değiştirilir:
dao = new HardwiredImpot();
Proje, artık [Test2] sınıfını çalıştıracak şekilde değiştirilmiştir:
![]() |
Ekran çıktıları öncekiyle aynıdır.
NUnit testi [NUnit1] (projede [4]'e bakınız)
[NUnit1] birim testi şu şekildedir:
using System;
using Dao;
using Entites;
using NUnit.Framework;
namespace Tests {
[TestFixture]
public class NUnit1 : AssertionHelper{
// [dao] katmanı test edilecek
private IImpotDao dao;
// oluşturucu
public NUnit1() {
// [dao] katmanının başlatılması
dao = new FileImpot("DataImpot.txt");
}
// test
[Test]
public void ShowTranchesImpot(){
// vergi dilimleri görüntüleniyor
TrancheImpot[] tranchesImpot = dao.TranchesImpot;
foreach (TrancheImpot t in tranchesImpot) {
Console.WriteLine("{0}:{1}:{2}", t.Limite, t.CoeffR, t.CoeffN);
}
// bazı testler
Expect(tranchesImpot.Length,EqualTo(7));
Expect(tranchesImpot[2].Limite,EqualTo(14753));
Expect(tranchesImpot[2].CoeffR, EqualTo(0.191));
Expect(tranchesImpot[2].CoeffN, EqualTo(1322.92));
}
}
}
- Test sınıfı, [AssertionHelper] sınıfından türetilmiştir; bu sayede Expect statik yöntemi kullanılabilmektedir (27-30. satırlar).
- 10. satır: [dao] katmanına bir referans
- 13-16. satırlar: Oluşturucu, [FileImpot] sınıfı ile [dao] katmanını örneklendirir
- 19-20. satırlar: test yöntemi
- 22. satır: [dao] katmanından vergi dilimleri tablosu alınır
- 23-25. satırlar: Bunlar daha önce olduğu gibi görüntülenir. Bu görüntüleme, gerçek bir birim testinde yer almamalıdır. Burada bu görüntüleme, öğretim amaçlıdır.
- 27. satır: 7 vergi dilimi olduğunu doğruluyoruz
- 28-30. satırlar: 2 numaralı vergi diliminin değerleri kontrol edilir
Bu birim testini çalıştırmak için projenin [Class Library] türü olması gerekir:
![]() |
- [1]'e: projenin türü değiştirilmiştir
- [2]'e: Oluşturulan DLL, [ImpotsV5-dao.dll] olarak adlandırılacaktır
- [3]'te: projenin oluşturulmasından sonra (F6), [dao/bin/Release] klasörü DLL ve [ImpotsV5-dao.dll] dosyalarını içerir
Ardından DLL ve [ImpotsV5-dao.dll] dosyaları NUnit çerçeve yapısına yüklenir ve çalıştırılır:
![]() |
- [1]'te: testler başarıyla tamamlandı. Artık [dao] katmanını çalışır durumda kabul ediyoruz. DLL dosyası, test sınıfları da dahil olmak üzere projenin tüm sınıflarını içerir. Bu sınıflar gereksizdir. Test sınıflarını hariç tutmak için DLL'i yeniden oluşturuyoruz.
- [2]'e: [tests] klasörü projeden çıkarılır
- [3]: yeni proje. Bu proje, yeni bir DLL oluşturmak üzere F6 tarafından yeniden oluşturulur.
6.4.4. [metier] katmanı
![]() |
![]() |
- [1]'e dönüştürüldüğünde, [metier] projesi çözümün aktif projesi haline geldi
- [2]'te: proje referansları
- [3]'te: [metier] katmanı
- [4]: test sınıfları
- [5]: vergi dilimlerinin bulunduğu [DataImpot.txt] dosyası, [6] proje yürütme klasörüne otomatik olarak kopyalanacak şekilde yapılandırılmıştır [7]
Proje referansları (bkz. [2], proje içinde)
[dao] projesinde olduğu gibi, [NUnit] testleri için gerekli olan [nunit.framework] referansı eklenir. [metier] katmanı, [dao] katmanına ihtiyaç duyar. Bu nedenle, bu katmanın DLL katmanına bir referans vermesi gerekir. İşlem şu şekilde yapılır:
![]() |
- [1]'te: [metier] projesinin referanslarına yeni bir referans eklenir
- [2]'te: [Browse] sekmesini seçin
- [3]'te: [dao/bin/Release] klasörü seçilir
- [4]'te: [dao] projesinde oluşturulan DLL ve [ImpotsV5-dao.dll]'i seçin
- [5]: yeni referans
[metier] katmanı (projede [3]'e bakınız)
[IImpotMetier] arayüzü önceki sürümün arayüzüdür. Aynı durum [ImpotMetier] sınıfı için de geçerlidir.
[Test1] testi (projede [4]'e bakınız)
[Test1] sınıfı sadece birkaç maaş hesaplaması yapmaktadır:
using System;
using Dao;
using Entites;
using Metier;
namespace Tests {
class Test1 {
static void Main() {
// [metier] katmanı oluşturuluyor
IImpotMetier metier = null;
try {
// [metier] katmanı oluşturuluyor
metier = new ImpotMetier(new FileImpot("DataImpot.txt"));
} catch (ImpotException e) {
// hata mesajı görüntüleniyor
string msg = e.InnerException == null ? null : String.Format(", Exception d'origine : {0}", e.InnerException.Message);
Console.WriteLine("L'erreur suivante s'est produite : [Code={0},Message={1}{2}]", e.Code, e.Message, msg == null ? "" : msg);
// program durduruldu
Environment.Exit(1);
}
// bazı vergiler hesaplanıyor
Console.WriteLine(String.Format("Impot(true,2,60000)={0} euros", metier.CalculerImpot(true, 2, 60000)));
Console.WriteLine(String.Format("Impot(false,3,60000)={0} euros", metier.CalculerImpot(false, 3, 60000)));
Console.WriteLine(String.Format("Impot(false,3,60000)={0} euros", metier.CalculerImpot(false, 3, 6000)));
Console.WriteLine(String.Format("Impot(false,3,60000)={0} euros", metier.CalculerImpot(false, 3, 600000)));
}
}
}
- 14. satır: [metier] ve [dao] katmanlarının oluşturulması. [dao] katmanı, [FileImpot] sınıfı ile uygulanır
- 12-21. satırlar: [ImpotException] türündeki olası bir istisnanın yönetimi
- 23-26. satırlar: [IImpotMetier] arayüzünün tek yöntemi olan CalculerImpot'in tekrar tekrar çağrılması.
[metier] projesi şu şekilde yapılandırılmıştır:
![]() |
- [1]: proje, konsol uygulaması türündedir
- [2]: Yürütülen sınıf, [Test1] sınıfıdır
- [3]: Projenin derlenmesi sonucunda [ImpotsV5-metier.exe] yürütülebilir dosyası oluşturulacaktır
Projenin çalıştırılması şu sonuçları verir:
[NUnit1] testi (projede bkz. [4])
[NUnit1] birim test sınıfı, önceki dört hesaplamayı alır ve sonuçlarını doğrular:
using Dao;
using Metier;
using NUnit.Framework;
namespace Tests {
[TestFixture]
public class NUnit1:AssertionHelper {
// [metier] katmanı test edilecek
private IImpotMetier metier;
// üretici
public NUnit1() {
// [metier] katmanının başlatılması
metier = new ImpotMetier(new FileImpot("DataImpot.txt"));
}
// test
[Test]
public void CalculsImpot(){
// vergi dilimlerini görüntüleme
Expect(metier.CalculerImpot(true, 2, 60000), EqualTo(4282));
Expect(metier.CalculerImpot(false, 3, 60000), EqualTo(4282));
Expect(metier.CalculerImpot(false, 3, 6000), EqualTo(0));
Expect(metier.CalculerImpot(false, 3, 600000), EqualTo(179275));
}
}
}
- 14. satır: [metier] ve [dao] katmanlarının oluşturulması. [dao] katmanı, [FileImpot] sınıfı ile uygulanır
- 21-24. satırlar: [IImpotMetier] arayüzünün tek yöntemi olan CalculerImpot'in sonuçların doğrulanmasıyla birlikte tekrar tekrar çağrılması.
[metier] projesi artık şu şekilde yapılandırılmıştır:
![]() |
- [1]: proje "sınıf kütüphanesi" türündedir
- [2]: Projenin oluşturulması sonucunda DLL ve [ImpotsV5-metier.dll] dosyaları üretilecektir
Proje oluşturuldu (F6). Ardından oluşturulan DLL, [ImpotsV5-metier.dll] ve dosyaları NUnit dosyasına yüklenip test edildi:
![]() |
Yukarıda, testler başarıyla tamamlanmıştır. Artık [metier] katmanını çalışır durumda kabul ediyoruz. DLL dosyası, test sınıfları da dahil olmak üzere projenin tüm sınıflarını içerir. Bu sınıflar gereksizdir. Test sınıflarını hariç tutmak için DLL'i yeniden oluşturuyoruz.
![]() |
- [1]'e: [tests] klasörü projeden hariç tutulur
- [2]: yeni proje. Bu proje, yeni bir DLL oluşturmak üzere F6 tarafından yeniden oluşturulur.
6.4.5. [ui] katmanı
![]() |
![]() |
- [1]'e dönüştürüldüğünde, [ui] projesi çözümün aktif projesi haline geldi
- [2]'te: proje referansları
- [3]'te: [ui] katmanı
- [4]'te: vergi dilimlerine ait [DataImpot.txt] dosyası, [5]'te [6] projesinin yürütme klasörüne otomatik olarak kopyalanacak şekilde yapılandırılmıştır
Proje referansları (bkz. projedeki [2])
[ui] katmanı, vergi hesaplamalarını gerçekleştirebilmek için [metier] ve [dao] katmanlarına ihtiyaç duyar. Bu nedenle, bu iki katmanın DLL referanslarına ihtiyaç duyar. [metier] katmanı için gösterildiği gibi işlem yapılır
Ana sınıf [Dialogue.cs] (projede [3]'e bakınız)
[Dialogue.cs] sınıfı, önceki sürümün sınıfıdır.
Testler
[ui] projesi şu şekilde yapılandırılmıştır:
![]() |
- [1]: proje "konsol uygulaması" türündedir
- [2]: Projenin derlenmesi sonucunda [ImpotsV5-ui.exe] yürütülebilir dosyası oluşturulacaktır
- [3]: çalıştırılacak sınıftır
Bir çalıştırma örneği (Ctrl+F5) şöyledir:
Paramètres du calcul de l'Impot au format : Marié (o/n) NbEnfants Salaire ou rien pour arrêter :o 2 60000
Impot=4282 euros
6.4.6. [Spring] katmanı
[Dialogue.cs] içindeki, [dao] ve [metier] katmanlarını oluşturan koda geri dönelim:
// katmanlar oluşturuluyor [metier et dao]
IImpotMetier metier = null;
try {
// katman oluşturuluyor [metier]
metier = new ImpotMetier(new FileImpot("DataImpot.txt"));
} catch (ImpotException e) {
// hata görüntüleniyor
...
// program durduruluyor
Environment.Exit(1);
}
- satır, her iki katmanın uygulama sınıflarını açıkça adlandırarak [dao] ve [metier] katmanlarını oluşturur: FileImpot katmanı için [dao], ImpotMetier katmanı için [metier]. Katmanlardan birinin uygulaması yeni bir sınıfla yapılırsa, 5. satır değiştirilecektir. Örneğin:
metier = new ImpotMetier(new HardwiredImpot());
Bu değişiklik dışında, her katman bir arayüz aracılığıyla bir sonraki katmanla iletişim kurduğu için uygulamada hiçbir değişiklik olmayacaktır. Arayüz değişmediği sürece, katmanlar arasındaki iletişim de değişmez. Spring çerçeve, farklı katmanları uygulayan sınıfların adlarını bir yapılandırma dosyasına taşıyabilmemizi sağlayarak katman bağımsızlığını biraz daha ileriye götürmemize olanak tanır. Böylece bir katmanın uygulamasını değiştirmek, bir yapılandırma dosyasını değiştirmekle eşdeğer hale gelir. Uygulamanın kodunda hiçbir etki olmaz.
![]() |
Yukarıda, [ui] katmanı, Spring’den [0]’i[dao], [1] ve [metier], [2] katmanlarını oluşturmasını isteyecektir. [ui] katmanı daha sonra Spring'den [3]'e, [metier] katmanına ilişkin bir referans isteyecektir:
// katmanlar oluşturuluyor [metier et dao]
IImpotMetier metier = null;
try {
// Spring bağlamı
IApplicationContext ctx = ContextRegistry.GetContext();
// [metier] katmanında referans isteniyor
metier = (IImpotMetier)ctx.GetObject("metier");
} catch (Exception e1) {
...
}
- 5. satır: Spring tarafından [dao] ve [metier] katmanlarının örneklenmesi
- 7. satır: [metier] katmanına ilişkin bir referans alınır. [ui] katmanının, [metier] katmanını uygulayan sınıfın adını belirtmeden bu referansa sahip olduğu unutulmamalıdır.
Spring çerçeve yapısı iki sürümde mevcuttur: Java ve .NET. .NET sürümü şu URL'de (Mart 2008) bulunabilir: [http://www.springframework.net/]:
![]() |
- [1] adresinde: [Spring.net] sitesi
- [2]: indirme sayfası
![]() |
- [3]'te: Spring 1.1'i indirin (Mart 2008)
![]() |
- [4]: .exe sürümünü indirin ve kurun
- [5]: kurulumun oluşturduğu klasör
- [6] içinde: [bin/net/2.0/release] klasörü, Visual Studio .NET 2.0 veya üzeri sürümlerdeki projeler için Spring’in DLL dosyalarını içerir. Spring, zengin özelliklere sahip bir çerçeve (framework)dır. Burada bir uygulamadaki katmanların entegrasyonunu yönetmek için kullanacağımız Spring özelliği, IoC: Kontrolün Tersine Çevrilmesi (Inversion of Control) veya DI: Bağımlılık Enjeksiyonu (Dependence Injection) olarak adlandırılır. Spring, NHibernate ile veritabanlarına erişim, web hizmetlerinin ve web uygulamalarının oluşturulması ve çalıştırılması için kütüphaneler sunar...
- Bir uygulamada katmanların entegrasyonunu yönetmek için gerekli olan DLL'ler, DLL, [7] ve [8]'tir.
Bu üç DLL'i projemizin bir [lib] klasöründe saklıyoruz:
![]() |
- [1]: Üç DLL dosyası, Windows Gezgini kullanılarak [lib] klasörüne yerleştirilir
- [2]: [ui] projesinde tüm dosyalar görüntülenir
- [3]: [ui/lib] klasörü artık görülebilir. Bu klasör projeye eklenir
- [4]: [ui/lib] klasörü projenin bir parçasıdır
[lib] klasörünün oluşturulması kesinlikle zorunlu değildir. Referanslar, [bin/net/2.0/release] klasöründeki üç DLL dosyası üzerinde doğrudan oluşturulabilirdi. Ancak [lib] klasörünün oluşturulması, [Spring.net]'in bulunmadığı bir bilgisayarda uygulamanın geliştirilmesine olanak tanır ve böylece mevcut geliştirme ortamına olan bağımlılığı azaltır.
[ui] projesine, üç yeni DLL dosyasına referanslar ekliyoruz:
![]() |
- [1]: [lib] klasöründeki üç DLL dosyasına referanslar oluşturuluyor [2]
- [3]: Üç DLL dosyası, projenin referanslarının bir parçasıdır
Uygulamanın mimarisine genel bir bakışla devam edelim:
![]() |
Yukarıda, [ui] katmanı, Spring’den [0]’i[dao], [1], [metier] ve [2] katmanlarını oluşturmasını isteyecektir. [ui] katmanı daha sonra Spring'den [3]'e, [metier] katmanına ilişkin bir referans isteyecektir. Bu, [ui] katmanında aşağıdaki kodla ifade edilecektir:
// [metier et dao] katmanları oluşturulur
IImpotMetier metier = null;
try {
// Spring bağlamı
IApplicationContext ctx = ContextRegistry.GetContext();
// [metier] katmanında bir referans isteniyor
metier = (IImpotMetier)ctx.GetObject("metier");
} catch (Exception e1) {
...
}
- 5. satır: Spring tarafından [dao] ve [metier] katmanlarının örneklenmesi
- 7. satır: [metier] katmanına bir referans alınır.
Yukarıdaki [5] satırı, Visual Studio projesinin [App.config] yapılandırma dosyasını kullanır. Bir C# projesinde bu dosya, uygulamayı yapılandırmak için kullanılır. Dolayısıyla [App.config], Spring'e özgü bir kavram değil, Spring'in kullandığı bir Visual Studio kavramıdır. Spring, [App.config] dışındaki diğer yapılandırma dosyalarını da kullanabilir. Bu nedenle burada sunulan çözüm, mevcut tek çözüm değildir.
Visual Studio sihirbazı ile [App.config] dosyasını oluşturalım:
![]() |
- [1] dosyasında: projeye yeni bir öğe eklenmesi
- [2]'te: "Application Configuration File" seçeneğini seçin
- [3]: [App.config], bu yapılandırma dosyasının varsayılan adıdır
- [4]: [App.config] dosyası projeye eklendi
[App.config] dosyasının içeriği şöyledir:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</configuration>
[App.config] , bir XML dosyasıdır. Proje yapılandırması, <configuration> etiketleri arasında yapılır. Spring için gerekli yapılandırma şöyledir:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="dao" type="Dao.FileImpot, ImpotsV5-dao">
<constructor-arg index="0" value="DataImpot.txt"/>
</object>
<object name="metier" type="Metier.ImpotMetier, ImpotsV5-metier">
<constructor-arg index="0" ref="dao"/>
</object>
</objects>
</spring>
</configuration>
- 11-23. satırlar: <spring> etiketiyle sınırlanan bölüm, <spring> bölüm grubu olarak adlandırılır. [App.config] dosyasında istenilen sayıda bölüm grubu oluşturulabilir.
- Bir bölüm grubu, bölümlerden oluşur; burada da durum böyledir:
- 12-14. satırlar: <spring/context> bölümü
- 15-22. satırlar: <spring/objects> bölümü
- 4-9. satırlar: <configSections> bölgesi, [App.config] içinde bulunan bölüm gruplarının işleyicilerinin (handlers) listesini tanımlar.
- 5-8. satırlar: <spring> grubundaki (name="spring") bölümlerin işleyicilerinin listesini tanımlar.
- 6. satır: <spring> grubundaki <context> bölümünün işleyicisi:
- name: yönetilen bölümün adı
- type: bölümü yöneten sınıfın adı, NomClasse, NomDLL biçiminde.
- <spring> grubunun <context> bölümü, [Spring.Context.Support.ContextHandler] sınıfı tarafından yönetilir; bu sınıf, DLL ve [Spring.Core.dll] içinde bulunur
- 7. satır: <spring> grubunun <objects> bölümünün yöneticisi
4-9. satırlar, Spring içeren bir [App.config] dosyasında standarttır. Bunları bir projeden diğerine kopyalamak yeterlidir.
- 12-14. satırlar: <spring/context> bölümünü tanımlar.
- 13. satır: <resource> etiketinin amacı, Spring'in örneklendirmesi gereken sınıfları tanımlayan dosyanın nerede olduğunu belirtmektir. Bu sınıflar, burada olduğu gibi [App.config] dosyasında olabilir, ancak başka bir yapılandırma dosyasında da bulunabilir. Bu sınıfların konumu, <resource> etiketinin uri özniteliğinde belirtilir:
- <resource uri="config://spring/objects">, örneklenecek sınıfların listesinin [App.config] (config:) dosyasında, //spring/objects bölümünde, <spring> etiketinin içindeki <objects> etiketinde yer aldığını belirtir.
- <resource uri="file://spring-config.xml">, örneklenecek sınıfların listesinin [spring-config.xml] dosyasında bulunduğunu belirtir. Bu dosya, projenin derleme klasörlerine (bin/Release veya bin/Debug) yerleştirilmelidir. En kolayı, [DataImpot.txt] dosyasında yapıldığı gibi, bu dosyayı [Copy to output directory=always] özelliğiyle birlikte projenin kök dizinine yerleştirmektir.
12-14. satırlar, Spring içeren bir [App.config] dosyasında standarttır. Bunları bir projeden diğerine kopyalamak yeterlidir.
- 15-22. satırlar: örneklenecek sınıfları tanımlar. Uygulamanın özel yapılandırması bu bölümde yapılır. <objects> etiketi, örneklenecek sınıfların tanımlandığı bölümü sınırlar.
- 16-18. satırlar: [dao] katmanı için örneklenecek sınıfı tanımlar.
- 16. satır: Spring tarafından örneklenen her nesne, bir <object> etiketiyle belirtilir. Bu etiketin name özniteliği, örneklenen nesnenin adını içerir. Uygulama, Spring’den bir referans talep ederken bu özniteliği kullanır: “dao adlı nesneye ilişkin bir referans ver”. type özniteliği, örneklenecek sınıfı NomClasse, NomDLL biçiminde tanımlar. Böylece 16. satır, "DLL" ve "ImpotsV5-dao.dll" içinde bulunan "Dao.FileImpot" sınıfının bir örneği olan "dao" adlı bir nesneyi tanımlar. Burada, sınıfın tam adının (ad alanı dahil) verildiğine ve DLL adında .dll sonekinin belirtilmediğine dikkat edilmelidir.
Spring ile bir sınıf iki şekilde örneklenebilir:
- parametreler geçirilen özel bir oluşturucu aracılığıyla: 16-18. satırlarda yapılan budur.
- parametre içermeyen varsayılan oluşturucu aracılığıyla. Bu durumda nesne, genel özellikleri aracılığıyla başlatılır: <object> etiketinde, bu farklı özellikleri başlatmak için <property> alt etiketleri bulunur. Burada bu duruma ilişkin bir örnek bulunmamaktadır.
- (devam)
- 16. satır: Oluşturulan sınıf, FileImpot sınıfıdır. Bu sınıfın oluşturucusu şöyledir:
public FileImpot(string fileName);
Oluşturucu parametreleri, <constructor-arg> etiketleri kullanılarak tanımlanır.
- 17. satır: Oluşturucunun ilk ve tek parametresini tanımlar. index özniteliği, oluşturucu parametresinin numarasıdır; value özniteliği ise değeridir: <constructor-arg index="i" value="valuei"/>
- 19-21. satırlar: [metier] katmanı için örneklenecek sınıfı tanımlar: DLL ve [ImpotsV5-metier.dll] sınıflarında bulunan [Metier.ImpotMetier] sınıfı.
- 19. satır: Oluşturulan sınıf, ImpotMetier sınıfıdır. Bu sınıfın oluşturucusu şöyledir:
public ImpotMetier(IImpotDao dao);
- (devam)
- 20. satır: Oluşturucunun ilk ve tek parametresini tanımlar. Yukarıda, oluşturucunun dao parametresi bir nesne referansıdır. Bu durumda, <constructor-arg> etiketinde, [dao] katmanı için kullanılan value özniteliği yerine ref özniteliği kullanılır: <constructor-arg index="i" ref="refi"/>. Yukarıdaki yapıcıda, dao parametresi, [dao] katmanındaki bir örneği temsil eder. Bu örnek, yapılandırma dosyasının 16-18. satırlarında tanımlanmıştır. Dolayısıyla 20. satırda:
<constructor-arg index="0" ref="dao"/>
ref="dao", 16-18. satırlarda tanımlanan "dao" Spring nesnesini temsil eder.
Özetlemek gerekirse, [App.config] dosyası:
- [dao] katmanını, FileImpot sınıfıyla örneklendirir; bu sınıf, DataImpot.txt'i parametre olarak alır (16-18. satırlar). Ortaya çıkan nesneye "dao" adı verilir
- [metier] katmanını, parametre olarak önceki "dao" nesnesini alan ImpotMetier sınıfıyla örneklendirir (satır 19-21).
Geriye sadece bu Spring yapılandırma dosyasını [ui] katmanında kullanmak kalıyor. Bunun için [Dialogue.cs] sınıfını [Dialogue2.cs] olarak kopyalıyoruz ve ikincisini [ui] projesinin ana sınıfı yapıyoruz:
![]() |
- [1]: [Dialogue.cs]'in kopyası
- [2]'e: birleştirme
- [3]'e: [Dialogue.cs]'in kopyası
- [4]'e: [Dialogue2.cs] olarak yeniden adlandırıldı
![]() |
- [6]'e: [Dialogue2.cs], [ui] projesinin ana sınıfı yapılır.
[Dialogue.cs]'teki aşağıdaki kod:
// [metier et dao] katmanları oluşturulur
IImpotMetier metier = null;
try {
// [metier] katmanının oluşturulması
metier = new ImpotMetier(new FileImpot("DataImpot.txt"));
} catch (ImpotException e) {
// hata mesajı görüntüleniyor
string msg = e.InnerException == null ? null : String.Format(", Exception d'origine : {0}", e.InnerException.Message);
Console.WriteLine("L'erreur suivante s'est produite : [Code={0},Message={1}{2}]", e.Code, e.Message, msg == null ? "" : msg);
// program durduruluyor
Environment.Exit(1);
}
// sonsuz döngü
while (true) {
...
[Dialogue2.cs]'te şu şekilde değişir:
// katmanlar oluşturuluyor [metier et dao]
IApplicationContext ctx = null;
try {
// Spring bağlamı
ctx = ContextRegistry.GetContext();
} catch (Exception e1) {
// hata mesajı görüntüleme
Console.WriteLine("Chaîne des exceptions : \n{0}", "".PadLeft(40, '-'));
Exception e = e1;
while (e != null) {
Console.WriteLine("{0}: {1}", e.GetType().FullName, e.Message);
Console.WriteLine("".PadLeft(40, '-'));
e = e.InnerException;
}
// programın durdurulması
Environment.Exit(1);
}
// [metier] katmanında referans isteniyor
IImpotMetier metier = (IImpotMetier)ctx.GetObject("metier");
// sonsuz döngü
while (true) {
....................................
- 2. satır: IApplicationContext, Spring tarafından örneklendirilen tüm nesnelere erişim sağlar. Bu nesneye, uygulamanın Spring bağlamı veya daha basitçe uygulama bağlamı denir. Şu anda bu bağlam henüz başlatılmamıştır. Bunu, takip eden try/catch bloğu gerçekleştirir.
- 5. satır: [App.config] içindeki Spring yapılandırması okunur ve işlenir. Bu işlemden sonra, herhangi bir istisna oluşmamışsa, <objects> bölümündeki tüm nesneler örneklenmiştir:
- "dao" adlı Spring nesnesi, [dao] katmanında bir örnektir
- "metier" adlı Spring nesnesi, [metier] katmanında bir örnektir
- 19. satır: [Dialogue2.cs] sınıfı, [metier] katmanında bir referansa ihtiyaç duyar. Bu referans, uygulama bağlamından istenir. IApplicationContext nesnesi, Spring nesnelerine adları aracılığıyla erişim sağlar (Spring yapılandırmasındaki <object> etiketinin name özniteliği). Dönüşte verilen referans, Object jenerik türüne ait bir referanstır. Dönüşüm sonucunda elde edilen referansı doğru türe, bu durumda [metier] katmanının arayüz türü olan IImpotMetier'e dönüştürmemiz gerekir.
Her şey yolunda gittiyse, 19. satırdan sonra [Dialogue2.cs], [metier] katmanına bir referansa sahip olur. 21. satır ve sonrasındaki kod, daha önce incelediğimiz [Dialogue.cs] sınıfına aittir.
- 6-17. satırlar: Spring yapılandırma dosyasının işlenmesi tamamlanamadığında ortaya çıkan istisna yönetimi. Bunun çeşitli nedenleri olabilir: yapılandırma dosyasının kendisinde hatalı sözdizimi ya da yapılandırılan nesnelerden birinin örneklenememesi. Örneğimizde, ikinci durum, [App.config] dosyasının 17. satırında geçen DataImpot.txt dosyası projenin çalışma klasöründe bulunmadığında ortaya çıkar.
- satırda bildirilen istisna, her bir istisnanın iki özelliğe sahip olduğu bir istisna zinciridir:
- Mesaj: istisna ile ilgili hata mesajı
- InnerException: istisna zincirindeki bir önceki istisna
10-14. satırlardaki döngü, zincirdeki tüm istisnaları şu biçimde görüntüler: istisna sınıfı ve ilgili mesaj.
[ui] projesini geçerli bir yapılandırma dosyasıyla çalıştırdığımızda, her zamanki sonuçları elde ederiz:
Paramètres du calcul de l'Impot au format : Marié (o/n) NbEnfants Salaire ou rien pour arrêter :o 2 60000
Impot=4282 euros
[ui] projesini, mevcut olmayan bir [DataImpotInexistant.txt] dosyasıyla çalıştırdığımızda,
<object name="dao" type="Dao.FileImpot, ImpotsV5-dao">
<constructor-arg index="0" value="DataImpotInexistant.txt"/>
</object>
şu sonuçlar elde edilir:
- 17. satır: [FileNotFoundException] türündeki orijinal istisna
- 15. satır: [dao] katmanı bu istisnayı bir [Entites.ImpotException] türüne sarmalar
- 9. satır: Spring, "dao" adlı nesneyi oluşturamadığı için bu istisnayı tetiklemiştir. Bu nesnenin oluşturulma sürecinde, daha önce 11. ve 13. satırlarda iki istisna daha meydana gelmiştir.
- "dao" nesnesi oluşturulamadığı için uygulama bağlamı da oluşturulamadı. 5. satırdaki istisnanın anlamı budur. Bundan önce, 7. satırdaki başka bir istisna meydana gelmişti.
- 3. satır: En üst düzeydeki istisna, zincirin sonuncusu: bir yapılandırma hatası bildiriliyor.
Tüm bunlardan çıkarılacak sonuç, genellikle en anlamlı olanın en alt düzeydeki istisna, yani burada 17. satırdaki istisna olduğudur. Ancak Spring'in, hatanın en üst düzeydeki nedenini belirlemek amacıyla 17. satırdaki hata mesajını saklayarak bunu 3. satırdaki en üst düzeydeki istisnaya ilettiği de unutulmamalıdır.
Spring tek başına bir kitap yazılmaya değer bir konudur. Burada konuyu sadece yüzeysel olarak ele aldık. Spring kurulum klasöründe bulunan [spring-net-reference.pdf] belgesiyle bu konuyu daha derinlemesine inceleyebilirsiniz:
![]() |
Ayrıca, VB.NET bağlamında sunulan bir Spring öğreticisi olan [http://tahe.developpez.com/dotnet/springioc] belgesini de okuyabilirsiniz.






























































