forked from burakbayramli/books
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchapobject.tex
881 lines (749 loc) · 43.8 KB
/
chapobject.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
\movetooddpage
\chapter{Nesnesel Tasarım} \label{object}
\thischapterhas{
\item Nesne odaklı tasarım ve programcılık
\item Tasarım düzenleri
\item Mimariler
}
\versal{N}\textsc{esne} odaklı tasarım ve programcılık günümüzde o kadar geniş
kabul görmüş haldedir ki, bu kavramlar sahneye çıkmadan önce nasıl program
yazdığımızı (yaşı uygun olanlar) bile bazen hatırlayamaz oluyoruz. Fakat aslında
kurumsal programcılığın en popüler dili Java'yı, onun temel aldığı C++, ve
Smalltalk dillerini 1967 yılında çıkmış tek bir dile bağlamak mümkündür: Simula.
Simula yaratıcıları (Kristen Nygaard, Ole-Johan Dahl) dünyanın ilk nesnesel
dilini simulasyon programları yazmak amacı ile yarattılar. Ayrısal olay
simulasyonu (discrete event simulation), bir matematiksel sistemi cebirsel
yöntemlerle (analitik modelleme -analytical modeling-) ile değil, sistemdeki
aktörleri bir nesne olarak betimleyip, onların üzerine gelen olayları (event)
gerçek dünya şartlarında olacağı gibi ama sanal bir ortamda tekrar yaratarak bir
sistemi çözmeye verilen addır. Bazı problemlerin çözülebilir (tractable) bir
analitik modeli kurulumadığından, ayrıksal olay simulasyonu popüler bir çözüm
olarak kullanım bulmuştur.
Nesne odaklı tasarım ve programlama kavramlarının ilk kez simulasyon amaçlı
yaratılmış bir dil içinde ortaya çıkmaları kesinlikle bir kaza değildir
\cite[sf. 1122]{ooconstruction}. Nesne kavramı, gerçek dünyadaki bir nesneyi çok
rahat yansıttığı için, bu yönde kullanım bulması rahattı. Daha sonra Simula
kullanıcılarının da fark ettiği üzere, simulasyon dışında Simula dilinin genel
programcılık için yararlı olabileceği anlaşılmaya başlanmıştır. Bundan sonra
nesne odaklı programlama kavramları genel programcı kitlesine de yayılmaya
başladı.
Simula'nın özellikleri diğer diller tarafından hızla adapte edilmeye başlandı:
Xerox PARC şirketinde Alan Kay, Simula'dan esinlendiği nesnesel odaklı
kavramları, grafik ortamda programlama ortamı sağlayan yeni projesine dahil
ederek Smalltalk dilini yarattı (1972). Eğer Simula akademik çevreleri
etkilediyse, Smalltalk Byte dergisinin ünlü 1981 Ağustos sayısında {\em
kitleleri} etkilemiştir: Bu sayıda görsel bir Smalltalk programlama ortamı ilk
kez genel programcı seyircisine nesneleri görsel bir şekilde
tanıtıyordu. Hakikaten de Smalltalk nesnesel diller içinde görsel geliştirme
ortamını ilk destekleyen dil olarak günümüzdeki modern IDE'lerin çoğunun
fikir babası sayılmaktadır.
Smalltalk'ın getirdiği ilginç bir özellik class (nesne tanımı) ile nesne kavramı
arasındaki farkı ortadan kaldırmasıydı; Smalltalk dünyasında herşey bir
nesneydi. Bu durum, günümüzde kullanılan ``nesne odaklı programlama'' teriminin
bile Smalltalk'tan ne kadar etkilendiğini göstermektedir çünkü bu birleşim, ne
yazık ki, her diğer ``nesne odaklı'' dil için doğru değildir: Java, Eiffel, C++
gibi güçlü tipleme takip eden dillerde class ve nesne çok net bir şekilde
birbirinden ayrılır, yâni ``nesne odaklı programlama'' terimi, güçlü tipleme
kullanan bir dil için ``class odaklı programlama'' olarak değiştirilmelidir
(neyse!). Fakat Smalltalk diğer noktalarda, meselâ her şeyi nesne hâline
getirmiş olması sayesinde debugger, nesne gezici (object browser) gibi koda
erişmesi gereken ek araçların işini rahatlatabilmeyi başarmıştır.
Smalltalk'un dezavantajı, zayıf tipleme kullanan dinamik bir dil olmasıdır ve bu
sebeple Smalltalk kullanan programlar ciddi performans problemleri
yaşamıştır. Sebebini şöyle açıklayabiliriz: Statik tipleme kullanan Java, Eiffel
ve C++ derleyicilerinin bazı performans iyileştirmelerini programcılarına
derleme anında sunmaları mümkündür: Meselâ, bu dillerin derleyicileri miras
(inheritance) durumunda fonksiyonları bir dizin olarak önceden tutarak, dinamik
bağlamayı (dynamic binding) anlık/sabit zamanda çözebilirler. Smalltalk, dinamik
bir dil olması sebebiyle bu tür iyileştirmelerden faydalanamamıştır
\cite[sf. 1134]{ooconstruction}.
Bu hatalar, AT\&T Bell Labarotuvarlarında çalışan Bjarne Stroustrup tarafından
dikkate alınarak, tekrarlanmamıştır. Simula'nın ana kavramlarını C diline
taşıyarak C++ dilini oluşturan Stroustrup, özellikle C'den nesnesel kavramına
geçiş yapmak isteyen programcılara 80 sonları ve 90 başlarında çok ideal bir
seçenek sunmayı başardı. Bu zamanlarda C++, Kurumsal Yazılım Müdürleri (IT
Manager) için her iki dünyanın en iyi birleşimiydi: Mevcut programcıları
korkutmayacak kadar C, ama ileri kavramları öğrenmek isteyenlerin seveceği kadar
``nesnesel''. Fakat C++'ın nesnesel dil eklerinin gereğinden fazla çetrefil
olması ve C++'ın bir çöp toplayıcıyı desteklememesi gibi sebepler yüzünden,
90'lı yılların ortalarında sektör kurumsal uygulamalarda Java diline doğru
kaymıştır.
Java, C++'ın sözdizimini daha temizleştirerek çöp toplayıcı eklemiş, ayrıca
eşzamanlı (concurrent) ve network üzerinde programlamaya paketten çıktığı
hâliyle destek vermesi ile, nokta com patlaması (dot com boom) ile aynı anda
anılır ve bilinir bir duruma gelmiştir. Bu yüzden ``daha iyi bir C++'' arayan
kurumsal programcılar, ve yeni Internet ortamının gerektirdiği programlama için
Java, gerekeni tam karşılayan bir dil hâline geliyordu. Günümüzde Java kurumsal
programcılığın en yaygın kullanılan dili hâline gelmiştir.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section[Nesnesel Tasarım ve Teknoloji][NESNESEL TASARIM VE TEKNOLOJİ]{Nesnesel Tasarım ve Teknoloji}
Kurumsal programcılıkta kullanılan dilin temizliğine ek olarak, kullanılan {\em
yan teknolojiler} ve onların gerektirdiği arayüzler (API) nesnesel tasarımımız
üzerinde kesinlikle çok etkilidirler. Nesnesel tasarımın ve yöntemlerinin ilk
yaygınlaşmaya başladığı 80'li ve 90'lı yıllarda yapılan önemli bir hata, dış
teknolojilerin yok sayılması ve nesnesel tasarımın bir boşlukta (vacuum)
içerisindeymiş gibi yapılmasının cesaretlendirilmeydi. Bu tavsiyenin projeler
üzerinde etkisi öldürücü olmuştur, çünkü bir projede kullandığınız her
teknoloji, {\em nesne yapılarınızı etkiler}.
Diğer programcılık dallarında bu her zaman geçerli olmayabilir. Meselâ bilimsel
formül hesabı (number crunching) yapması gereken türden programlar, dış
sistemlerle fazla etkileşime girmezler. Girdi olarak bir dosya alırlar, ve çıktı
olarak bir diğer dosya üretirler (ya da çizim (plot) basabilirler). Kıyasla bir
kurumsal sistem, en azından bir veri tabanı ürünüyle, ek olarak ta uygulama
servisi, JMS ile asenkron iletişim, e-mail, yardımcı kütüphaneler gibi birçok
dış araç ile iletişim hâlinde olacaktır. Formül hesabı yapan programlarda dış
sistemleri yok sayarak daha rahat nesnesel tasarım yapabilirsiniz, ama kurumsal
sistemlerde nesnesel tasarımı {\em nerede} kullanacağımızı çok iyi bilmemiz
gerekecektir.
Nesnesel tasarım yapacağımız yer, programınızın işleyiş kontrolünün altyapı
kodlarından sizin yazacağınız kodlara geldiği yerde başlar, ve tekrar dış
teknolojiye geçtiğinde biter. Burada vurgulamak istediğimiz bir {\em öncelik
mentalitesi} farkıdır: Yapılması yanlış olan, teknolojiden habersiz bir şekilde
şirin ve güzel nesneler tasarlayıp onu sonra teknolojiye bağlamaktır. Bunun
yerine yapılması gereken, önce teknolojiyle nasıl konuşmamız gerektiğini
anlamamız ve sonra iletişim noktalarını iyice kavradıktan {\em sonra}, arada
kalan boş bölgeleri nesnesel tasarım ile doldurmamızdır. Bunu bir örnek ile
anlatmaya çalışalım:
Sisteme müşteri eklemesi gereken bir uygulama düşünelim. Bu müşteri veri yapısı
hakkında tutmamız gereken özellikler (attributes) biliniyor olsun. Öğeler
alındıktan sonra uygulamanın bazı doğruluk kontrolleri yapması gerekiyor, bunlar
isim ve soyadının mecburi olması ve e-mail içinde \PVerb!@! işaretinin
bulunmasının kontrol edilmesi gibi işlemler olacaktır. Daha sonra, başka bir
işlem ile müşteri veri tabanına yazılacaktır.
Bu gereklilik listesi üzerinden, bazı nesnesel programcılar tasarıma direk
başlanabileceğini savunurlar. İdeal bir modeli de şöyle kurabilirler (Şekil
\ref{object:tech:wrong:customer}).
\begin{figure}[!hbp]
\center{
\scalebox{0.40}{
\includegraphics{./images/customer-wrong.eps}
}
}
\caption{\label{object:tech:wrong:customer} Yanlış Müşteri Nesnesi}
\end{figure}
Bu modele göre önyüz, bir \PVerb!Customer! (müşteri) nesnesini yaratacak,
üzerinde \PVerb!setName!, \PVerb!setLastName!, vs. ile gereken verileri
koyacak, daha sonra veri doğruluk kontrolü için \PVerb!validate! çağrısını
yapacak, ve en sonunda \PVerb!save! ile müşteri nesnesini kaydedecektir.
Fakat, bu teorik model hiçbir {\em teknolojiyi} dikkate almamıştır, bu sebeple
tasarımı etkileyebilecek bazı ek faktörler atlanmıştır. Bakalım teknolojiyi
ekleyince modelimiz etkilenecek mi? Meselâ eğer önyüzde Struts altyapısını
kullanıyorsak, bağlanan (client) tarafında doğruluk kontrolü yapmanın bir Struts
tekniği vardır. Özellikle örnekte gösterilen müşteri nesnesinde yaptığımız
türden kontroller (isim, soyadı, e-mail) için, Javascript bazlı çalışan bir
altyapı mevcuttur. Bu doğruluk kontrol altyapısı, Struts'ın ayar dosyasından
\PVerb!struts-config.xml! ayarlanır, ve modelde görülen türden bir Java kodu
yazılmasını gerektirmez. Bu yüzden, modelimize koyduğumuz \PVerb!validate!
metodu tamamen gereksizdir.
Aynı şekilde, eğer projemizde Hibernate kullanıyorsak, kalıcılık metodu olan
\PVerb!save!, müşteri nesnesi üzerinde değil, \PVerb!org.hibernate.Session!
nesnesi üzerindedir. Hibernate \PVerb!save!'e parametre olarak müşteri nesnesini
geçmek gerekir, \PVerb!save! metodu müşteri üzerinde çağırılmaz. Yine
teknolojiyi dikkate alınmadan modelin eksik olacağını kanıtlamış oluyoruz.
Fakat bitmedi. Şimdi önyüz ile \PVerb!Customer! nesnesi arasında olabilecek
çağrıları ele alalım. Meselâ, önyüz teknolojisi Swing olsun, ve bu sebeple
birçok Swing önyüzünün ağ (network) üzerinden müşteriye erişmesi gereksin. Pür
nesnesel modelleme yaptığımız için ve teknolojiyi dikkate (!) almadığımız için
de, \ref{object:tech:still:wrong:customer} üzerindeki yapıyı kuruyoruz.
\begin{figure}[!hbp]
\center{
\scalebox{0.40}{
\includegraphics{./images/customer-still-wrong.eps}
}
}
\caption{\label{object:tech:still:wrong:customer} Network Üzerinden Müşteri Eklemek}
\end{figure}
Bu model optimal çalışır mı? Eğer bir uygulamanın performansı en az doğruluğu
kadar önemliyse, bu modelin hızlı çalısması önemli olmalıdır. Ne yazık ki bu
soruya cevap, ``hayır'' olacaktır. Dikkat edersek önyüz, yâni \PVerb!Page!
(sayfa) nesnesi, \PVerb!new! ile bir \PVerb!Customer! nesnesi yarattıktan sonra,
bu nesne üzerinde \PVerb!set! metotlarını çağıracaktır. İşte problem burada
ortaya çıkacaktır, çünkü network üzerinde erişim kurallarına göre, ufak ufak
\PVerb!set! çağrıları yapmak yerine, tüm gereken parametreleri birarada
paketleyerek {\em tek bir defada} hepsini göndermek daha hızlıdır. Kurumsal
programcılıkta ufak ufak ve çok çağrı yapan sistemlere geveze (chattery)
sistemler denmektedir, ve bu tür mimarilerden kaçınılır.
O zaman, modelimize arayüzü daha geniş olan yeni bir class daha eklememiz
gerekiyor. Bu class, dış dünyaya gösterilen arayüz olacaktır, çünkü parametre
listesini network üzerinden göndermeye daha elverişli durumdadır. Bu yeni model,
Şekil \ref{object:tech:still:right:customer} üzerinde görülebilir.
\begin{figure}[!hbp]
\center{
\scalebox{0.40}{
\includegraphics{./images/customer-right.eps}
}
}
\caption{\label{object:tech:still:right:customer} Doğru Müşteri Modeli}
\end{figure}
Bu şekil üzerinde yeni bir class, \PVerb!CustomerService! görüyoruz. Bu class
hangi dağıtık nesne teknolojisini kullanıyorsak, o şekilde uzaktan erişime
açılacak olan class'tır.
Özet olarak, ilk yola çıktığımız modelden oldukça uzaklaştık. Metot
\PVerb!validate!, hem Struts hem de Swing ortamında ``bağlanan'' tarafa alınması
gereken bir metottu, bu yüzden \PVerb!Customer!'dan çıkartıldı. Metot
\PVerb!save!, kalıcılık aracı üzerinde olacaktı, bu sebeple çıkartıldı. Ve
son olarak uzaktan erişim için ilk modelde hesaba alınmayan
\PVerb!CustomerService! class'ı eklendi, böylece \PVerb!Customer! nesnesinin
uzaktan çağırılmasını engelledik.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Modelleme}
Teknolojinin kontrolü bize bıraktığı ve bizim kontrolü geri verdiğimiz noktanın
arasında (ve hâla teknolojiyi aklımızda tutarak) artık klasik anlamda modelleme
yapabiliriz. Tabii en teorik, ve teknolojiden bağımsız olduğunu iddia eden
modeller bile tek bir teknolojik önkabul yapmaktadırlar; Bir nesnenin diğer bir
nesneye yaptığı metot çağrısının milisaniye seviyesinde ve aynı JVM içinde
işletileceği önkabulu. Bölümümüzün geri kalanında bu teknolojik önkabule
dayanarak istediğimiz büyüklükte ve sayıda class'ı modelimize koymakta, ve
herhangi bir metotu bir class'tan ötekine aktarmakta sakınca görmeyeceğiz.
\subsection{Prensipler}
Modellerimiz için takip etmemiz gereken basit prensipler şunlar olacaktır:
\begin{itemize}
\item Fonksiyonların yan etkisi olmamalıdır.
\item Kurumsal uygulamalarda, model için class seçerken ilişkisel modelde
(veri tabanı şemasında) tablo olarak planlanmış birimlerin class hâline
gelmesinde bir sakınca yoktur. Bu ilişki her zaman birebir olmayabilir, ama
şemamızdaki tabloların büyük bir çoğunluğunu modelimizde class olarak
görülecektir.
\item Bir class'ın metotları ve fonksiyonlarını bir ``alışveriş listesinin
kalemleri'' gibi görmeliyiz. Bir class dış dünyaya birden fazla servis
sunabilir, ve hâtta sunmalıdır. Bir class sadece tek bir iş için
yazılma\textbf{ma}lıdır.
\item Uygun olduğu yerde, tasarım düzenleri (design patterns) faydalı
araçlardır. Erich Gamma ve arkadaşlarının paylaştığı düzenler içinden
\begin{itemize}
\item Command
\item Template Method
\item Facade
\item Singleton
\end{itemize}
kurumsal sistemlerde kullanılan düzenlerdir (bunların haricindeki Gamma
düzenleri kullanım görmez).
\item Hibernate ile eşlediğimiz POJO'lara \PVerb!set! ve \PVerb!get! haricinde
metotlar eklemekte sakınca yoktur.
\end{itemize}
Şimdi bu prensipleri teker teker açıklayalım:
\subsection{Fonksiyonların Yan Etkisi}
Bir fonksiyon, geriye bir sonuç döndüren bir çağrıdır. Bu geri döndürülen değer,
ya o çağrı içinde anlık yapılan bir hesabın sonucu, ya da nesnede tutulmakta
olan bir nesneye ait referans değeri olacaktır. Her ne olursa olsun, bir
fonksiyon bir değer döndürmekle yükümlüdür. Kıyasla metotlar geriye hiçbir değer
döndürmezler. Bu yüzden metot dönüş tipi olarak \PVerb!void! kullanmak gerekir.
Bir fonksiyonun görevi, raporlamaktır. Metotlar nesne içinde {\em bir şeyler
değiştirmek} için kullanılır. Eğer bir örnek vermek gerekirse bir nesneyi, bir
radyo gibi düşünebiliriz. Bu radyoda metotlar, kanal değiştirme, ses açma/kapama
gibi düğmelerdir. Fonksiyon ise, radyonun hangi kanalda olduğunu gösteren bir
ibre olabilir.
\begin{figure}[!hbp]
\center{
\scalebox{0.40}{
\includegraphics{./images/dvdplayer.eps}
}
}
\caption{Java ArrayList Bir Makina Olsaydı}
\end{figure}
Bu yüzden, fonksiyonlarımızı yazarken nesne içinde değişiklik yapmamaları için
çok özen göstermeliyiz. Bu prensibe dikkat etmeyen ve çağırılınca nesnede
değişikliğe sebep olan fonksiyonlara {\em yan etkisi olan} fonksiyonlar
denmektedir, çünkü fonksiyonun ana amacı dışında bir yan etkiye sebebiyet
vermiştir; Nesnenin iç verisinde değişikliğe sebep olunmuştur. Radyo örneğimize
dönersek, düşünün ki hangi kanalda olduğumuza bakınca radyonun kanalı değişiyor!
Böyle bir radyo muhakkak pek kullanışlı olmazdı, çünkü bir nesne hakkında bilgi
almak istediğimizde, o bilgi alma işleminin, nesne üzerinde bir değişiklik
yapmamasını bekleriz. Nesnesel odaklı tasarımımızda bir fonksiyon yazarken,
aynı mantık ile düşünmeliyiz. Fonksiyonlarımız değişikliğe sebep olmuyorsa,
içimiz rahat bir şekilde bu fonksiyonları istediğimiz kadar çağırabiliriz.
\subsection{Veri Tabanı ve Nesnesel Model}
Model için class seçmek, çoğu nesne odaklı tasarım literatüründe işlenen bir
konudur, ve birçok kulağa küpe (rule of thumb) kural ortaya atılmıştır. Mesela
dilbilgilsel bir yöntem söyledir: Gereklilikler (requirements) dokümanının
cümlelerindeki isimler (noun) seçilir ve bunlardan bazıları class olarak
seçilir. Bu yönteme göre, bir gereklilik dökümanındaki şu cümlede;
\begin{quote}
``Asansör hareket etmeden önce kapılarını kapatmalıdır, ve asansör bir kattan
öteki kata her hareket edişinde veri tabanında bir kayıt yazılmalıdır''
\end{quote}
{\em asansör}, {\em kat} ve {\em kapı} birer class adayı olarak
seçilmelidirler.
Fakat böyle basitçi bir yöntem ne yazık ki bizi fazla uzağa götürmez. İnsan dili
değişik nüanslara çok açıktır ve çok daha katı kurallara göre yazılması gereken
bir bilgisayar sistemi için bizi yanlış yöne sevkedebilir. Mesela üstteki
örnekte aslında class olması gereken şey, {\em hareket}'tir. Ama bu kelime isim
değil bir fiil olduğu için, yöntemimiz tarafından seçilmemiştir
\cite[sf. 723]{ooconstruction}.
Kurumsal uygulamalarda dilbilgisel yöntem yerine veri tabanındaki tablolara
bakarsak, kullanmamız gereken class'lar hakkında daha iyi bir fikir
edinebiliriz. Meselâ üstteki örnekteki hareket, ilişkisel modelleme sırasında
kesinlikle yakalanacak bir tablo olduğundan, bu tabloya bakarak modelimize aynı
isimde bir class ekleyebiliriz.
Tabii şemamızdaki her tabloyu bir class hâline getirmemiz gerekmez. Meselâ daha
önce kullandığımız \PVerb!Garage! ve \PVerb!Car! örneğine gelirsek, bu iki tablo
arasında çoka çok eşleme yaptığımız durumda bir ara tablo (cross reference
table) kullanmamız gerekecektir. Bu tablonun nesne modeline yansıtılması
gerekmez. Şekil \ref{object:modeling:db} üzerinde bu eşlemeyi görüyoruz.
\begin{figure}[!hbp]
\center{
\scalebox{0.55}{
\includegraphics{./images/garage_car_coka_cok_mapping.eps}
}
}
\caption{\label{object:modeling:db} Şemadaki Her Tablonun Nesne Modeline Yansıtılması Gerekmez}
\end{figure}
Bu eşlemede \PVerb!GARAGE_CAR! adlı tablo, nesne dünyasına yansıtılmamıştır (bu
eşlemenin Hibernate üzerinden nasıl yapıldığını anlamak için
\ref{hibernate:rels:manytomany} bölümüne bakabilirsiniz.
\subsection{Metotlar ve Alışveriş Listesi} \label{object:shoppinglist}
Class seçerken eşzamanlı olarak aklımızın bir köşesinde bir seçim daha yaparız;
Hangi metotlar ve hangi fonksiyonların bir class üzerinde olacağı
seçimi. Zihnimizde belli servisler ile veriler biraraya geldiğinde, bu
birliktelikten bir class doğar. Peki bu birlikteliği bulmak için bir yöntem var
mıdır? Bu iş için nasıl bir zihin durumu (mindframe) içinde bulunmalıyız?
\subsubsection{Tek Amaçlı Class}
Nesnesel tasarımda {\em tek bir} servis sağlayan class'lardan kaçınmamız
gerekir, çünkü bu tür class'lar, hâla class modülünü bir servisler listesi
olarak görmediğimizi gösterir. Bu tür nesneleri, onları belgeleyen dokümanlardan
rahatça anlayabilirsiniz, genellikle şöyle târif edilirler: ``Bu nesne xxx işini
yapıyor''. Bir xyz işini ``yapıyor'' diyebildiğimiz nesne, demek ki tek o iş
için yazılmıştır. Halbuki nesnesel yöntemin gücü, bir veri tanımı etrafında
birçok servisin sağlanabilmesidir. Bu servislerin illâ ki birbirlerini çağırıyor
ve birbirleri ile yakın alâkada olacak hâlde tasarlanmış olması gerekmez. Bu
yüzden bir class'ın metot listesi, ``bir alışveriş listesine benzer''
denir. Değişik ihtiyaçlar için değişik kalemler
vardır\footnote{\ref{dist:command} bölümünde bahsedilen Command mimarisinin bu
kurala uymadığı düşünülebilir, fakat Command mimarisindeki durum, teknolojik bir
gereklilikten (network iletişim hızının aynı JVM'de olan iletişimden daha düşün
olması sebebiyle) ortaya çıkmıştır. Dediğimiz gibi, teknolojik sınırlar mimariyi
etkilemiştir.}.
Meselâ \PVerb!java.util.ArrayList! class'ını örnek alırsak, dış dünyaya
sağladığı birçok servis vardır. \PVerb!add! çağrısı ile nesneye (listeye) bir
eleman eklenmesini sağlar, \PVerb!remove! ile bir nesnenin silinmesi servisini
sağlar. Çağrılar \PVerb!add! ile \PVerb!remove! arasında direk bir kod
bağlantısı yoktur. Bu iki metot birbirlerini çağırmazlar. Ama aynı class
üzerinde bulunurlar. Eğer bu iş için sadece ``tek bir işlem için'' yazılmış bir
class kullansaydık, o zaman \PVerb!ListAdder! ve \PVerb!ListRemover! gibi iki
class yazmamız gerekecekti. Bu class'lardaki metotları ise \PVerb!doIt! gibi
komik bir isimde olabilecek bir tek metot olacaktı. Tavsiyemiz, \PVerb!doIt!
metotlarından ve bu metotları içerecek class'lardan kaçınmanızdır.
Değişik servisler sağlayan bir nesne düşünmek tasarımcı için faydalıdır. Aynı
şekilde, bir sistem içinde aynı class'tan gelen birçok nesnelenin aynı anda,
farklı roller oynayabileceğini (yâni iki nesnenin değişik metotlarının
çağırılabileceğini) düşünmek te tasarımcı için faydalıdır.
\subsubsection{Nesnesel Modellerde Tepe Fonksiyonu Yoktur!}
Prosedürel disiplinden gelen ve kurtulmamız gereken ikinci bir alışkanlık,
yazmakta olduğumuz uygulama için sürekli bir ``üst nokta'' aramaktır. Üst
noktadan kastımız, uygulamada her şeyin başladığı ve kontrol edildiği o tek
işletici, çağırıcı, ana metot, başlangıç noktasıdır. Prosedürel günlerden kalma
bu alışkanlığı, nesnesel sistemler kurarken terketmemiz gerekiyor, çünkü eğer
sürekli üst nokta metotunu düşünürsek, tasarımız çok fazla ``o kullanım için''
olacak, ve kod düzeni açısından tüm fonksiyonlarımız o başlangıç koca metotunun
uydusu hâline gelerek, en kötü durumda başlangıç metotunun olduğu class'ta bir
takım ek metotlar haline gelecektir.
\textbf{Nesnesel sistemlerde üst nokta yoktur}. Nesnesel tasarımlarda ``uygulama''
denen şey, tüm class'lar kodlandıktan sonra {\em en son aşama olarak}, son anda
gereken class'ların birleştirilerek (çağırılarak) biraraya getirilmesi gereken
bir yapboz resmidir. Burada belki de en önemli fark, hangi eylemin hangisinden
önce geleceğini vurgulayan bir öncelik farkıdır. Prosedürel yöntemler tasarımın
yukarıdan-aşağı (top-down) yapılmasını zorlarken, nesnesel yöntemde tasarım
alttan yukarı (bottom-up) doğru gider.
\subsubsection{Örnek}
Bu düşünce farkını herhalde en iyi şekilde bir örnek üzerinden
aktarabiliriz. Uygulamamızın amacı şu olsun: Bir sayıyı bir sayı düzeninden
diğerine taşımak. Meselâ, 2'lik düzende 110010010 sayısını, 10'luk düzendeki
karşılığına gitmemiz gerekiyor. Ya da tam tersi yönde gidebilmemiz lazım. Her
düzenden her düzene geçebilmeliyiz.
Prosedürel (functional) bir geçmişten gelen programcı, hemen bu noktada ``ne
yapılması gerektiğine'' odaklanır, ve o üst fonksiyonu düşünmeye başlardı. Onun
bulması gerekenler, verileri alan, işleyen fonksiyonlar olacaktı, ve veriler bir
işlemden diğerine aktarılırken yolda değişe değişe istenen sonuca
ulaşılacaktı. Tasarım şöyle olabilir.
\begin{lstlisting}[language=Java, frame=none]
public static void main() {
double fromNumber;
double fromBase;
double toBase;
fromNumber = ...;
fromBase = ...;
toBase = ...;
...
}
\end{lstlisting}
İyi bir prosedürel programcı olarak hemen ana metotu koyduk. Uygulama için
gereken girdileri burada zaten alıyoruz. O zaman bu girdileri ne yapacağımızı
tasarlamamız gerekiyor. Hemen bir takım alt metotları kodlamaya başlıyoruz. Bir
sayıyı bir düzenden diğerine çevirirken ara seviye olarak onluk düzene gitmemiz
gerekiyor, çünkü onluk düzenden diğerlerine nasıl geçeceğimizi biliyoruz. O
zaman ilk önce, onluk düzene geçen metotu çağırmamız ve kodlamamız gerekiyor.
\begin{lstlisting}[language=Java, frame=none]
public static void main() {
String fromNumber;
int fromBase;
int toBase;
fromNumber = ...;
fromBase = ...;
toBase = ...;
int fromNumBase10 = convertToBaseTen(fromNumber);
}
public double convertToBaseTen(double fromNumber) {
...
}
\end{lstlisting}
Sonra bu ele geçen sayıyı, yeni düzene çevirecek fonksiyona göndereceğiz.
\begin{lstlisting}[language=Java, frame=none]
public static void main() {
String fromNumber;
int fromBase;
int toBase;
fromNumber = ...;
fromBase = ...;
toBase = ...;
int fromNumBase10 = convertToBaseTen(fromNumber);
String toNumber = convertToBase(fromNumBaseTen, toBase);
}
public int convertToBaseTen(double fromNumber) {
..
return numBaseTen;
}
public String convertToBase(int fromNumBaseTen, int toBase){
...
return toNum;
}
\end{lstlisting}
Bu yöntem oldukça kalabalık bir koda sebebiyet verdi. Özellikle ana metot daha
programın başında neredeyse yapılabilecek her çağrıyı yapıyor, ve tüm gereken
değerleri o hatırlıyor. Bundan daha iyi bir tasarım yapamaz mıydık?
Nesnesel tasarımı deneyelim. Nesnesel yöntemdeki prensipleri
hatırlayalım. Yukarıdan aşağı değil, aşağıdan yukarı gidiyoruz. Bunu yaparken
tasarladığımız metotları, bir class'ın alışveriş listesi gibi görüyoruz. Bir
class, bir uygulama içinde birden fazla rol oynayabilir. O zaman uygulamada bu
tanıma uyan class nedir?
Bir sayı! Evet, bize gereken {\em kendini} onluk düzene, ya da {\em kendini}
onluk düzenden başka bir düzene çevirebilecek olan, ve hangi sayı düzeninde
olduğunu {\em kendi bilen} bir sayı class'ıdır. Bir sayı class'ı, uygulama
sırasında birçok değişik rol oynayabilir; Çevirilen rolü oynayan bir nesne,
kendini onluk düzenden gösterebilecek, hedef rolünü oynayan nesne ise, hangi
düzende olması gerektiğini bilen, ve çevirim için kendini önce onluk düzene,
sonra gereken hedef düzene çevirmeyi bilecek bir nesne olacaktır. Bu açıdan
bakılınca sayı class'ı üzerindeki metotlar bir alışveriş listesidir. Üst nokta
düşünmeden bu metotları koyduk, yâni uygulamamız artık tepe noktadan kontrol
edilen bir çağrı zinciri değil, {\em iki nesnenin rol aldığı bir simulasyon}
hâline geldi. Kodu yazalım:
\begin{lstlisting}[language=Java, frame=none]
public class Number {
String value;
int base;
public Number(String value, int base) {
}
public Number(int base) {
}
public int toBaseTen() {
..
return numInTen;
}
public String getValue() {
return value;
}
public void convert(Number from) {
//
// Çevirilecek sayı: from.toBaseTen()
// Hedef: this.base
//
}
}
\end{lstlisting}
Gördüğümüz gibi kodlama açısından neredeyse aynı olan metotlar doğru class
üzerinde gelince isimleri daha temiz hâle geldi. Hangi düzende olduğunu bilen
bir nesneye \PVerb!convert! çağrısı gelince ve parametre olarak bir diğer
\PVerb!Number! nesnesi verilince bunun anlamı çok nettir; Parametre olarak gelen
\PVerb!Number!'daki sayı düzeninden kendi içimizde tuttuğumuz sayı düzenine
geçmek istiyoruz.
Ayrıca bu yeni tasarımda, çevirilecek ve hedef sayılar hakkındaki bilgileri ana
metotta duran değişkenlerde tutmamız gerekmiyor. Her class, kendisi hakkında
bilgileri kendi tutacaktır. Bunlar, sayı değeri ve hangi sayı düzeninde
olunduğudur.
Bu yeni tasarımı kodladıktan sonra, {\em en son olarak} ana metotu
kodlayabiliriz. Ana metotun ne kadar daha temiz olduğunu göreceğiz.
\begin{lstlisting}[language=Java, frame=none]
public class App {
public static void main(..) {
Number from = Number(..,..);
Number to = Number(..);
to.convert(from);
System.out.println(to.getValue());
}
}
\end{lstlisting}
Görüldüğü gibi çevirilecek sayı, hangi düzende olduğu bilgisiyle beraber,
\PVerb!from! referansıyla erişilen \PVerb!Number! nesnesi içinde
tutulmaktadır. Hedef sayı düzeni, \PVerb!to! referansı ile erişilen
\PVerb!Number! içinde tutulmaktadır. Sayı değeri ve düzeni, beraber, aynı modül
içinde durmaktadırlar, ve bu durum ana modülü rahatlatmış, ve genelde kod bakımı
açısından bir ilerleme sağlamıştır.
Diğer bir ilerleme, \PVerb!convert! adlı metota hedef sayının bir \PVerb!Number!
parametresi olarak gelmesidir. Eskiden \PVerb!String! ve \PVerb!int! tipinde
değerler geliyordu, ve bu basit tiplere bakarak neyin ne olduğu tam
anlaşılamıyordu. Yeni yöntem sayesinde daha üst seviyede olan \PVerb!Number!
tipleri metot'tan metota gönderilmektedir, ve bu da kodun anlaşılırlığı
açısından iyidir.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section[Tasarım Düzenleri][TASARIM DÜZENLERİ]{Tasarım Düzenleri (Design Patterns)}
Nesnesel dillerin programcıya sağladığı belli silahlar/yetenekler (capabilities)
vardır. Bu yetenekler nesnesel yöntemin tabiatından gelirler, meselâ çokyüzlülük
(polymorphism) ve miras alma (inheritance) yeteneklerinin standart örneği, üst
seviye bir class'tan miras alan alt seviye gerçek (concrete) class'ların, üst
seviye referansı üzerinden erişilmesi örneğidir. Bu kullanımda, meselâ, aynı
\PVerb!List! üzerinde aynı üst seviye tipinde ama gerçek tipi değişik alt
tiplerde olan nesnelerin örneği gösterilebilir. Tüm bu alt tipte nesneler
üzerinde, üst seviyedeki bir metot alta çokyüzlülük ile intikal etmiştir, ve bu
metotun gerçekleştirimi her alt tipte değişik olması sebebiyle, üst seviye tip
üzerinde yapılan çağrılar, değişik davranışta bulunurlar. Bu kullanım, ufak
çapta bir tasarım düzenidir.
Bu tasarım düzenini kullanan bir uygulama örneği şöyle olabilir: Ekranda üçgen,
çember ve poligon çizmeye izin veren bir çizim programını düşünün. Mouse ile
tıkladığımız noktada, hangi figür mod'undaysak (\PVerb!Circle!,
\PVerb!Triangle!, \PVerb!Polygon!), o figür, o noktada, o nesnenin \PVerb!draw!
metotu üzerinden çizilecektir.
Buna ek olarak çizim programı, ``ekranı yenile'' komutuna hazır olmak için
ekrandaki figürlerin bir listesini tutmalıdır. Böylece ekrana ``tekrar çiz''
(\PVerb!refresh!) komutu verildiğinde, herşey silinip tüm figürler üzerinde
tekrar \PVerb!draw! metotu çağırılabilecektir. \PVerb!List! nesnesini taşıyan
\PVerb!Screen! (ekran) nesnesi, o listeyi tekrar gezdiğinde, listedeki
elemanları \PVerb!Figure! tipine dönüştürmesi (cast) yeterlidir. Çünkü
\PVerb!Figure! üzerinde \PVerb!draw! interface metotu tanımlıdır, ve çokyüzlülük
kanunlarına göre \PVerb!Figure! tipi üzerinden baktığımız bir nesnenin
\PVerb!draw! metotunu çağırmak için o nesnenin gerçek tipine inmemiz
gerekmez. Nesnesel yöntem, \PVerb!draw! çağrısını gerekli koda otomatik olarak
götürecektir. Böylece alttaki kod parçası
\begin{lstlisting}[language=Java, frame=none]
for (int i = 0; i < figureList.size(); i++) {
Figure figure = (Figure)figureList.get(i);
figure.draw();
}
\end{lstlisting}
ile tüm figürleri tekrar çizmemiz mümkün olacaktır.
\begin{figure}[!hbp]
\center{
\scalebox{0.40}{
\includegraphics{./images/figures.eps}
}
}
\caption{Şekiller Nesne Diyagramı}
\end{figure}
Demek ki tasarım düzenleri, baz seviyedeki nesnesel teknikleri kullanarak,
değişik şekillerde birleştirerek oluşturulmuş üst seviye tekniklerdir. Nesnesel
tekniklerin ve dillerin anlatıldığı her kitapta aslında tasarım düzenleri de bir
yandan anlatılmaktadır, sadece en direk ve dil özelliğini en basitçe
vurgulayacak olan çeşitleri ön planda olmaktadır.
\subsection{Kullanılan Düzenler}
Gamma ve arkadaşlarının çıkardığı Tasarım Düzenleri \cite{designpatterns} adlı
kitap, yazarlarının projelerinde üst üste kullandığı ve temel kullanımlardan
daha değişik kodlama ve tasarım düzenlerini dünyaya tanıtmıştır. Bu kitaptaki
teknikler bir yana, bir tasarım düzeninin nasıl bulunup ortaya konulacağını
ortaya koyması açısından kitap daha da faydalı olmuştur. Kurumsal programcılar
için yapmamız gereken tek uyarı, bu kitaptaki paylaşılan tasarım numaralarının
pür dil seviyesinde olmasıdır (ve kurumsal uygulamalarda dış teknolojinin
önemini artık biliyoruz). Tasarım Düzenleri kitabındaki çözümlerin neredeyse
tamamı ``aynı JVM, yerel metot çağrısı'' öngörüsüyle yazılmış
tekniklerdir. Kurumsal yazılımlarda işe yarayan Gamma TD teknikleri altta
görülebilir:
\begin{itemize}
\item Command
\item Template Method
\item Facade
\item Singleton
\end{itemize}
Bu düzenleri kısaca açıklayalım (Command düzeni haricinde, çünkü bu düzen
\ref{dist:command} bölümünde ayrtıntısıyla anlatılmıştır).
\subsubsection{Template Method}
\begin{figure}[!hbp]
\center{
\scalebox{0.40}{
\includegraphics{./images/templatemethod.eps}
}
}
\caption{Template Method Nesne Tasarımı}
\end{figure}
Arasında miras ilişkisi olan üst ve alt class tiplerini kodlarken, ileride yeni
eklenebilecek alt class tiplerine yarayacak olan metotların üst class'a
çekilmesi gerekir. Bu metotlar, böylece her yeni alt class tarafından
paylaşılabilmiş olacaktır.
Ortak metotları üst class'a koyduğumuzda, bazen, kodun içinde ``genel olmayan''
ve ``her class için değişik olması gereken'' bir bölüm gözümüze çarpabilir. Eğer
böyle bir bölüm mevcut ise, tüm metotu tekrar aşağı, alt class'a doğru itmeden,
Template Method düzenini kullanabiliriz. Bu düzene göre, her alt class'ta
değişik olabilecek ufak kod parçası ayrı bir metot içine konarak, üst tipte
soyut (\PVerb!abstract!) olarak tanımlanır. Böylece alt class'lar bu metotu
tanımlamaya mecbur kalırlar. Ve alt class'taki özel bölüm tanımlama/kodlaması
yapılır yapılmaz üst class'taki metotlar genel bölümlerini işletip, özel bölüm
için alt sınıftaki metota işleyişi devredebilirler. Bundan kendilerinin
haberleri bile olmaz, çünkü onlar kendi seviyelerindeki \PVerb!abstract! metotu
çağırmaktadırlar.
Template Method, üst seviyede bir metot iskeleti tanımlayıp alt class'lara
sadece ufak değişiklikler için izin verilmesi gerektiği durumlarda
kullanışlıdır.
\subsubsection{Facade}
\begin{figure}[!hbp]
\center{
\scalebox{0.45}{
\includegraphics{./images/facade.eps}
}
}
\caption{\label{object:designpatterns:usefulpatterns:facade} Facade}
\end{figure}
Bir özelliği işletmek için birçok nesneyi ardı ardına çağırmak gerekiyorsa,
özellik kullanımını tek bir giriş class'ına alarak arka plan çağrıları giriş
class'ına yaptırmak faydalı olabilir. Tasarım Düzenleri kitabında bu giriş
class'ına Facade adı veriliyor. Facade'ın faydası, karmaşıklığı azaltarak bir
sistemin dış dünyaya gösterdiği arayüzü basitleştirmeyi amaçlamasıdır. Meselâ
Şekil \ref{object:designpatterns:usefulpatterns:facade} üzerinde gösterilen
nesne modeli \PVerb!javac! gibi bir derleyici (compiler) sistemin tasarım
modelidir. Bu modelde görüldüğü gibi birçok iş yapan class'lar mevcuttur. Fakat
dışarıdan bağlanan için bu alt seviye nesneleri yaratıp teker teker çağırmak
şart değildir, dışarısı için tek giriş nesnesi olan \PVerb!Compiler!'ı kullanmak
hem kullanım, hem de kod bakımı açısından daha rahat olacaktır.
\subsubsection{Singleton}
\begin{figure}[!hbp]
\center{
\scalebox{0.40}{
\includegraphics{./images/singleton.eps}
}
}
\caption{Singleton}
\end{figure}
Uygulamamızda bir class'tan sadece bir nesne olsun istiyorsak ve bunu mimari
olarak kodu kullanan her programcı üzerinde zorlamak istiyorsak, o zaman
Singleton düzenini kullanabiliriz. Bu düzene göre Singleton olmasını istediğimiz
class'ın ilk önce kurucu metotunu (constructor) Java \PVerb!private! komutu ile
dışarıdan saklarız. Böylece kurucu metot sadece class'ın kendisi tarafından
kullanılır olur. Bunu yapmazsak herkes class'ı istediği gibi alıp birden
fazla nesnesini kullanabilirdi.
Kurucu metotu sakladığımız için, bir yaratıcı metotu bir şekilde sağlamak
zorundayız. Singleton class'larında bu metot tipik olarak \PVerb!instance!
adında bir yaratıcı metot olur. Bu metot, \PVerb!static! olmalıdır çünkü
Singleton class'ından hâlen tek bir nesne bile mevcut değildir ve ilk
çağrılabilecek metot bu yüzden \PVerb!static! olmalıdır. Bu metot, gerçek
nesneyi \PVerb!static! olan diğer bir \PVerb!private! değişken üzerinde
arar/tutar, buna \PVerb!uniqueInstance! (tekil nesne) adı verilebilir. Eğer
\PVerb!uniqueInstance! üzerinde bir nesne var ise, o döndürülür, yok ise, bir
tane yaratılıp döndürülür. Önce mevcudiyet kontrolü yapıldığı için nesnenin bir
kez yaratılması yeterli olmaktadır, ilk \PVerb!new! kullanımından sonra
Singleton'dan geriye gelen nesne hep aynı olacaktır.
\subsection{POJO'lar ve İşlem Mantığı}
Kalıcılık aracı Hibernate, ya da diğer POJO bazlı teknolojileri kullanırken, bir
modelleme tavsiyesini aklımızda tutmalıyız; POJO'lar diğer class'lar gibi bir
class'tırlar, üzerlerine \PVerb!get! ve \PVerb!set! haricinde çetrefil, işlem
mantığı metotları ve fonksiyonları konulmasında bir zarar yoktur.
Hâtta daha ileri giderek bunu yapılmasını şiddetle tavsiye edeceğiz. Son zamanda
popüler olan veri, uzaktan nesne çağrısı yapma teknolojilerinin POJO bazlı
olmaya başlaması ile oluşan bir izlenim, POJO'ların aptal bir şekilde bırakılıp
sadece veri transferi için kullanılmaya başlanmasıdır. Buna hiç gerek yoktur
çünkü bir eğer bir POJO, uygulamamızın verisini tutan {\em yer} ise ve bir
class, (nesnesel modelleme açısından) veriler ve işlemleri birarada tutan bir
birim ise, o zaman işlemlerimizi POJO'lardan ayırıp bambaşka bir ``işlem
class'ı'' içine gömmemize gerek kalmayacaktır. İşlemimiz bir POJO içindeki
veriyi kullanıyor ve modelleme açısında bu temiz bir sonuç veriyor ise, işlemin
POJO class'ı içine koyulmasında hiçbir sakınca yoktur.
\subsection{Diğer Düzenler}
Gamma ve arkadaşlarının Tasarım Düzenleri kitabındaki düzenlerin pek azının
kurumsal uygulamalar için faydalı olmasına rağmen tasarım düzenleri kavramı, bir
prensip ve düşünce sistemi olarak yazılım dünyası için faydalıdır. Hatta
söylenebilir ki matematiksel bazı olmayan yazılım mühendisliğine disiplinli ve
metodik bir şekilde yaklaşmak isteyenler için tasarım düzenleri ve işleyen
yöntemler (best practices), neredeyse izlenebilecek ``en formele yakın''
yöntemlerdir.
Bu bağlamda, okuduğunuz bu kitap bize göre faydalı tasarım düzenleri ve işleyen
yöntemlerin toplamıdır. Fakat bu kitapta Gamma kitabına kıyasla daha dış
teknolojiye yakın tasarım düzenleri sunulmaktadır. Bu sebeple buradaki tasarım
düzenleri mimari(architecturel) çözümler kategorisine girebilecek
tavsiyelerdir. Bunun sebepleri bizim şahsi proje tecrübemize dayanıyor; Tasarım
Düzenleri kitabının neredeyse her kelimesini dikkatle izlediğimiz ve yine de
birçok badire atlattığımız bir projemizin sonunda teknik liderimiz ve arkadaşım
Jim D'Augustine yakınarak şöyle demişti: ``Bize tasarım düzenleri değil, mimari
düzenler lâzım!''
Son olarak, Karşı Düzen (Anti Pattern) akımından bahsedelim. Karşı Düzenler,
normâl tasarım düzenleri aksine ne yapılması gerektiğini değil, {\em ne
yapılmaması gerektiğini} tavsiye ederler. Örneklerden bir tanesi DTO (Data
Transfer Object) karşı düzenidir; Bilindiği gibi DTO tekniğini kullanlar, servis
tarafından yapılacak her transfer için sadece get/set metotlarından oluşan bir
veri class'ı (Hibernate buna POJO diyor) yazmayı salık verir. DTO karşı düzeni
DTO class'larının gereksiz olduğunu söylemektedir. Buna biz de katılıyoruz,
çünkü artık Hibernate gibi modern yaklaşımlar sayesinde veri alışverişinin
tamamen POJO'lar üzerinden olduğu için, ek veri transfer class'larına gerek
kalmamıştır. Servis tarafı ve veri tabanı arasında kullanılan nesneler, büyük
bir rahatlıkla başka yerlere veri transfer etmek için de
kullanılabilirler. (Tarihi olarak insanlar DTO kullanımına herhalde Entity
Bean'lerin hantal yapısı yüzünden mecbur olmuştu, fakat Entity Bean'ler artık
teknik olarak emekli edildiğine göre, DTO'ya olan ihtiyaç ta ortadan
kalkmıştır).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section[Mimari][MİMARİ]{Mimari} \label{object:architecture}
Nesnesel tasarımda birkaç class'ı içeren bir tasarım, nokta vuruş çözümü yâni
ufak çapta bir tasarımdır. Bu teknikler uygulamanın ufak bir bölümününün çözümü
için kullanılan nesnesel {\em numaralardır}. Fakat uygulamadaki tüm özelliklerin
kodlanması için bazı genel kurallar koyan bir {\em alt tabaka} olması
şarttır. Bu tabaka bazı kodlama kalıplarının içeren ve programcıların miras
alması gereken bir üst class, ya da çağırılması proje teknik lideri tarafından
herkese duyurulmuş olan yardımcı class'lar toplamı olabilir. Literatürde bu
şekilde genel kurallar koyan class'ların ve kod parçalarının toplamına
\textbf{mimari} diyoruz. Eğer \PVerb!StrutsHibAdv! örnek projesini düşünürsek,
bu projenin mimarisini şöyle tarif edebiliriz:
\begin{quote}
``Tüm Hibernate işlemlerinden önce \PVerb!Session! açılması, ve transaction
başlatılması \PVerb!HibernateSession! yardımcı class'ı üzerinden
yapılacaktır. Tüm Struts Action'ları \PVerb!org.mycompany.kitapdemo.actions!
altında olacak, ve her sayfa \PVerb!header.inc! dosyasını kendi kodları içine en
tepe noktada dahil edecektir. Basit veri erisimi haricinde sorgu içeren tüm veri
erişim işlemleri, bir \PVerb!DAO! kullanmaya mecburdur; Meselâ \PVerb!Car!
ağırlıklı sorgular için \PVerb!CarDAO! kullanılması gibi. Her \PVerb!DAO!,
kurucu metotu içinde bir Hibernate transaction başlatmalıdır, ama commit
\PVerb!DAO! commit yapmayacaktır. Struts Action'lar da commit yapamazlar. Commit
yapmak, bir Servlet filtresi olan \PVerb!HibernateCloseSessionFilter!'nin
görevidir, ve bu filtre her \PVerb!.do! soneki için yine herkesin kullandığı
\PVerb!web.xml! içinde aktif edilmiştir. Aynı filtre, \PVerb!Session!
kapatmak, ve hata (exception) var ise, o anki transaction'ı rollback etmek ile
de yükümlüdür''.
\end{quote}
Görüldüğü gibi bu kurallar projedeki her programcının bilmesi gereken
kurallardır. Projeye ortasında katılan bir programcı, hemen bir Struts Action
yazmaya başlayıp içine alânen bir takım JDBC kodları yazarak veri tabanına
erişmeye çalışmayacaktır. Bu projenin kurallarına, {\em mimarisine} göre, Struts
Action'da \PVerb!HibernateSession! üzerinde açılan \PVerb!Session! ile,
Hibernate yöntemleri üzerinden veri tabanına erişilecektir. Yine aynı kurallara
göre yeni programcı \PVerb!commit!, ve \PVerb!close! çağrılarını elle yapmaktan
men edilmiştir. Projenin mimarisi, bu çağrıları merkezileştirmiş, ve kodun
geneline bu şekilde bir temizlik sağlamıştır. Yeni gelen programcının bu kuralı
takip etmesi beklenecektir.
Bu şekilde tarif edilen mimarilerin, kod temizliği açısından olduğu gibi, proje
idaresi yönünden de etkileri olduğunu anlamamız gerekiyor. Bir mimari bağlamında
bazı kuralların konulması ve bazı kullanımların merkezileştirilmesi demek,
uygulamamızda önce bitmesi gereken parçanın ``mimari kısmı'' olduğu sonucunu
getirir. Proje idaresi açısında mimari, kodlama açısından seri üretime geçmeden
önce bitmesi gereken şeydir. Eğer seri üretimden çıkan her ürünü bir özellik
olarak düşünürsek, mimari de fabrika olacaktır. Tabii bu analojiyi dikkatli
anlamak gerekiyor, sonuçta işler hâldeki bir program da bir fabrika gibi
görülebilir; Bizim burada bahsettiğimiz programın işleyişi değil, o programın
kodlama aşamasında programcıların kodlama eforudur.
Proje idaresi bakımından mimarinin önce bitmesine karşı bir argüman, mimarinin
özellikler kodlanırken bir yan ürün olarak kendi kendine çıkması beklentisidir;
Fakat eğer mimari kod temizliği, hata azaltımı gibi getirmesi açısından önemli
ise, önceden mevcut olması gereken bir kavram olduğu ortadadır. Bir mimariyi
projenin ortasında kodlarımıza sonradan empoze etmeye karar vermişsek, bu durum
mevcut olan kodlar üzerinde yapılması gereken büyük bir değişiklik anlamına
gelebilir, ve bu değişiklik için harcanacak efor, o teknik ilerlemenin
getireceği herhangi bir avantajı silip yokedebilir. Bu sebeple mimarinin
projenin başında hazır olması önemlidir.
Mimariyi tasarlamak, her projede teknik liderin görevidir. Ayrıca mimari ortaya
çıkartıldıktan sonra kuralların takip edildiğinin kontrolü de teknik lider
üzerinde olacaktır. Öyle ki, proje bittiğinde tüm kod bazı sanki tek bir kişi
yazmış gibi gözükmelidir. İyi bir mimari kodda tekrarı azaltacak, kullanım
kalıplarını ortaya koyarak yeni katılan programcılara yön gösterecek ve hata
yapma ihtimallerini azaltacaktır. Mimari önceki projelerde alınan dersleri de
yansıtan bir kurallar toplamıdır.
Bunun haricinde her özelliği (functionality) kodlayan programcı kendi
istediği gibi kodlamakta serbesttir.
Tasarım düzenleri ile mimari arasındaki ilişki şöyledir: Mimarimizin bir kısmı
içinde bir tasarım düzeni bir kural olarak ortaya çıkabilir. Örnek olarak
\ref{dist:command} bölümünde tarif edilen mimari, \PVerb!Command! tasarım
düzeninin her uzaktan nesne erişim gerektiren durum için kullanılmasını mecbur
kılmış, böylece bir mimari seçim hâline gelmiştir. Fakat bir tasarım düzeni
hiçbir mimarinin parçası olmadan, tek bir özellik için kendi başına da
kullanılabilir. Kısacası mimari genelde birçok kişiyi etkileyen, ve genel olan
bir kavramdır.