İçeriğe geç
academia.sh

Ders 19 / 30

Sorumluluk Zinciri

İndirim kurallarının tek gövdede sıralanması ile isteğin işleyiciler arasında aktarılmasının karşılaştırılması: kural sırası değiştiğinde değişen satır sayısı, üç yeni kural eklendiğinde en karmaşık gövdenin çevrimsel karmaşıklığı ile dosya sayısının nasıl büyüdüğü ve kalıbın bedeli olarak hiçbir kurala uymayan isteğin ayırt edilemeyişi.

İçindekiler

Durum nesnesi bir soruyu tek bir yerden yanıtlıyordu: bu olay burada geçerli mi. Ücret düzeltmeleri tarafında soru tek bir yere sorulamıyor. Bir gönderiye sözleşmeli müşteri indirimi, hacim indirimi, kampanya indirimi ve elle girilen düzeltme sırayla uygulanabilir; kuralların bir kısmı gönderiye uymaz ve atlanır, bir kısmı uygulandıktan sonra zinciri keser. Bugün bu kurallar tek gövdede iç içe koşullarla duruyor ve sıraları gövdenin akışına gömülü.

Sorumluluk zinciri (chain of responsibility) isteği bir işleyici dizisine verir. Her işleyici isteğe uygun olup olmadığına kendisi karar verir, uygunsa işini yapar, ardından isteği sonraki işleyiciye bırakır ya da zinciri keser. Ölçülecek sayılar kural sırası değiştiğinde değişen satır sayısı, kural sayısı arttığında en karmaşık gövdenin çevrimsel karmaşıklığı ve dosya sayısıdır.

Dört Kural

Kurallar sırayla şunlar: sözleşmeli müşteriye yüzde on iki, yirmi kilogramdan ağır gönderiye yüzde sekiz, üçüncü bölgedeki hafif gönderiye yüzde on beş (uygulanınca zinciri keser), elle girilen orana eşit düzeltme (uygulanınca zinciri keser).

mkdir -p gomulu zincir

cat > zincir/sozlesmeli.mjs <<'SON'
// zincir/sozlesmeli.mjs
export const sozlesmeli = {
  ad: "sozlesmeli",
  keser: false,
  uygunMu: (g) => g.sozlesmeli === true,
  oran: () => 12,
};
SON

cat > zincir/hacim.mjs <<'SON'
// zincir/hacim.mjs
export const hacim = {
  ad: "hacim",
  keser: false,
  uygunMu: (g) => g.agirlik >= 20,
  oran: () => 8,
};
SON

cat > zincir/kampanya.mjs <<'SON'
// zincir/kampanya.mjs — uygulandiginda zinciri keser
export const kampanya = {
  ad: "kampanya",
  keser: true,
  uygunMu: (g) => g.bolge === "3" && g.agirlik < 5,
  oran: () => 15,
};
SON

cat > zincir/elle.mjs <<'SON'
// zincir/elle.mjs — uygulandiginda zinciri keser
export const elle = {
  ad: "elle",
  keser: true,
  uygunMu: (g) => typeof g.elleOran === "number",
  oran: (g) => g.elleOran,
};
SON
// veri.mjs — indirim kurallarinin denenecegi gonderiler
export const GONDERILER = [
  { kod: "GN-1", agirlik: 2, bolge: "1", sozlesmeli: false },
  { kod: "GN-2", agirlik: 6, bolge: "2", sozlesmeli: true },
  { kod: "GN-3", agirlik: 22, bolge: "3", sozlesmeli: false },
  { kod: "GN-4", agirlik: 3, bolge: "3", sozlesmeli: true },
  { kod: "GN-5", agirlik: 25, bolge: "2", sozlesmeli: false, elleOran: 5 },
];

Birinci sürümde dört kural ve sıraları tek gövdededir. Gövde ayrıca kaç kuralın yoklandığını sayar; bu sayı iki sürümü karşılaştırmayı sağlar.

// gomulu/indirim.mjs — dort kural ve siralari tek govdede
export function indirim(gonderi) {
  const uygulanan = [];
  let oran = 0;
  let ziyaret = 1;
  if (gonderi.sozlesmeli === true) {
    oran += 12;
    uygulanan.push("sozlesmeli");
  }
  ziyaret += 1;
  if (gonderi.agirlik >= 20) {
    oran += 8;
    uygulanan.push("hacim");
  }
  ziyaret += 1;
  if (gonderi.bolge === "3" && gonderi.agirlik < 5) {
    oran += 15;
    uygulanan.push("kampanya");
    return { oran, ziyaret, uygulanan };
  }
  ziyaret += 1;
  if (typeof gonderi.elleOran === "number") {
    oran += gonderi.elleOran;
    uygulanan.push("elle");
    return { oran, ziyaret, uygulanan };
  }
  return { oran, ziyaret, uygulanan };
}

İkinci sürümde sıra bir dizidir ve tek satırda durur.

// zincir/sira.mjs — zincirin sirasi tek satirda
import { sozlesmeli } from "./sozlesmeli.mjs";
import { hacim } from "./hacim.mjs";
import { kampanya } from "./kampanya.mjs";
import { elle } from "./elle.mjs";

export const SIRA = [sozlesmeli, hacim, kampanya, elle];
// zincir/zincir.mjs — istek kesilene ya da zincir bitene kadar aktarilir
export function indirim(kurallar, gonderi) {
  const uygulanan = [];
  let oran = 0;
  let ziyaret = 0;
  for (const kural of kurallar) {
    ziyaret += 1;
    if (kural.uygunMu(gonderi) === false) continue;
    oran += kural.oran(gonderi);
    uygulanan.push(kural.ad);
    if (kural.keser) break;
  }
  return { oran, ziyaret, uygulanan };
}

Zincir yürütücüsü hiçbir kuralın ne yaptığını bilmez; yalnızca uygunMu, oran ve keser üçlüsünü kullanır. Kural sayısı ne olursa olsun bu gövde değişmez.

Davranışın Eşitliği

// kosum.mjs — iki surumu ayni gonderilerle kosar ve sapmayi sayar
import { GONDERILER } from "./veri.mjs";
import { indirim as gomuluIndirim } from "./gomulu/indirim.mjs";
import { indirim as zincirIndirim } from "./zincir/zincir.mjs";
import { SIRA } from "./zincir/sira.mjs";

let sapma = 0;
for (const g of GONDERILER) {
  const a = gomuluIndirim(g);
  const b = zincirIndirim(SIRA, g);
  if (a.oran !== b.oran || a.uygulanan.join() !== b.uygulanan.join()) sapma += 1;
  console.log(
    `${g.kod} gomulu oran=${a.oran} ziyaret=${a.ziyaret} [${a.uygulanan.join(" ")}]  zincir oran=${b.oran} ziyaret=${b.ziyaret} [${b.uygulanan.join(" ")}]`,
  );
}
console.log(`iki surum arasindaki sapma sayisi = ${sapma}`);
GN-1 gomulu oran=0 ziyaret=4 []  zincir oran=0 ziyaret=4 []
GN-2 gomulu oran=12 ziyaret=4 [sozlesmeli]  zincir oran=12 ziyaret=4 [sozlesmeli]
GN-3 gomulu oran=8 ziyaret=4 [hacim]  zincir oran=8 ziyaret=4 [hacim]
GN-4 gomulu oran=27 ziyaret=3 [sozlesmeli kampanya]  zincir oran=27 ziyaret=3 [sozlesmeli kampanya]
GN-5 gomulu oran=13 ziyaret=4 [hacim elle]  zincir oran=13 ziyaret=4 [hacim elle]
iki surum arasindaki sapma sayisi = 0

Sapma sıfır: oranlar, uygulanan kural listeleri ve ziyaret sayıları birebir aynı. GN-4’te ziyaret sayısının üç olması kesme davranışının çalıştığını gösteriyor; kampanya uygulandıktan sonra elle düzeltme yoklanmadı.

Sıra Değiştiğinde

Elle girilen düzeltmenin her şeyin önüne geçmesi isteniyor: operatörün girdiği oran diğer indirimleri bastırmalı. Gömülü sürümde bu, bir bloğun gövde içinde taşınmasıdır; zincir sürümünde dizinin sırasıdır.

cp -r gomulu gomulu-yeni
cp -r zincir zincir-yeni

cat > gomulu-yeni/indirim.mjs <<'SON'
// gomulu/indirim.mjs — dort kural ve siralari tek govdede
export function indirim(gonderi) {
  const uygulanan = [];
  let oran = 0;
  let ziyaret = 1;
  if (typeof gonderi.elleOran === "number") {
    oran += gonderi.elleOran;
    uygulanan.push("elle");
    return { oran, ziyaret, uygulanan };
  }
  ziyaret += 1;
  if (gonderi.sozlesmeli === true) {
    oran += 12;
    uygulanan.push("sozlesmeli");
  }
  ziyaret += 1;
  if (gonderi.agirlik >= 20) {
    oran += 8;
    uygulanan.push("hacim");
  }
  ziyaret += 1;
  if (gonderi.bolge === "3" && gonderi.agirlik < 5) {
    oran += 15;
    uygulanan.push("kampanya");
    return { oran, ziyaret, uygulanan };
  }
  return { oran, ziyaret, uygulanan };
}
SON

sed -i.y 's/\[sozlesmeli, hacim, kampanya, elle\]/[elle, sozlesmeli, hacim, kampanya]/' zincir-yeni/sira.mjs
rm -f zincir-yeni/*.y

for k in gomulu zincir; do
  echo "$k: duzenlenen dosya=$(diff -rq $k $k-yeni | grep -c '^Files')  degisen satir=$(diff -r -u $k $k-yeni | grep '^[+-][^+-]' | grep -vc '^[+-]//')"
done

cat > kosum-yeni.mjs <<'SON'
import { GONDERILER } from "./veri.mjs";
import { indirim as gomuluIndirim } from "./gomulu-yeni/indirim.mjs";
import { indirim as zincirIndirim } from "./zincir-yeni/zincir.mjs";
import { SIRA } from "./zincir-yeni/sira.mjs";

let sapma = 0;
for (const g of GONDERILER) {
  const a = gomuluIndirim(g);
  const b = zincirIndirim(SIRA, g);
  if (a.oran !== b.oran || a.uygulanan.join() !== b.uygulanan.join()) sapma += 1;
}
const g5 = GONDERILER[4];
console.log(`GN-5 yeni sirada: gomulu oran=${gomuluIndirim(g5).oran}  zincir oran=${zincirIndirim(SIRA, g5).oran}`);
console.log(`yeni sirada iki surum arasindaki sapma sayisi = ${sapma}`);
SON
node kosum-yeni.mjs
gomulu: duzenlenen dosya=1  degisen satir=12
zincir: duzenlenen dosya=1  degisen satir=2
GN-5 yeni sirada: gomulu oran=5  zincir oran=5
yeni sirada iki surum arasindaki sapma sayisi = 0

On iki satıra karşı iki satır. GN-5’in oranı 13’ten 5’e düştü: elle düzeltme öne geçtiği için hacim indirimi hiç yoklanmadı. Bu, sıranın bir biçim tercihi değil davranışın parçası olduğunu gösteriyor — ve bu yüzden sıranın nerede yazıldığı önemlidir. Gömülü sürümde sıra, koşulların gövde içindeki yerleşimidir; okumak için gövdenin tamamını izlemek gerekir. Zincir sürümünde sıra tek satırlık bir dizidir.

Kural Sayısı Büyüdüğünde

Kalıbın karşılığını verip vermediği, kural sayısı arttığında görülür. Üç kural daha ekleniyor: on kilogramdan ağır gönderiye yüzde üç yakıt indirimi, ilk gönderisini yapan müşteriye yüzde on, üçüncü bölgeye yüzde dört.

// buyume.mjs — uc yeni kural eklenince govde karmasikligi nasil buyuyor
import { cpSync, readFileSync, readdirSync, writeFileSync } from "node:fs";

const KARAR = /\bif\b|&&|\|\||\?|\bcase\b|\bwhile\b|\bfor\b|\bcontinue\b|\bbreak\b/g;
const enKarmasik = (dizin) =>
  Math.max(
    ...readdirSync(dizin).map((d) => (readFileSync(`${dizin}/${d}`, "utf8").replace(/^\/\/.*$/gm, "").match(KARAR) ?? []).length + 1),
  );

const once = { gomulu: enKarmasik("gomulu"), zincir: enKarmasik("zincir") };

cpSync("gomulu", "gomulu-uc", { recursive: true });
cpSync("zincir", "zincir-uc", { recursive: true });

const EK = `  ziyaret += 1;
  if (gonderi.agirlik >= 10) {
    oran += 3;
    uygulanan.push("yakit");
  }
  ziyaret += 1;
  if (gonderi.ilkMi === true) {
    oran += 10;
    uygulanan.push("ilkGonderi");
  }
  ziyaret += 1;
  if (gonderi.bolge === "3") {
    oran += 4;
    uygulanan.push("uzakBolge");
  }
  ziyaret += 1;`;
writeFileSync(
  "gomulu-uc/indirim.mjs",
  readFileSync("gomulu/indirim.mjs", "utf8").replace('  ziyaret += 1;\n  if (gonderi.bolge === "3" && gonderi.agirlik < 5) {', `${EK}\n  if (gonderi.bolge === "3" && gonderi.agirlik < 5) {`),
);

const YENI = [
  ["yakit", "(g) => g.agirlik >= 10", "() => 3"],
  ["ilk-gonderi", "(g) => g.ilkMi === true", "() => 10"],
  ["uzak-bolge", '(g) => g.bolge === "3"', "() => 4"],
];
for (const [ad, kosul, oran] of YENI) {
  const degisken = ad.replace(/-(\w)/g, (_, c) => c.toUpperCase());
  writeFileSync(
    `zincir-uc/${ad}.mjs`,
    `// zincir/${ad}.mjs\nexport const ${degisken} = {\n  ad: "${degisken}",\n  keser: false,\n  uygunMu: ${kosul},\n  oran: ${oran},\n};\n`,
  );
}
writeFileSync(
  "zincir-uc/sira.mjs",
  `${readFileSync("zincir/sira.mjs", "utf8")
    .replace('import { elle } from "./elle.mjs";', 'import { elle } from "./elle.mjs";\nimport { yakit } from "./yakit.mjs";\nimport { ilkGonderi } from "./ilk-gonderi.mjs";\nimport { uzakBolge } from "./uzak-bolge.mjs";')
    .replace("[sozlesmeli, hacim, kampanya, elle]", "[sozlesmeli, hacim, yakit, ilkGonderi, uzakBolge, kampanya, elle]")}`,
);

const sonra = { gomulu: enKarmasik("gomulu-uc"), zincir: enKarmasik("zincir-uc") };
console.log(`en karmasik govde  gomulu: ${once.gomulu} -> ${sonra.gomulu}   zincir: ${once.zincir} -> ${sonra.zincir}`);
console.log(`dosya sayisi       gomulu: ${readdirSync("gomulu").length} -> ${readdirSync("gomulu-uc").length}   zincir: ${readdirSync("zincir").length} -> ${readdirSync("zincir-uc").length}`);

const { GONDERILER } = await import("./veri.mjs");
const g = await import("./gomulu-uc/indirim.mjs");
const z = await import("./zincir-uc/zincir.mjs");
const s = await import("./zincir-uc/sira.mjs");
let sapma = 0;
let dusenIstek = 0;
for (const gonderi of GONDERILER) {
  const a = g.indirim(gonderi);
  const b = z.indirim(s.SIRA, gonderi);
  if (a.oran !== b.oran) sapma += 1;
  if (b.uygulanan.length === 0) dusenIstek += 1;
}
console.log(`yedi kuralda sapma=${sapma}  hicbir kurala uymayan gonderi=${dusenIstek}`);
en karmasik govde  gomulu: 6 -> 9   zincir: 6 -> 6
dosya sayisi       gomulu: 1 -> 1   zincir: 6 -> 9
yedi kuralda sapma=0  hicbir kurala uymayan gonderi=1

Dört kuralda kalıp karmaşıklık kazancı vermiyordu: altıya karşı altı. Zincir yürütücüsünün kendisi, dört kuralı sıralayan gövde kadar karmaşıktır. Yedi kuralda ise yollar ayrılıyor. Gömülü gövde 6’dan 9’a çıkıyor, zincir yürütücüsü 6’da kalıyor çünkü her yeni kural gövdeye değil kendi dosyasına yazılıyor. Bedelin karşılığı da aynı satırda: dosya sayısı 6’dan 9’a çıkıyor, gömülü sürümde 1’de kalıyor.

Kalıbın eşiği bu iki eğrinin kesiştiği yerdir ve buradaki ölçüm eşiği dört kural civarına koyuyor: dördün altında zincir yalnızca dosya ekler.

Bedelin İkinci Kalemi

Son satırın son sayısı ayrı bir bedeldir: bir gönderi hiçbir kurala uymadı. Zincir bittiğinde oran sıfırdır, ama “indirim yok” ile “hiçbir kural isteği ele almadı” aynı sonuçla temsil edilir. Gömülü gövdede de aynı belirsizlik var, fakat zincirde bu sessizlik daha kolay gözden kaçar: işleyicilerden biri yanlışlıkla listeden çıkarıldığında sonuç hâlâ geçerli görünür, yalnızca oran düşer. Zincirin sonuna hiçbir zaman uygulanmayan bir işleyici koymak bu belirsizliği kapatmanın bilinen yoludur; bu, konunun dokuzuncu dersinde ölçülecek boş nesne kalıbıdır.

Özet

  • Sorumluluk zinciri isteği bir işleyici dizisine verir; her işleyici uygunluğuna kendi karar verir, işini yapar ve isteği sonrakine bırakır ya da zinciri keser.
  • Dört kuralla iki sürüm birebir aynı oran, aynı uygulanan kural listesi ve aynı ziyaret sayısını üretti; sapma 0 çıktı.
  • Kural sırası değiştiğinde gömülü sürümde 12, zincir sürümünde 2 satır değişti; sıra davranışın parçasıdır — elle düzeltme öne geçince GN-5’in oranı 13’ten 5’e indi.
  • Dört kuralda en karmaşık gövde ikisinde de 6; yedi kuralda gömülü sürüm 9’a çıktı, zincir 6’da kaldı, karşılığında dosya sayısı 6’dan 9’a yükseldi.
  • Bedel: kural başına bir dosya, ve hiçbir işleyicinin ele almadığı istek ile indirimsiz istek aynı sonuçla temsil edilir.

Sonraki Adım

Zincir isteği bir dizide sırayla dolaştırıyor: dolaşma düz, tek yönlü ve sıra dizinin kendisinde. Kitaplığın rota tarafında dolaşılacak yapı düz değil. Bir rota, aktarma noktalarından oluşan bir ağaçtır: merkez, bölge deposu, dağıtım şubesi, teslim noktası. Bu ağaç üzerinde birbirinden bağımsız beş işlem yapılıyor — toplam mesafe, en uzun bekleme, kapasite denetimi, etiket listesi, maliyet toplamı — ve her işlem ağacı kendi gezinme koduyla dolaşıyor. Sonraki ders gezinme kodunun kaç kez yinelendiğini sayar, sonra gezinmeyi işlemden ayırıp yeni işlem ve yeni düğüm türü eklendiğinde dokunulan dosya sayısını iki yönde birden ölçer.

İ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