Alıntı (Salih Dinçer):
> Benim eleştirdiğim ve belki de anlamaya çalıştığım şeyler kalıplar.
Bunu kırıcı olmadan nasıl söyleyeceğimden emin değilim ama "eleştirme" ve "anlamaya çalışma" kavramlarını aynı cümlede kullanıyorsun. :) MVC'nin büyük bir esprisi olmadığını da bu konunun 9 numaralı mesajında söylemiştin. "MVC olayını öğrenelim" dedikten sonra daha sekiz saat bile geçmeden... :) Bence biraz yavaşlayalım ve önce olayı tam olarak anlayalım ve hatta biraz deneyim kazanalım; ondan sonra fikirlerimizi tartalım.
Ben MVC kalıbını uygulayanların körlemesine uyguladıklarını düşünmüyorum. Mutlaka bir yararı vardır. Aslında yararları tanımında açıkça geçiyor: Programın farklı işler yapan bölümlerini birbirlerinden ayırmak. Örneğin program mantığı verinin nasıl görüntülendiğini bilmezse ona lego parçası gibi farklı görüntüleyiciler bağlayabiliyoruz.
Alıntı:
> programcıyı, kalıplaşmış bir yapı içinde sıkıştırıyor ve onu üç kategoriye göre seçim yapma zorunluluğunda bırakıyor.
O MVC'ye tamamen ters bir görüş. MVC'nin getirdiği yarar sıkıştırmak değil, tam tersine serbest bırakmaktır.
Ayrıca "kalıp" kavramıyla ilgili bir konuyu hatırlatmak istiyorum. Pattern denen bu kalıplar hiçbir zaman birileri oturup daha iyi olduğuna karar verdikleri için ortaya çıkmıyor. Bunlar başka insanların birbirlerinden bağımsız olarak keşfettikleri, yararlarını gördükleri, ve en sonunda da isimlendirdikleri olgulardır. Bir anlamda kalıplar kendiliklerinden ortaya çıkarlar.
Alıntı:
> MVC konusu ne zamandır kafamdaydı ve onun sayesinde bana göre olmadığını anladım.
Haklı olabilirsin ama ben haddim olmayarak henüz iyi bir uygulamasıyla karşılaşmamış olduğun için öyle düşündüğünü düşünüyorum.
MVC için bir deneme de ben yapacağım. Hatırlayalım:
-
Model, programın asıl işini yürütüyor ve programdaki bilgilerin sahibi.
-
View, programın durumunun dışa vurumunu üstleniyor.
-
Controller, programın kullanıcısıyla etkileşimini hallediyor ve Model'ın değişmesini sağlıyor.
Örnek olarak Salih'in hatırlattığı İşlevler Bölümü'nün ikinci problemini düşünüyorum:
http://ddili.org/ders/d/islevler.html
Tabii oradaki program MVC ayrımı gözetilmeden yazılmıştır ve zaten kullanıcıyla etkileşim diye bir kaygısı da yoktur.
Model olarak oradan esinlendiğim şu Kağıt modülünden başlayalım:
class Kağıt {
dchar[][] kareler; // Kağıdı oluşturan kareler (tuval)
Kağıtİlgilisi[] ilgililer; // Bu kağıttaki değişiklerden haberdar olmak
// isteyen ilgililer (observers)
this(size_t satırAdedi, size_t sütunAdedi, dchar zemin) {
auto boşSatır = new dchar[sütunAdedi];
boşSatır[] = zemin;
foreach (i; 0 .. satırAdedi) {
this.kareler ~= boşSatır.dup;
}
}
// Bu, "observer pattern"da adı geçen registerObserver() işlevidir
void ilgiliyiTanı(Kağıtİlgilisi ilgili) {
this.ilgililer ~= ilgili;
}
// Kağıdı verilen beneklerle doldurur
void boya(dchar[Yer] benekler) {
foreach (yer, renk; benekler) {
kareler[yer.satır][yer.sütun] = renk;
}
// Yeni benekler oluştuğunu ilgililere haber ver
foreach (ilgili; ilgililer) {
// Bu, "observer pattern"daki notify()'dır
ilgili.değişti(kareler);
}
}
}
Üzerinde uzunca konuşmaya değecek bir sınıf değil. Sunduğu iki olanak var:
Birincisi, kağıdın durumunda değişiklik yapmayı sağlayan boya() işlevi. O işlev controller'ın bir kağıt nesnesini değiştirmesi için yeterlidir. Bu işlevin üzerinde durmaya gerek olmadığını düşünebiliriz. Herhalde nesnenin durumunda yapılan değişikliklerin bir üye işlev ile sağlanması gerektiğini artık kanıksamış olmalıyız. (Bunun alternatifi zararlı olabilir: Örneğin kullanıcılar 'noktalar' üyesine doğrudan da erişebilirler ve değiştirebilirlerdi.)
Sunduğu ikinci olanak da kendisine tanıştırılan Kağıtİlgilisi nesnelerini aklında tutması ve kendisinde oluşan değişiklikleri onlara haber vermesi. Bunu, boya()'nın en sonunda yapıyor.
Yukarıdaki Model'ın yararı şu: Bu sınıf yalnızca kendi işini biliyor. Ne kendisini kullananların ayrıntılarını biliyor ne de kendisiyle ilgilenenlerin ayrıntılarını.
Şimdi kağıt nesnelerini kullanmayı bilen bir controller'a bakalım:
class RasgeleBoyayan {
size_t satırAdedi;
size_t sütunAdedi;
size_t rasgeleNoktaAdedi;
this(size_t satırAdedi, size_t sütunAdedi, size_t rasgeleNoktaAdedi) {
this.satırAdedi = satırAdedi;
this.sütunAdedi = sütunAdedi;
this.rasgeleNoktaAdedi = rasgeleNoktaAdedi;
}
void kullan(Kağıt kağıt) {
dchar[Yer] noktalar;
foreach (i; 0 .. rasgeleNoktaAdedi) {
immutable yer = Yer(uniform(0, satırAdedi), uniform(0, sütunAdedi));
immutable renk= 'a' + uniform(0, 26);
noktalar[yer] = renk;
}
kağıt.boya(noktalar);
}
}
Dışarıdan birisi ona "bu kağıdı kullan" diyor ve o da rasgele renkli noktalar oluşturarak kağıdı o noktalarla boyuyor. Kağıt hakkında tek bildiği, Kağıt.boya işlevi. Bu sınıfın da view nesnelerinden en ufak bir haberi yok. Tek yaptığı, model'da değişiklikler oluşturmak.
Son olarak bir view sınıfına bakalım:
class İkiBoyutluGösterici : Kağıtİlgilisi {
void değişti(const dchar[][] noktalar) {
foreach (i, satır; noktalar) {
writefln("%3s| %s", i, satır);
}
}
}
Bu sınıf Kağıtİlgilisi arayüzünü gerçekleştiriyor. İkiBoyutluGösterici.değişti() işlevi Kağıt tarafından "ben değiştim" anlamında çağrılacak.
(Not: Değişiklikler başka biçimlerde de bildirilebilirdi. Ben örnek kısa olsun diye iki boyutlu dchar dizisini olduğu gibi geçirmeye karar verdim.)
Bu view sınıfı da çok yararlıdır: Ne model'dan haberi var ne de controller'dan. Tek bildiği, kendisine verilen değişiklikleri görüntülemek. (Tekrarlamak istiyorum: Bu view sanki kağıdın dchar[][] türündeki üyesinden haberliymiş gibi algılanmasın; kısa olsun diye öyle yaptım. Değişiklikleri bambaşka bir tür olarak da alabilirdi.)
Şimdi bu tasarımın getirdiği esnekliğe bakalım.
main() işlevi model, view, ve controller nesneleri oluşturarak onları birbirlerine tanıştırıyor ve programı işletiyor:
// Model: Bir kağıt nesnesi oluşturuyoruz
auto kağıt = new Kağıt(satırAdedi, sütunAdedi, zeminRengi);
// View: İki farklı view nesnesini kağıda tanıtıyoruz
kağıt.ilgiliyiTanı(new İkiBoyutluGösterici());
kağıt.ilgiliyiTanı(new ListeleyenGösterici(zeminRengi));
// Controller: Kağıtta değişiklikler yapacak olan nesneyi oluşturuyoruz
enum rasgeleBenekAdedi = 10;
auto boyacı = new RasgeleBoyayan(satırAdedi, sütunAdedi, rasgeleBenekAdedi);
boyacı.kullan(kağıt);
O main kullanıldığında model iki farklı ilgili nedeniyle iki farklı biçimde görüntüleniyor:
'./mvc_deneme
0| .....q..q.
1| .........k
2| .........h
3| .r....r...
4| ...t...yx.
=== Zemin Renginde Olmayan Noktalar ===
0,5: q
0,8: q
1,9: k
2,9: h
3,1: r
3,6: r
4,3: t
4,7: y
4,8: x
'
Başka view ve controller nesneleri ise tamamen farklı bir program oluşturmaya yetecektir. Aşağıdaki programda iki farklı view ve iki farklı controller var. Bütün programı oluşturan dosyaları parçalar halinde buraya yazıyorum:
"boyaci.d" iki farklı controller'dan oluşuyor:
module boyaci;
import std.stdio;
import std.random;
import kagit;
import yer;
class RasgeleBoyayan {
size_t satırAdedi;
size_t sütunAdedi;
size_t rasgeleNoktaAdedi;
this(size_t satırAdedi, size_t sütunAdedi, size_t rasgeleNoktaAdedi) {
this.satırAdedi = satırAdedi;
this.sütunAdedi = sütunAdedi;
this.rasgeleNoktaAdedi = rasgeleNoktaAdedi;
}
void kullan(Kağıt kağıt) {
dchar[Yer] noktalar;
foreach (i; 0 .. rasgeleNoktaAdedi) {
immutable yer = Yer(uniform(0, satırAdedi), uniform(0, sütunAdedi));
immutable renk= 'a' + uniform(0, 26);
noktalar[yer] = renk;
}
kağıt.boya(noktalar);
}
}
// Noktayı kağıt üzerinde yürütür; noktanın daha önceden geçtiği yerleri de
// belirtir.
class NoktaYürüten {
enum uçRengi = 'X';
enum gövdeRengi = 'o';
Yer uç;
this(Yer başlangıç) {
this.uç = başlangıç;
}
void kullan(Kağıt kağıt) {
kağıt.boya([ uç : uçRengi ]);
writeln("Lütfen noktanın yolunu belirtiniz");
bool devam_mı = true;
while (devam_mı) {
write("(w: Yukarı, z: Aşağı, a: Sola, s: Sağa, 0: Çıkış) ? ");
dchar yön;
readf(" %s", &yön);
immutable eskiUç = uç;
switch (yön) {
case 'w': uç = uç.yukarıdaki; break;
case 'z': uç = uç.aşağıdaki; break;
case 'a': uç = uç.soldaki; break;
case 's': uç = uç.sağdaki; break;
case '0': devam_mı = false; break;
default: writeln("Hatalı giriş!"); break;
}
kağıt.boya([ eskiUç : gövdeRengi, uç : uçRengi ]);
}
}
}
"gosterici.d" iki farklı view'dan oluşuyor:
module gosterici;
import std.stdio;
import kagit_ilgilisi;
// Kağıdın durumunu satır bilgileri halinde gösterir
class ListeleyenGösterici : Kağıtİlgilisi {
dchar zeminRengi;
this(dchar zeminRengi) {
this.zeminRengi = zeminRengi;
}
void değişti(const dchar[][] noktalar) {
writeln("\n=== Zemin Renginde Olmayan Noktalar ===");
foreach (satır, satırNoktaları; noktalar) {
foreach (sütun, renk; satırNoktaları) {
if (renk != zeminRengi) {
writefln("%s,%s: %s", satır, sütun, renk);
}
}
}
}
}
// Kağıdı iki boyutlu olarak gösterir
class İkiBoyutluGösterici : Kağıtİlgilisi {
void değişti(const dchar[][] noktalar) {
foreach (i, satır; noktalar) {
writefln("%3s| %s", i, satır);
}
}
}
"kagit.d" Kağıt türünü tanımlıyor:
module kagit;
import kagit_ilgilisi;
import yer;
class Kağıt {
dchar[][] kareler; // Kağıdı oluşturan kareler (tuval)
Kağıtİlgilisi[] ilgililer; // Bu kağıttaki değişiklerden haberdar olmak
// isteyen ilgililer (observers)
this(size_t satırAdedi, size_t sütunAdedi, dchar zemin) {
auto boşSatır = new dchar[sütunAdedi];
boşSatır[] = zemin;
foreach (i; 0 .. satırAdedi) {
this.kareler ~= boşSatır.dup;
}
}
// Bu, "observer pattern"da adı geçen registerObserver() işlevidir
void ilgiliyiTanı(Kağıtİlgilisi ilgili) {
this.ilgililer ~= ilgili;
}
// Kağıdı verilen beneklerle doldurur
void boya(dchar[Yer] benekler) {
foreach (yer, renk; benekler) {
kareler[yer.satır][yer.sütun] = renk;
}
// Yeni benekler oluştuğunu ilgililere haber ver
foreach (ilgili; ilgililer) {
// Bu, "observer pattern"daki notify()'dır
ilgili.değişti(kareler);
}
}
}
"kagit_ilgilisi.d" kağıttaki değişiklerden haberdar olmak isteyenlerin gerçekleştirmeleri gereken arayüzü bildiriyor:
module kagit_ilgilisi;
// Bu, "observer pattern"daki observer'dır.
//
// Kağıt'taki değişikliklerden haberdar olmak isteyenlerin gerçekleştirmeleri
// gereken arayüz. Kağıt, ilgilileri haberdan etmek için değişti() işlevini
// çağıracak.
interface Kağıtİlgilisi {
// Bu, "observer pattern"daki notify() işlevidir.
void değişti(const dchar[][] noktalar);
}
"yer.d" satır ve sütun bilgisinden oluşan ve bir kaç yararlı işlev sunan bir tür içeriyor:
module yer;
struct Yer
{
size_t satır;
size_t sütun;
Yer yukarıdaki() const @property {
return Yer(satır - 1, sütun);
}
Yer aşağıdaki() const @property {
return Yer(satır + 1, sütun);
}
Yer sağdaki() const @property {
return Yer(satır, sütun + 1);
}
Yer soldaki() const @property {
return Yer(satır, sütun - 1);
}
}
"main.d" MVC tasarımının getirdiği esnekliği göstermek için birisi -version seçeneği ile seçilebilen iki farklı main() içeriyor:
module main;
import std.stdio;
import kagit;
import gosterici;
import boyaci;
import yer;
enum satırAdedi = 5;
enum sütunAdedi = 10;
enum zeminRengi = '.';
version (etkiles) {
void main()
{
// Model: Bir kağıt nesnesi oluşturuyoruz
auto kağıt = new Kağıt(satırAdedi, sütunAdedi, zeminRengi);
// View: Bir view nesnesini kağıda tanıtıyoruz
auto gösterici = new İkiBoyutluGösterici();
kağıt.ilgiliyiTanı(gösterici);
// Controller: Kağıtta değişiklikler yapacak olan nesneyi oluşturuyoruz
immutable ortaNokta = Yer(satırAdedi / 2, sütunAdedi / 2);
auto boyacı = new NoktaYürüten(ortaNokta);
boyacı.kullan(kağıt);
}
} else { // version
void main()
{
// Model: Bir kağıt nesnesi oluşturuyoruz
auto kağıt = new Kağıt(satırAdedi, sütunAdedi, zeminRengi);
// View: İki farklı view nesnesini kağıda tanıtıyoruz
kağıt.ilgiliyiTanı(new İkiBoyutluGösterici());
kağıt.ilgiliyiTanı(new ListeleyenGösterici(zeminRengi));
// Controller: Kağıtta değişiklikler yapacak olan nesneyi oluşturuyoruz
enum rasgeleBenekAdedi = 10;
auto boyacı = new RasgeleBoyayan(satırAdedi, sütunAdedi, rasgeleBenekAdedi);
boyacı.kullan(kağıt);
}
} // version
Derlemek için:
'dmd boyaci.d gosterici.d kagit.d kagit_ilgilisi.d main.d yer.d -ofmvc_deneme -unittest -property -w -gc -debug '
O satır yukarıda verdiğim çıktıyı üretir. Aşağıdaki satır ise kullanıcının programla etkileştiği ve ekranda yılan gibi ilerletebildiği bir nokta oluşturur (Ekran dışına çıkıldığında hata atıyor; kısa olsun diye onlarla ilgilenmedim):
'dmd ... -version=etkiles'
Ali
--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]