İçeriğe geç
academia.sh

Ders 05 / 14

Çok Biçimlilik

Alt tip, parametrik ve geçici çok biçimliliğin aynı tarife problemi üzerinde ayrı ayrı gerçekleştirilmesi: sisteme yeni bir tarife türü ve yeni bir işlem eklendiğinde her yolda dokunulan dosya sayısının sayılması ve iki genişleme yönünün karşıtlığının gösterilmesi.

İçindekiler

Önceki derste iki alt sınıf aynı adı — ucret — farklı gerçekleştirdi ve istemci hangisiyle çalıştığını bilmedi. Bu, tek bir adın birden çok davranışa karşılık gelmesidir. Alt tip ilişkisi bunun yollarından yalnız biridir ve seçilen yol, sistemin hangi yönde genişleyebileceğini belirler.

Çok biçimlilik (polymorphism), tek bir arayüzün birden çok tiple çalışabilmesidir. Programlama Temelleri kursunda tanıtılan tanım budur. Bu derste üç ayrı biçimi aynı problem üzerinde kurulur ve aralarındaki seçimin bedeli sayılır.

Üç Ayrı Biçim

Alt tip çok biçimliliği (subtype polymorphism), çağrının nesnenin çalışma zamanındaki tipine göre yönlendirilmesidir. Ad ortak bir üst tipte tanımlıdır, gerçekleştirim alt tiplerdedir.

Parametrik çok biçimlilik (parametric polymorphism), aynı kodun tipten bağımsız çalışmasıdır. Kod hiçbir tipe bakmaz; yaptığı iş tüm tipler için aynıdır. Tipe özgü olan kısım dışarıdan verilir.

Geçici çok biçimlilik (ad hoc polymorphism), her tip için ayrı bir gerçekleştirimin yazılıp ortak bir adla çağrılmasıdır. Aşırı yükleme destekleyen dillerde bu, aynı adın farklı imzalarla tanımlanmasıdır; burada tür etiketine bakan bir yönlendirme tablosuyla gerçekleştirilir.

Problem üçünde de aynıdır: iki tarife türü (kademeli, sabit) ve iki işlem (ucret, teslimGunu).

Aynı Problem, Üç Gerçekleştirim

Alt tip yolunda üst tip işlemleri bildirir, her tür kendi dosyasında gerçekleştirir.

// v1/alttip/tarife.mjs — ust tip: her tarife turu bu iki yontemi gerceklestirir
export class Tarife {
  ucret(gonderi) { throw new Error(`ucret gerceklestirilmedi: ${gonderi}`); }
  teslimGunu(gonderi) { throw new Error(`teslimGunu gerceklestirilmedi: ${gonderi}`); }
}
// v1/alttip/kademeli.mjs — kademeli tarife turu
import { Tarife } from "./tarife.mjs";

export class Kademeli extends Tarife {
  ucret(g) {
    return g.agirlikGram <= 1000 ? 4500 : 4500 + Math.ceil((g.agirlikGram - 1000) / 1000) * 1800;
  }
  teslimGunu(g) { return g.bolge === 1 ? 1 : 3; }
}
// v1/alttip/sabit.mjs — sabit ucretli tarife turu
import { Tarife } from "./tarife.mjs";

export class Sabit extends Tarife {
  ucret() { return 9000; }
  teslimGunu() { return 2; }
}

Geçici yolda tür bir etikettir ve her işlem kendi dosyasında tüm türleri tek tabloda toplar. Bölünme yönü alt tip yolunun tam tersidir.

// v1/gecici/ucret.mjs — ucret islemi: tum turler icin tek tabloda
const TABLO = {
  kademeli: (g) => (g.agirlikGram <= 1000
    ? 4500 : 4500 + Math.ceil((g.agirlikGram - 1000) / 1000) * 1800),
  sabit: () => 9000,
};

export const ucret = (tarife, gonderi) => TABLO[tarife.tur](gonderi);
// v1/gecici/teslim-gunu.mjs — teslim gunu islemi: tum turler icin tek tabloda
const TABLO = { kademeli: (g) => (g.bolge === 1 ? 1 : 3), sabit: () => 2 };

export const teslimGunu = (tarife, gonderi) => TABLO[tarife.tur](gonderi);

Parametrik yolda tür hiç geçmez. Aşağıdaki iki işlev, ölçütü dışarıdan aldığı için her tarife temsiliyle çalışır: sınıf örnekleriyle de, etiketli düz nesnelerle de.

// v1/parametrik/secim.mjs — tur bilgisi kullanmaz; olcutu disaridan alir
export function enUcuz(secenekler, olcut) {
  let en = secenekler[0];
  for (const s of secenekler) if (olcut(s) < olcut(en)) en = s;
  return en;
}

export const siraya = (secenekler, olcut) =>
  [...secenekler].sort((a, b) => olcut(a) - olcut(b));

Üç yol aynı gönderi üzerinde aynı sonuçları verir.

// v1/kosum.mjs — uc yolun ayni gonderide ayni sonuclari verdigini gosterir
import { Kademeli } from "./alttip/kademeli.mjs";
import { Sabit } from "./alttip/sabit.mjs";
import { ucret } from "./gecici/ucret.mjs";
import { teslimGunu } from "./gecici/teslim-gunu.mjs";
import { enUcuz } from "./parametrik/secim.mjs";

const G = { agirlikGram: 4000, bolge: 2 };
const nesneler = [new Kademeli(), new Sabit()];
const kayitlar = [{ tur: "kademeli" }, { tur: "sabit" }];

console.log("alttip     ", nesneler.map((t) => `${t.ucret(G)}/${t.teslimGunu(G)}`).join(" "));
console.log("gecici     ", kayitlar.map((t) => `${ucret(t, G)}/${teslimGunu(t, G)}`).join(" "));
console.log("parametrik ", enUcuz(nesneler, (t) => t.ucret(G)).constructor.name,
  enUcuz(kayitlar, (t) => ucret(t, G)).tur);
node v1/kosum.mjs
alttip      9900/3 9000/2
gecici      9900/3 9000/2
parametrik  Sabit sabit

Parametrik satır, o yolun karakterini gösteriyor: enUcuz hem sınıf örnekleri hem etiketli kayıtlar üzerinde çalıştı, çünkü ikisine de bakmadı — yalnız kendisine verilen ölçütü çağırdı.

İki Genişleme

Sisteme iki değişiklik gelir. Birincisi yeni bir tür: aktarma noktasından geçen gönderi tarifesi. Aşağıdaki blok ikinci sürümü birinciden kopyalayarak kurar ve bu türü ekler.

cp -r v1 v2
cat > v2/alttip/aktarmali.mjs <<'DOSYA'
// v2/alttip/aktarmali.mjs — yeni tur: aktarma noktasindan gecen gonderi
import { Tarife } from "./tarife.mjs";

export class Aktarmali extends Tarife {
  ucret(g) { return 6000 + Math.ceil(g.agirlikGram / 1000) * 1200; }
  teslimGunu() { return 5; }
}
DOSYA
cat > v2/gecici/ucret.mjs <<'DOSYA'
// v2/gecici/ucret.mjs — ucret islemi: yeni tur icin satir eklendi
const TABLO = {
  kademeli: (g) => (g.agirlikGram <= 1000
    ? 4500 : 4500 + Math.ceil((g.agirlikGram - 1000) / 1000) * 1800),
  sabit: () => 9000,
  aktarmali: (g) => 6000 + Math.ceil(g.agirlikGram / 1000) * 1200,
};

export const ucret = (tarife, gonderi) => TABLO[tarife.tur](gonderi);
DOSYA
cat > v2/gecici/teslim-gunu.mjs <<'DOSYA'
// v2/gecici/teslim-gunu.mjs — teslim gunu islemi: yeni tur icin satir eklendi
const TABLO = { kademeli: (g) => (g.bolge === 1 ? 1 : 3), sabit: () => 2, aktarmali: () => 5 };

export const teslimGunu = (tarife, gonderi) => TABLO[tarife.tur](gonderi);
DOSYA
echo "v2 kuruldu"
v2 kuruldu

İkinci değişiklik yeni bir işlem: gönderinin beyan edilen değerine göre sigorta bedeli. Üçüncü sürüm ikinciden kopyalanarak kurulur.

cp -r v2 v3
cat > v3/alttip/tarife.mjs <<'DOSYA'
// v3/alttip/tarife.mjs — ust tipe ucuncu yontem eklendi
export class Tarife {
  ucret(gonderi) { throw new Error(`ucret gerceklestirilmedi: ${gonderi}`); }
  teslimGunu(gonderi) { throw new Error(`teslimGunu gerceklestirilmedi: ${gonderi}`); }
  sigortaBedeli(gonderi) { throw new Error(`sigortaBedeli gerceklestirilmedi: ${gonderi}`); }
}
DOSYA
cat > v3/alttip/kademeli.mjs <<'DOSYA'
// v3/alttip/kademeli.mjs — ucuncu yontem eklendi
import { Tarife } from "./tarife.mjs";

export class Kademeli extends Tarife {
  ucret(g) {
    return g.agirlikGram <= 1000 ? 4500 : 4500 + Math.ceil((g.agirlikGram - 1000) / 1000) * 1800;
  }
  teslimGunu(g) { return g.bolge === 1 ? 1 : 3; }
  sigortaBedeli(g) { return Math.round(g.deger * 0.01); }
}
DOSYA
cat > v3/alttip/sabit.mjs <<'DOSYA'
// v3/alttip/sabit.mjs — ucuncu yontem eklendi
import { Tarife } from "./tarife.mjs";

export class Sabit extends Tarife {
  ucret() { return 9000; }
  teslimGunu() { return 2; }
  sigortaBedeli() { return 0; }
}
DOSYA
cat > v3/alttip/aktarmali.mjs <<'DOSYA'
// v3/alttip/aktarmali.mjs — ucuncu yontem eklendi
import { Tarife } from "./tarife.mjs";

export class Aktarmali extends Tarife {
  ucret(g) { return 6000 + Math.ceil(g.agirlikGram / 1000) * 1200; }
  teslimGunu() { return 5; }
  sigortaBedeli(g) { return Math.round(g.deger * 0.02); }
}
DOSYA
cat > v3/gecici/sigorta-bedeli.mjs <<'DOSYA'
// v3/gecici/sigorta-bedeli.mjs — yeni islem: tek dosya, tum turler
const TABLO = {
  kademeli: (g) => Math.round(g.deger * 0.01),
  sabit: () => 0,
  aktarmali: (g) => Math.round(g.deger * 0.02),
};

export const sigortaBedeli = (tarife, gonderi) => TABLO[tarife.tur](gonderi);
DOSYA
echo "v3 kuruldu"
v3 kuruldu

Dokunulan Dosya Sayısı

Ölçüm iki sürüm arasındaki farkı sayar. diff -rq hem değişen hem yeni eklenen dosya için bir satır üretir; sayılan bu satırlardır. Karşılaştırma yalnız üç yolun kendi dizinlerini kapsar — istemci tarafı üç yolda da aynı biçimde etkilendiği için ölçüme girmez.

for s in alttip gecici parametrik; do
  echo "$s"
  echo "  yeni tur   -> dokunulan dosya = $(diff -rq v1/$s v2/$s | wc -l | tr -d ' ')"
  echo "  yeni islem -> dokunulan dosya = $(diff -rq v2/$s v3/$s | wc -l | tr -d ' ')"
done
alttip
  yeni tur   -> dokunulan dosya = 1
  yeni islem -> dokunulan dosya = 4
gecici
  yeni tur   -> dokunulan dosya = 2
  yeni islem -> dokunulan dosya = 1
parametrik
  yeni tur   -> dokunulan dosya = 0
  yeni islem -> dokunulan dosya = 0

Sayılar karşıt. Alt tip yolunda yeni tür bir dosyaya, yeni işlem dört dosyaya mal oldu. Geçici yolda ilişki tersine döndü: yeni tür iki dosya, yeni işlem bir dosya. Parametrik yolda iki genişleme de sıfır dosyaya mal oldu.

Karşıtlığın nedeni bölünme yönüdür. Alt tip yolu kodu türe göre böler; bir türün tüm işlemleri yan yanadır, bu yüzden tür eklemek ucuz, işlem eklemek pahalıdır. Geçici yol kodu işleme göre böler; bir işlemin tüm türleri yan yanadır, bu yüzden ilişki tersine döner. Bir tasarım iki yönde birden ucuz olamaz; seçim, hangi yönde daha sık genişleneceğine göre verilir.

Parametrik yolun sıfırları, o yolun bedava olduğu anlamına gelmez. enUcuz iki genişlemeden de etkilenmedi çünkü türe özgü hiçbir iş yapmıyor; ücreti hesaplayan ölçütü çağıran taraf veriyor. Parametrik çok biçimlilik türe özgü işi ortadan kaldırmaz, onu kendi dışına iter. Bu yüzden bir sistemde tek başına değil, diğer ikisiyle birlikte bulunur.

Genişletilmiş sistemin üç türü ve üç işlemi aynı sonuçları üretiyor.

// v3/kosum.mjs — genisletilmis sistem: uc tur, uc islem, ayni sonuclar
import { Kademeli } from "./alttip/kademeli.mjs";
import { Sabit } from "./alttip/sabit.mjs";
import { Aktarmali } from "./alttip/aktarmali.mjs";
import { ucret } from "./gecici/ucret.mjs";
import { teslimGunu } from "./gecici/teslim-gunu.mjs";
import { sigortaBedeli } from "./gecici/sigorta-bedeli.mjs";
import { enUcuz } from "./parametrik/secim.mjs";

const G = { agirlikGram: 4000, bolge: 2, deger: 150000 };
const nesneler = [new Kademeli(), new Sabit(), new Aktarmali()];
const kayitlar = [{ tur: "kademeli" }, { tur: "sabit" }, { tur: "aktarmali" }];

const n = (t) => `${t.ucret(G)}/${t.teslimGunu(G)}/${t.sigortaBedeli(G)}`;
const k = (t) => `${ucret(t, G)}/${teslimGunu(t, G)}/${sigortaBedeli(t, G)}`;

console.log("alttip     ", nesneler.map(n).join(" "));
console.log("gecici     ", kayitlar.map(k).join(" "));
console.log("parametrik ", enUcuz(nesneler, (t) => t.ucret(G)).constructor.name,
  enUcuz(kayitlar, (t) => ucret(t, G)).tur);
node v3/kosum.mjs
alttip      9900/3/1500 9000/2/0 10800/5/3000
gecici      9900/3/1500 9000/2/0 10800/5/3000
parametrik  Sabit sabit

Seçim Ölçütü

Ölçüt, sistemin hangi eksende genişlediğidir ve bu tahminle değil geçmişle yanıtlanır: son dönemde kaç tür, kaç işlem eklendiğine bakılır.

Tür ekseninin işlek olduğu yerlerde — taşıyıcı seçenekleri, ödeme yöntemleri, bildirim kanalları — işlem kümesi genelde erken kararlaşır ve alt tip yolu ucuza gelir. İşlem ekseninin işlek olduğu yerlerde — sabit bir tür kümesi üzerinde sürekli yeni rapor, yeni doğrulama, yeni dışa aktarma — geçici yol ucuza gelir; tür kümesi kapalıysa yeni bir işlem eklemek tek dosyaya iner.

Bir de yanlış seçimin sinyali vardır. Alt tip yolunda her yeni işlem için dört dosyaya dokunuluyorsa ve bu her ay tekrarlanıyorsa, tasarımın yanlış eksende bölündüğü ölçülmüş demektir. Aynı biçimde, geçici yolda tablolara sürekli yeni tür satırı ekleniyor ve bir tür eklendiğinde bir tabloyu güncellemek unutuluyorsa — o durumda hata çalışma zamanında TABLO[tarife.tur] tanımsız çıkarak görünür — eksen tersine dönmüş demektir.

Özet

  • Çok biçimliliğin üç biçimi aynı problemi çözebilir: alt tip çalışma zamanı tipine yönlendirir, geçici tür etiketine göre tablo seçer, parametrik hiçbir tipe bakmaz.
  • Aynı gönderide üç yol da aynı sonucu verdi; karşılaştırma davranış üzerinden değil genişleme maliyeti üzerinden yapıldı.
  • Yeni bir tür eklendiğinde dokunulan dosya: alt tip 1, geçici 2, parametrik 0.
  • Yeni bir işlem eklendiğinde dokunulan dosya: alt tip 4, geçici 1, parametrik 0.
  • Karşıtlığın nedeni bölünme yönüdür: alt tip yolu türe göre, geçici yol işleme göre böler; bir tasarım iki yönde birden ucuz olamaz.
  • Parametrik yolun sıfırları türe özgü işin ortadan kalktığını değil, kodun dışına itildiğini gösterir; tek başına değil diğer ikisiyle birlikte kullanılır.

Sonraki Adım

Alt tip yolundaki Tarife sınıfı iki iş yapıyordu: hangi yöntemlerin bulunacağını bildirmek ve gerçekleştirilmediğinde hata fırlatmak. Bu iki iş ayrılabilir. Bir soyutlama yalnız sözleşmeyi bildirebilir, bir başkası sözleşmeyle birlikte ortak gerçekleştirimi de taşıyabilir. Sonraki ders bu iki aracı — arayüz ile soyut sınıfı — karşılaştırır ve seçimin kırıldığı noktayı koşturarak gösterir: bir tarife türü iki ayrı soyutlamaya birden ihtiyaç duyduğunda her düzenin ne yaptığı ve kaç satırın tekrar edildiği ölçülür.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat