---
title: 'Model–Görünüm Aileleri'
source: 'https://academia.sh/tr/kurslar/mimari-bicemler/model-gorunum-aileleri'
course: 'Mimari Biçemler'
language: tr
updated: '2026-08-17T18:10:36+00:00'
license: 'CC BY-SA 4.0'
---

# Model–Görünüm Aileleri

Aynı teklif ekranının denetleyici, sunucu ve görünüm modeli varyantlarıyla üretilmesi ve sorumluluk dağılımının sayıya dökülmesi: gösteren dosyanın kaç alan adını tanıdığı, ithal kapanışının kaç dosya olduğu, sınırdan kaç çağrı ve kaç bayt geçtiği, sıra ve biçim kararlarının hangi dosyalarda durduğu.

Buraya kadarki iki biçem aynı türden birimler arasındaki etkileşimi ele aldı: konuşan tarafların
ikisi de ücret hesaplayan ya da tarife duyuran birimlerdi, aradaki fark yalnız kimin
başlattığıydı. Kitaplıkta bir de türü farklı bir sınır var. Operasyon masasındaki görevli ücretin
nasıl hesaplandığını değil, ekranda ne yazdığını görür: bölge adı, kademe, indirim ve ödenecek
tutar belirli bir biçimde dizilmiş olmalıdır. Bu sınırın iki tarafı eşit değildir — biri kuralı
ve veriyi taşır, öbürü onu gösterir.

Bu ders sorumluluğun üç ayrı dağıtımını karşılaştırır ve gösteren dosyanın kaç alan adını
tanıdığını, sınırdan kaç çağrı ile kaç bayt geçtiğini, sıra ve biçim kararlarının hangi
dosyalarda durduğunu sayar. Üç varyantın hepsi aynı ekranı üretmek zorundadır; ölçüm ancak o
zaman aynı işin iki düzenlenişini karşılaştırır.

## Adlandırma ve Sınır

Aile adları üç harfle kısaltılır: **model–görünüm–denetleyici** (model–view–controller),
**model–görünüm–sunucu** (model–view–presenter) ve **görünüm modeli** (view model) varyantı.
Buradaki **sunucu** sözcüğü gösterilecek değerleri sunan taraftır; İstemci–Sunucu dersindeki
bekleyen taraf anlamıyla akraba değildir, aynı sözcüğün iki ayrı kataloğa ait kullanımıdır.

Görünüm modeli Sunucu Tarafı Temelleri kursundaki Sunucuda Şablon Oluşturma dersinde kurulmuştu:
şablon ile veri kaynağı arasındaki sözleşmedir, hesap ve veri erişimi şablonda değil onu kuran
kodda yapılır. Şablon değerlendirmesi, kaçışlama ve düzen şablonları oraya aittir ve burada
yeniden ele alınmaz. Uygulama Mimarisi: Yönlendirme, Durum ve Veri kursundaki durum yönetimi
dersleri de aynı sınırın arayüz tarafındaydı. Burada yeni olan tek şey **sorumluluk dağılımının
sayılmasıdır**.

Bağlandığı **kalite niteliği** sürdürülebilirliktir ve nitelik sorusu şudur: ekranın düzeni ya
da tutar biçimi değiştiğinde kaç dosya düzenlenir.

## Ortak Model

Üç varyant aynı hesabı kullanır. Model beş alan döndürür ve hiçbirinde biçimlendirme yoktur;
sayılar sayı, kodlar kod olarak kalır.

```sh
mkdir -p model mvd mvs gm
```

```js
// model/fiyat.mjs — ucretlendirme baglami: hesap ve alan adlari
export const IL_ADI = { "34": "Istanbul", "06": "Ankara", "65": "Van" };
const KATSAYI = { "34": 1, "06": 1.4, "65": 2.1 };

export function fiyatla(gonderi) {
  const kademe = gonderi.agirlik <= 1 ? 1 : gonderi.agirlik <= 5 ? 2 : gonderi.agirlik <= 20 ? 3 : 4;
  const ham = 25 * kademe * KATSAYI[gonderi.il];
  const indirim = ham * gonderi.sozlesmeOrani;
  return { il: gonderi.il, kademe, ham, indirim, net: ham - indirim };
}
```

## Denetleyici Varyantı

Birinci varyantta denetleyici girdiyi alır, modeli çağırır ve görünümü seçer. Görünüm model
sonucunu doğrudan alır: alan adlarını kendisi okur, biçimlendirmeyi kendisi yapar.

```js
// mvd/gorunum.mjs — gorunum modelin alan adlarini kendisi okur ve bicimlendirmeyi kendisi yapar
import { IL_ADI } from "../model/fiyat.mjs";

export const goster = (sonuc) =>
  [
    ["Bolge", IL_ADI[sonuc.il]],
    ["Kademe", String(sonuc.kademe)],
    ["Taban ucret", `${sonuc.ham.toFixed(2)} TL`],
    ["Indirim", `${sonuc.indirim.toFixed(2)} TL`],
    ["Odenecek", `${sonuc.net.toFixed(2)} TL`],
  ]
    .map(([etiket, deger]) => `${etiket.padEnd(11)}: ${deger}`)
    .join("\n");
```

## Sunucu Varyantı

İkinci varyantta görünüm edilgendir: kendisine yazılanı tutar, hiçbir alan adı bilmez, hiçbir
hesap yapmaz. Sunucu modelden okur, biçimlendirir ve satır satır görünüme yazar.

```js
// mvs/gorunum.mjs — edilgen gorunum: yalnizca yazilani tutar, hicbir alan adi bilmez
export function gorunum() {
  const satir = new Map();
  return {
    yaz: (etiket, deger) => satir.set(etiket, deger),
    metin: () => [...satir].map(([e, d]) => `${e.padEnd(11)}: ${d}`).join("\n"),
  };
}
```

```js
// mvs/sunucu.mjs — modelden okur, bicimlendirir ve edilgen gorunume satir satir yazar
import { fiyatla, IL_ADI } from "../model/fiyat.mjs";
import { gorunum } from "./gorunum.mjs";

export function teklifEkrani(gonderi, kur = gorunum) {
  const s = fiyatla(gonderi);
  const g = kur();
  g.yaz("Bolge", IL_ADI[s.il]);
  g.yaz("Kademe", String(s.kademe));
  g.yaz("Taban ucret", `${s.ham.toFixed(2)} TL`);
  g.yaz("Indirim", `${s.indirim.toFixed(2)} TL`);
  g.yaz("Odenecek", `${s.net.toFixed(2)} TL`);
  return g.metin();
}
```

## Görünüm Modeli Varyantı

Üçüncü varyantta araya bir veri yapısı girer. Görünüm modeli modelden okur ve gösterilmeye hazır
alanlar üretir; görünüm yalnız o alanları okur ve nasıl dizileceğine karar verir.

```js
// gm/gorunum-modeli.mjs — modelden gorunume hazir alanlar ureten ayri veri yapisi
import { fiyatla, IL_ADI } from "../model/fiyat.mjs";

export function gorunumModeli(gonderi) {
  const s = fiyatla(gonderi);
  return {
    bolgeAdi: IL_ADI[s.il],
    kademeMetni: String(s.kademe),
    tabanUcret: `${s.ham.toFixed(2)} TL`,
    indirimTutari: `${s.indirim.toFixed(2)} TL`,
    odenecek: `${s.net.toFixed(2)} TL`,
  };
}
```

```js
// gm/gorunum.mjs — yalnizca gorunum modelinin alanlarini okur, hicbir hesap yapmaz
const DUZEN = [
  ["Bolge", "bolgeAdi"],
  ["Kademe", "kademeMetni"],
  ["Taban ucret", "tabanUcret"],
  ["Indirim", "indirimTutari"],
  ["Odenecek", "odenecek"],
];

export const goster = (gm) => DUZEN.map(([e, a]) => `${e.padEnd(11)}: ${gm[a]}`).join("\n");
```

Kalan iki dosya birer satırdır: denetleyici ile görünüm modelini kuran ekran. İkisi de görünümü
bir parametre olarak alır, böylece ölçüm sırasında sınırdan geçenler sayılabilir.

```sh
cat > mvd/denetleyici.mjs <<'SON'
// mvd/denetleyici.mjs — girdiyi alir, modeli cagirir, gorunumu secer
import { fiyatla } from "../model/fiyat.mjs";
import { goster } from "./gorunum.mjs";

export const teklifEkrani = (gonderi, ciz = goster) => ciz(fiyatla(gonderi));
SON
cat > gm/ekran.mjs <<'SON'
// gm/ekran.mjs — gorunum modelini kurar ve gorunume verir
import { gorunumModeli } from "./gorunum-modeli.mjs";
import { goster } from "./gorunum.mjs";

export const teklifEkrani = (gonderi, ciz = goster) => ciz(gorunumModeli(gonderi));
SON
```

## Ölçüm

Betik önce üç varyantı aynı gönderiyle koşturup çıktılarının aynı olduğunu doğrular. Sonra her
ailenin görünüm dosyasında kaç model alan adı ve kaç sunum adı geçtiğini sayar, görünümün ithal
kapanışını çıkarır ve sınırdan geçen çağrıları kaydederek bayt miktarını toplar. Son tablo iki
kararı arar: satırların sırasını belirleyen dosya ile tutar biçimini belirleyen dosya.

```js
// sorumluluk.mjs — uc ailenin ayni ekrani uretmesi, sinirdan gecen veri ve gorunumun bildigi adlar
import { readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";
import { teklifEkrani as mvd } from "./mvd/denetleyici.mjs";
import { teklifEkrani as mvs } from "./mvs/sunucu.mjs";
import { teklifEkrani as gmEkran } from "./gm/ekran.mjs";
import { goster as gosterMvd } from "./mvd/gorunum.mjs";
import { goster as gosterGm } from "./gm/gorunum.mjs";

const GONDERI = { il: "06", agirlik: 12, sozlesmeOrani: 0.15 };
const ALAN = ["il", "kademe", "ham", "indirim", "net"];
const SUNUM = ["bolgeAdi", "kademeMetni", "tabanUcret", "indirimTutari", "odenecek"];

const kapanis = (kok) => {
  const gorulen = new Set([kok]), yigit = [kok];
  while (yigit.length > 0) {
    const d = yigit.pop();
    for (const [, y] of readFileSync(d, "utf8").matchAll(/from "(\.[^"]+)"/g)) {
      const h = normalize(join(dirname(d), y));
      if (gorulen.has(h) === false) { gorulen.add(h); yigit.push(h); }
    }
  }
  return gorulen.size;
};

const adSay = (yol, kume) => {
  const metin = readFileSync(yol, "utf8");
  return kume.filter((a) => new RegExp(`[.\\["']${a}\\b`).test(metin)).length;
};

const sayarak = (asil) => {
  const kayit = [];
  return [(...a) => { kayit.push(a); return asil(...a); }, kayit];
};

function mvsKosum() {
  const kayit = [];
  const kur = () => {
    const satir = new Map();
    return {
      yaz: (e, d) => { kayit.push([e, d]); satir.set(e, d); },
      metin: () => { kayit.push([]); return [...satir].map(([e, d]) => `${e.padEnd(11)}: ${d}`).join("\n"); },
    };
  };
  return [mvs(GONDERI, kur), kayit];
}

const [cizMvd, kayitMvd] = sayarak(gosterMvd);
const [cizGm, kayitGm] = sayarak(gosterGm);
const [mvsCikti, kayitMvs] = mvsKosum();
const cikti = [mvd(GONDERI, cizMvd), mvsCikti, gmEkran(GONDERI, cizGm)];

console.log(cikti[0]);
console.log(`uc ailenin ciktisi ayni mi = ${new Set(cikti).size === 1}`);

const AILE = [
  ["MVD", "mvd/gorunum.mjs", ["mvd/gorunum.mjs", "mvd/denetleyici.mjs"], kayitMvd],
  ["MVS", "mvs/gorunum.mjs", ["mvs/gorunum.mjs", "mvs/sunucu.mjs"], kayitMvs],
  ["GM ", "gm/gorunum.mjs", ["gm/gorunum.mjs", "gm/gorunum-modeli.mjs", "gm/ekran.mjs"], kayitGm],
];

console.log("\naile  alan adi  sunum adi  kapanis  sinir cagrisi  gecen bayt");
for (const [ad, gorunumYolu, , kayit] of AILE) {
  const bayt = kayit.reduce((t, a) => t + JSON.stringify(a).length, 0);
  console.log(
    `${ad}   ${String(adSay(gorunumYolu, ALAN)).padStart(6)}${String(adSay(gorunumYolu, SUNUM)).padStart(11)}` +
      `${String(kapanis(gorunumYolu)).padStart(9)}${String(kayit.length).padStart(15)}${String(bayt).padStart(12)}`,
  );
}

console.log("\naile  sirayi belirleyen        bicimi belirleyen         dokunulan dosya");
for (const [ad, , dosyalar] of AILE) {
  const bul = (desen) => dosyalar.find((d) => readFileSync(d, "utf8").includes(desen)) ?? "-";
  const sira = bul('"Odenecek"'), bicim = bul("toFixed");
  console.log(`${ad}   ${sira.padEnd(24)}${bicim.padEnd(26)}${new Set([sira, bicim]).size}`);
}
```

```sh
node sorumluluk.mjs
```

```
Bolge      : Ankara
Kademe     : 3
Taban ucret: 105.00 TL
Indirim    : 15.75 TL
Odenecek   : 89.25 TL
uc ailenin ciktisi ayni mi = true

aile  alan adi  sunum adi  kapanis  sinir cagrisi  gecen bayt
MVD        5          0        2              1          62
MVS        0          0        1              6         106
GM         0          5        1              1         115

aile  sirayi belirleyen        bicimi belirleyen         dokunulan dosya
MVD   mvd/gorunum.mjs         mvd/gorunum.mjs           1
MVS   mvs/sunucu.mjs          mvs/sunucu.mjs            1
GM    gm/gorunum.mjs          gm/gorunum-modeli.mjs     2
```

## Sayıların Okunması

Üç varyant aynı beş satırı üretti, yani ölçüm aynı işi karşılaştırıyor.

Birinci sütun bilme yükümlülüğünü verir. Denetleyici varyantında görünüm beş model alan adını
tanıyor; öbür ikisinde sıfır. İkinci sütun bunun nereye gittiğini gösterir: görünüm modeli
varyantında görünüm beş **sunum** adı tanıyor, yani bilgi yok olmadı, model adlarından sunum
adlarına çevrildi. Sunucu varyantında görünüm hiç ad tanımıyor; yazılan etiketi ve değeri olduğu
gibi tutuyor.

Üçüncü sütun bunun somut sonucudur. Denetleyici varyantının görünüm dosyasının ithal kapanışı 2:
görünümü yüklemek modeli de yüklemek anlamına gelir. Öbür iki görünümün kapanışı 1; ikisi de
model olmadan yüklenip sınanabilir.

Dördüncü ve beşinci sütunlar sınırdan geçeni ölçer ve ödünleşimi açığa çıkarır. Denetleyici
varyantında sınırı tek çağrı geçti ve 62 bayt taşıdı — ham sayılar kısa. Sunucu varyantında altı
çağrı geçti ve 106 bayt taşıdı; her satır ayrı bir çağrıdır, biçimlendirilmiş dizeler ham
sayıdan uzundur. Görünüm modeli varyantında tek çağrı 115 bayt taşıdı: beş biçimlendirilmiş alan
bir arada. Sınırı geçen veri en az olan varyant, görünümün model hakkında en çok şey bildiği
varyanttır. Ucuz sınır ile bilgisiz görünüm aynı düzende bulunmuyor.

## Kararların Yeri

Son tablo sürdürülebilirlik sorusunu yanıtlar. Ekranın satır sırası ile tutar biçimi iki ayrı
karardır ve hangi dosyada durdukları aileye göre değişir.

Denetleyici ve sunucu varyantlarında iki karar aynı dosyada duruyor, dolayısıyla ikisini de
değiştiren bir istek tek dosyaya dokunur. Görünüm modeli varyantında sıra görünümde, biçim
görünüm modelinde; aynı istek iki dosyaya dokunur. Bu, ayrımın bedelidir ve karşılığı da
aynı tabloda okunur: yalnız sıranın değiştiği bir istek görünüm modeli varyantında görünüm
dosyasına dokunur, o dosya ise modeli hiç tanımaz ve tek başına sınanabilir. Denetleyici
varyantında aynı istek, beş model alan adını tanıyan dosyayı düzenlemeyi gerektirir.

Seçim ölçütü isteklerin türüne bağlıdır. Sık değişen şey düzense ayrım kazanç sağlar; sık değişen
şey hangi alanların gösterildiğiyse iki dosyayı birlikte düzenlemek maliyettir.

## Özet

- Üç varyant aynı beş satırlık ekranı üretti; fark işlevde değil, model ile görünüm arasındaki
  sorumluluk dağılımındadır.
- Görünümün tanıdığı model alan adı denetleyici varyantında 5, öbür ikisinde 0; görünüm modeli
  varyantında bu bilgi 5 sunum adına çevrildi, sunucu varyantında görünümde hiç ad kalmadı.
- Görünüm dosyasının ithal kapanışı denetleyici varyantında 2, öbür ikisinde 1; ikinci ve üçüncü
  görünüm model olmadan yüklenebilir.
- Sınırdan geçen veri ters yönde arttı: denetleyici varyantında 1 çağrı ve 62 bayt, sunucu
  varyantında 6 çağrı ve 106 bayt, görünüm modeli varyantında 1 çağrı ve 115 bayt.
- Sıra ve biçim kararları ilk iki varyantta tek dosyada, görünüm modeli varyantında iki ayrı
  dosyada duruyor; ikisini birden değiştiren istek 1 yerine 2 dosyaya dokunur.

## Sonraki Adım

Bu üç varyantta da veri tek bir sıçrayışla sınırı geçiyordu: model sonucu bir çağrıda verildi,
biçimlendirme bir yerde yapıldı. Ücretlendirme bağlamının asıl iş akışı ise böyle değil. Günlük
gönderi listesi işlenirken her kayıt sırayla birkaç adımdan geçer: alan adları normalleştirilir,
bölge türetilir, tarife uygulanır, indirim düşülür, tutar yuvarlanır. Bugün bu beş adım tek bir
gövdenin içinde. Yeni bir adım — yakıt ek ücreti — istendiğinde o gövde düzenlenmek zorunda ve
adımların hiçbiri tek başına sınanamıyor. Sonraki ders adımları ayrı birimlere böler ve zinciri
ölçer: yeni adım eklendiğinde düzenlenen dosya sayısı, her adımın tanıdığı ad sayısı, adımlar
arasındaki sınırdan geçen alan sayısı ve kayıtlar tek tek akıtıldığında bellekte aynı anda
tutulan kayıt sayısı.
