Ders 04 / 19
Arayüz Ayrımı İlkesi
Şişkin bir taşıyıcı arayüzünün istemcilere maliyetinin sayılması: her istemcinin gerçekten çağırdığı yöntem sayısı ile arayüzün dayattığı yöntem sayısının taranarak karşılaştırılması, sahte bağımlılıklardaki toplam yöntem sayısının ölçülmesi ve arayüze dokuzuncu bir yöntem eklendiğinde kaç sahtenin kırıldığının koşumla bulunması.
İçindekiler
Sözleşme sınaması iki yöntem üzerinden yürüdü ve her tarife ikisini de gerçekten kullanıyordu. Taşıyıcı tarafına geçildiğinde bu denge bozulur: taşıyıcı seçimi, rota planlama ve teslimat takibi aynı nesnenin üstünde toplanır. Tek bir taşıyıcı arayüzü kurulduğunda yalnız ücret hesaplayan bir istemci de rota ve takip yöntemlerine bağlanır.
Arayüz ayrımı ilkesi (interface segregation principle) bunu yasaklar: hiçbir istemci kullanmadığı yöntemlere bağımlı olmaya zorlanmamalıdır. İlkenin ölçüsü iki ayrı sayının karşılaştırılmasıdır — istemcinin çağırdığı yöntem sayısı ile arayüzün dayattığı yöntem sayısı.
Tek ve Geniş Sözleşme
İlk sürümde sekiz yöntemin tamamı tek bir sözleşmede toplanmıştır. Sözleşme bir liste ve onu denetleyen bir doğrulayıcı olarak yazılır; istemciler doğrulayıcıyı çağırarak aldıkları nesnenin sözleşmeyi karşıladığından emin olur.
// v1/tasiyici.mjs — tek ve genis tasiyici sozlesmesi export const TASIYICI_SOZLESMESI = [ "bolgeKatsayisi", "tabanUcret", "aktarmaNoktalari", "tahminiSure", "durum", "teslimAlindi", "etiketBas", "iptalEt", ]; export function sozlesmeyiDogrula(t) { const eksik = TASIYICI_SOZLESMESI.filter((ad) => typeof t?.[ad] !== "function"); if (eksik.length > 0) throw new TypeError(`tasiyici sozlesmesi eksik: ${eksik.join(", ")}`); return t; }
Üç istemci bu sözleşmeye bağlanır. Her biri GEREKEN adıyla, bağlandığı sözleşmeyi dışa
verir; ölçüm betiği bu adı okuyacak.
// v1/ucret-hesabi.mjs — ucret istemcisi: iki yontem cagirir, sekizini ister import { sozlesmeyiDogrula, TASIYICI_SOZLESMESI } from "./tasiyici.mjs"; export const GEREKEN = TASIYICI_SOZLESMESI; export function ucret(tasiyici, gonderi) { sozlesmeyiDogrula(tasiyici); return Math.round((tasiyici.tabanUcret(gonderi) * tasiyici.bolgeKatsayisi(gonderi.adres)) / 100); }
// v1/rota-planlayici.mjs — rota istemcisi import { sozlesmeyiDogrula, TASIYICI_SOZLESMESI } from "./tasiyici.mjs"; export const GEREKEN = TASIYICI_SOZLESMESI; export function rota(tasiyici, kaynak, hedef) { sozlesmeyiDogrula(tasiyici); const noktalar = tasiyici.aktarmaNoktalari(kaynak, hedef); return { noktalar, saat: tasiyici.tahminiSure(noktalar) }; }
// v1/takip.mjs — takip istemcisi import { sozlesmeyiDogrula, TASIYICI_SOZLESMESI } from "./tasiyici.mjs"; export const GEREKEN = TASIYICI_SOZLESMESI; export function teslimEt(tasiyici, takipNo, imza) { sozlesmeyiDogrula(tasiyici); tasiyici.teslimAlindi(takipNo, imza); return tasiyici.durum(takipNo); }
İki Sayının Ölçülmesi
Ölçüm betiği her istemcinin metninde tasiyici. ile başlayan çağrıları tarar ve bulduğu ayrı
yöntem adlarını sayar. Dayatılan sayı ise istemcinin bağlandığı sözleşmenin uzunluğudur.
// arayuz-olc.mjs — istemcinin cagirdigi yontem sayisi ile arayuzun dayattigi sayi import { readFileSync } from "node:fs"; const CAGRI = /\btasiyici\.(\w+)\(/g; const kok = process.argv[2]; const istemciler = process.argv.slice(3); let kullanilanToplam = 0; let dayatilanToplam = 0; for (const ad of istemciler) { const metin = readFileSync(`${kok}/${ad}`, "utf8"); const kullanilan = new Set([...metin.matchAll(CAGRI)].map((m) => m[1])); const { GEREKEN } = await import(`./${kok}/${ad}`); kullanilanToplam += kullanilan.size; dayatilanToplam += GEREKEN.length; console.log(`${ad.padEnd(20)} kullanilan = ${kullanilan.size} dayatilan = ${GEREKEN.length}` + ` fazlalik = ${GEREKEN.length - kullanilan.size}`); } console.log(`${kok}: kullanilan toplam = ${kullanilanToplam}, dayatilan toplam = ${dayatilanToplam}`);
node arayuz-olc.mjs v1 ucret-hesabi.mjs rota-planlayici.mjs takip.mjs
ucret-hesabi.mjs kullanilan = 2 dayatilan = 8 fazlalik = 6 rota-planlayici.mjs kullanilan = 2 dayatilan = 8 fazlalik = 6 takip.mjs kullanilan = 2 dayatilan = 8 fazlalik = 6 v1: kullanilan toplam = 6, dayatilan toplam = 24
Altı çağrının karşılığı yirmi dört bağımlılık. Fazlalık kavramsal değil: her istemci, kullanmadığı altı yöntemin adına ve varlığına bağlıdır.
Sahte Bağımlılığın Maliyeti
Fazlalığın ilk somut faturası sahte bağımlılıklarda çıkar. Her istemci için yalnız o istemcinin çağırdığı yöntemleri taşıyan sahteler yazılır.
// sahte-az.mjs — her sahte yalniz kendi istemcisinin cagirdigi yontemleri tasir export const SAHTE = { ucret: { bolgeKatsayisi: () => 115, tabanUcret: () => 6400 }, rota: { aktarmaNoktalari: (k, h) => [k, "41", h], tahminiSure: (n) => n.length * 6 }, takip: { durum: () => "teslim edildi", teslimAlindi: () => undefined }, };
// sahte-dene.mjs — verilen sahtelerle uc istemciyi calistirir, kirilani ve maliyeti sayar const kok = process.argv[2]; const { SAHTE } = await import(process.argv[3]); const { ucret } = await import(`./${kok}/ucret-hesabi.mjs`); const { rota } = await import(`./${kok}/rota-planlayici.mjs`); const { teslimEt } = await import(`./${kok}/takip.mjs`); const isler = [ ["ucret", () => ucret(SAHTE.ucret, { agirlik: 3.0, adres: "06500" })], ["rota", () => rota(SAHTE.rota, "34", "06")], ["takip", () => teslimEt(SAHTE.takip, "TK-1", "A.Y.")], ]; let kirilan = 0; for (const [ad, f] of isler) { try { console.log(`${ad.padEnd(6)} -> ${JSON.stringify(f())}`); } catch (e) { kirilan += 1; console.log(`${ad.padEnd(6)} -> ${e.message}`); } } const yontem = Object.values(SAHTE).reduce((t, s) => t + Object.keys(s).length, 0); console.log(`${kok} + ${process.argv[3]}: sahte yontem = ${yontem}, kirilan istemci = ${kirilan}`);
node sahte-dene.mjs v1 ./sahte-az.mjs
ucret -> tasiyici sozlesmesi eksik: aktarmaNoktalari, tahminiSure, durum, teslimAlindi, etiketBas, iptalEt rota -> tasiyici sozlesmesi eksik: bolgeKatsayisi, tabanUcret, durum, teslimAlindi, etiketBas, iptalEt takip -> tasiyici sozlesmesi eksik: bolgeKatsayisi, tabanUcret, aktarmaNoktalari, tahminiSure, etiketBas, iptalEt v1 + ./sahte-az.mjs: sahte yontem = 6, kirilan istemci = 3
Üç istemcinin üçü de kırıldı. Çalışması için her sahtenin sekiz yöntemi de taşıması gerekir; altısı hiç çağrılmayacak dolgudur.
// sahte-dolu.mjs — genis sozlesmeyi karsilamak icin doldurulmus sahteler const BOS = () => undefined; export const SAHTE = { ucret: { bolgeKatsayisi: () => 115, tabanUcret: () => 6400, aktarmaNoktalari: BOS, tahminiSure: BOS, durum: BOS, teslimAlindi: BOS, etiketBas: BOS, iptalEt: BOS, }, rota: { aktarmaNoktalari: (k, h) => [k, "41", h], tahminiSure: (n) => n.length * 6, bolgeKatsayisi: BOS, tabanUcret: BOS, durum: BOS, teslimAlindi: BOS, etiketBas: BOS, iptalEt: BOS, }, takip: { durum: () => "teslim edildi", teslimAlindi: BOS, bolgeKatsayisi: BOS, tabanUcret: BOS, aktarmaNoktalari: BOS, tahminiSure: BOS, etiketBas: BOS, iptalEt: BOS, }, };
node sahte-dene.mjs v1 ./sahte-dolu.mjs
ucret -> 7360
rota -> {"noktalar":["34","41","06"],"saat":18}
takip -> "teslim edildi"
v1 + ./sahte-dolu.mjs: sahte yontem = 24, kirilan istemci = 0
Altı yöntemlik sahte düzeneği yirmi dörde çıktı. Aynı yük gerçek gerçekleştirimlerde de görülür: yalnız takip hizmeti veren bir taşıyıcı da sekiz yöntemi doldurmak zorundadır.
Sözleşmenin İstemciye Göre Bölünmesi
İkinci sürümde sekiz yöntem, istemci sayısı kadar sözleşmeye ayrılır. Bölmenin ölçütü yöntemlerin konusu değil, onları çağıran istemcidir.
// v2/sozlesmeler.mjs — istemciye ozgu uc kucuk sozlesme export const UCRET_SOZLESMESI = ["bolgeKatsayisi", "tabanUcret"]; export const ROTA_SOZLESMESI = ["aktarmaNoktalari", "tahminiSure"]; export const TAKIP_SOZLESMESI = ["durum", "teslimAlindi"]; export const dogrula = (ad, liste) => (t) => { const eksik = liste.filter((y) => typeof t?.[y] !== "function"); if (eksik.length > 0) throw new TypeError(`${ad} sozlesmesi eksik: ${eksik.join(", ")}`); return t; };
// v2/ucret-hesabi.mjs — yalniz ucret sozlesmesine baglanir import { dogrula, UCRET_SOZLESMESI } from "./sozlesmeler.mjs"; export const GEREKEN = UCRET_SOZLESMESI; const dogrulaUcret = dogrula("ucret", UCRET_SOZLESMESI); export function ucret(tasiyici, gonderi) { dogrulaUcret(tasiyici); return Math.round((tasiyici.tabanUcret(gonderi) * tasiyici.bolgeKatsayisi(gonderi.adres)) / 100); }
// v2/rota-planlayici.mjs — yalniz rota sozlesmesine baglanir import { dogrula, ROTA_SOZLESMESI } from "./sozlesmeler.mjs"; export const GEREKEN = ROTA_SOZLESMESI; const dogrulaRota = dogrula("rota", ROTA_SOZLESMESI); export function rota(tasiyici, kaynak, hedef) { dogrulaRota(tasiyici); const noktalar = tasiyici.aktarmaNoktalari(kaynak, hedef); return { noktalar, saat: tasiyici.tahminiSure(noktalar) }; }
// v2/takip.mjs — yalniz takip sozlesmesine baglanir import { dogrula, TAKIP_SOZLESMESI } from "./sozlesmeler.mjs"; export const GEREKEN = TAKIP_SOZLESMESI; const dogrulaTakip = dogrula("takip", TAKIP_SOZLESMESI); export function teslimEt(tasiyici, takipNo, imza) { dogrulaTakip(tasiyici); tasiyici.teslimAlindi(takipNo, imza); return tasiyici.durum(takipNo); }
Sekiz yöntemin tamamını sunan bir taşıyıcı üç sözleşmeyi de karşılar; bölünen arayüz, gerçekleştirimin bölünmesini gerektirmez. Değişen yalnız istemcinin ne bildiğidir.
node arayuz-olc.mjs v2 ucret-hesabi.mjs rota-planlayici.mjs takip.mjs node sahte-dene.mjs v2 ./sahte-az.mjs
ucret-hesabi.mjs kullanilan = 2 dayatilan = 2 fazlalik = 0
rota-planlayici.mjs kullanilan = 2 dayatilan = 2 fazlalik = 0
takip.mjs kullanilan = 2 dayatilan = 2 fazlalik = 0
v2: kullanilan toplam = 6, dayatilan toplam = 6
ucret -> 7360
rota -> {"noktalar":["34","41","06"],"saat":18}
takip -> "teslim edildi"
v2 + ./sahte-az.mjs: sahte yontem = 6, kirilan istemci = 0
Dayatılan toplam yirmi dörtten altıya, sahte düzeneği yirmi dört yöntemden altıya indi.
Dokuzuncu Yöntem
Asıl fark arayüz büyüdüğünde ortaya çıkar. Sigorta primi hesabı gerekiyor ve yeni yöntem
ücret tarafına aittir. İki ağaç kopyalanır, değişiklik ikisine de uygulanır. Yerinde
düzenlemede yedek uzantısı verilmiştir; GNU ve BSD sed bu biçimde aynı davranır.
cp -r v1 v1-yeni cp -r v2 v2-yeni sed -i.y 's/"etiketBas", "iptalEt",/"etiketBas", "iptalEt", "sigortaPrimi",/' v1-yeni/tasiyici.mjs sed -i.y 's/UCRET_SOZLESMESI = \["bolgeKatsayisi", "tabanUcret"\]/UCRET_SOZLESMESI = ["bolgeKatsayisi", "tabanUcret", "sigortaPrimi"]/' v2-yeni/sozlesmeler.mjs rm -f v1-yeni/*.y v2-yeni/*.y node sahte-dene.mjs v1-yeni ./sahte-dolu.mjs node sahte-dene.mjs v2-yeni ./sahte-az.mjs
ucret -> tasiyici sozlesmesi eksik: sigortaPrimi
rota -> tasiyici sozlesmesi eksik: sigortaPrimi
takip -> tasiyici sozlesmesi eksik: sigortaPrimi
v1-yeni + ./sahte-dolu.mjs: sahte yontem = 24, kirilan istemci = 3
ucret -> ucret sozlesmesi eksik: sigortaPrimi
rota -> {"noktalar":["34","41","06"],"saat":18}
takip -> "teslim edildi"
v2-yeni + ./sahte-az.mjs: sahte yontem = 6, kirilan istemci = 1
Ücret tarafına eklenen tek yöntem, geniş arayüzde üç istemcinin üçünü birden kırdı; bölünmüş arayüzde yalnız ilgili olanı kırdı. Sigorta priminden hiç haberi olmayan rota planlayıcının sınaması ilk sürümde bozuldu, ikincisinde bozulmadı.
Bölmenin Ölçütü ve Sınırı
Arayüzü bölmenin doğru ekseni istemcidir. Yöntemleri konularına göre gruplamak — “hesapla ilgili olanlar”, “kayıtla ilgili olanlar” — aynı sonucu vermez; iki farklı istemci aynı konudan farklı alt kümeler kullanabilir. Ölçüt tarama sonucudur: hangi istemci hangi yöntem adlarını çağırıyor.
Bölmenin de bir bedeli vardır. Üç sözleşme üç doğrulayıcı ve üç ad demektir; ayrıca bir istemci gerçekten dört yöntemin dördünü de çağırıyorsa onu ikiye bölmek fazlalığı azaltmaz, yalnız dosya sayısını artırır. İlkenin eşiği ölçümdedir: fazlalık sıfırsa bölmenin kazancı da sıfırdır.
Özet
- Arayüz ayrımı ilkesi, istemcinin kullanmadığı yöntemlere bağımlı kalmasını yasaklar; ölçüsü çağrılan yöntem sayısı ile dayatılan yöntem sayısının farkıdır.
- Geniş sözleşmede üç istemcinin toplam çağrısı 6, dayatılan bağımlılık 24 bulundu; bölünmüş sözleşmelerde ikisi de 6 oldu.
- Fazlalığın ilk faturası sahte bağımlılıklardadır: geniş sözleşmeyi karşılamak için sahte düzeneği 6 yöntemden 24 yönteme çıktı, 18’i hiç çağrılmayan dolgudur.
- Sözleşmeye dokuzuncu bir yöntem eklendiğinde geniş sürümde 3 istemci, bölünmüş sürümde 1 istemci kırıldı.
- Bölme ekseni yöntemlerin konusu değil, onları çağıran istemcidir; fazlalık sıfır ölçülen yerde bölme yalnız dosya sayısını artırır.
Sonraki Adım
Bölünmüş sözleşmeler v2/sozlesmeler.mjs dosyasında, istemcilerin yanında durdu. Bu yerleşim
tesadüf değil: sözleşmeyi kimin tanımladığı, arayüzün kaç parçaya bölündüğünden ayrı bir
karardır. Sözleşme taşıyıcı gerçekleştiriminin yanında tanımlansaydı, istemciler bölünmüş bir
arayüze bağlanmaya devam ederdi ama yine gerçekleştirimin bulunduğu tarafı ithal ederdi.
Sonraki ders bu iki yerleşimi ithal yönünü çıkararak karşılaştırır ve soyutlamanın hangi
tarafta tanımlandığını bir sayıya çevirir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.