---
title: Tekil
source: 'https://academia.sh/tr/kurslar/tasarim-kaliplari/tekil'
course: 'Tasarım Kalıpları'
language: tr
updated: '2026-08-17T18:10:38+00:00'
license: 'CC BY-SA 4.0'
---

# Tekil

Tek örneğin küresel durum olarak tutulmasının sınanabilirlik cinsinden ölçülmesi: aynı dört sınamanın modül düzeyinde tek örnek tutan kayıt ile örnek üreten kayda uygulanması, başarısız sınama sayısının sayılması, sınama sırası değiştirildiğinde sonucun değişmesi ve sıfırlama düzeneğinin dışa açılan yüzeye eklediği adın ölçülmesi.

Dört yaratımsal düzenek nesnenin nereden geldiğini düzenledi; hiçbiri nesnenin kaç tane
olduğunu sormadı. Kitaplıkta tek örneğinin bulunması istenen nesneler var: tarife kaydı bunların
başında geliyor, çünkü iki ayrı kayıt aynı ada iki farklı tarife bağlayabilir ve aynı gönderi
iki farklı ücret alabilir.

**Tekil** (singleton) bu isteği nesnenin kendisine yazar: tip kendi örneğini kurar, tek
tuttuğunu güvenceye alır ve o örneğe küresel erişim verir. Klasik biçiminde kurucu dışarıya
kapatılır ve erişim tek bir sınıf yöntemiyle verilir. Modül sistemi olan bir dilde bu iş zaten
modülün kendisiyle yapılır: bir modülün gövdesi süreç başına bir kez çalışır ve modül düzeyindeki
durum bütün ithal edenler için aynıdır. Bu ders kalıbın bedelini tek bir ölçüyle sayar:
sınanabilirlik.

## Modül Düzeyinde Tek Örnek

Birinci sürümde kayıt bir modüldür. Durum modül düzeyinde durur, ilk ithalde kurulur ve
süreç boyunca tek kalır.

```js
// tekil/tarifeler/standart.mjs — standart tarife tanimi
export const STANDART = { ad: "standart", taban: () => 3900 };
```

```js
// tekil/kayit.mjs — modul duzeyinde tek ornek; durum ilk ithalde kurulur
import { STANDART } from "./tarifeler/standart.mjs";

const TARIFELER = new Map();

export function kaydet(tarifeNesnesi) {
  TARIFELER.set(tarifeNesnesi.ad, tarifeNesnesi);
}

export function tarife(ad) {
  const t = TARIFELER.get(ad);
  if (t === undefined) throw new RangeError(`bilinmeyen tarife: ${ad}`);
  return t;
}

export const adlar = () => [...TARIFELER.keys()];

export function sifirla() {
  TARIFELER.clear();
  kaydet(STANDART);
}

sifirla();
```

Son satır kalıbın belirleyici yanıdır: durum modül yüklenirken kurulur, dolayısıyla ithal eden
hiçbir modül kurulum anını seçemez. `TARIFELER` dışa verilmemiştir, ama `kaydet` çağıran her
modül onu değiştirir.

## Aynı Kaydın Örnek Olarak Kurulması

İkinci sürümde kayıt bir tiptir ve durum örneğin özel alanında durur. Kaç örnek kurulacağına
modül karar vermez; bunu çağıran belirler.

```js
// ornek/tarifeler/standart.mjs — standart tarife tanimi
export const STANDART = { ad: "standart", taban: () => 3900 };
```

```js
// ornek/kayit.mjs — her cagri bagimsiz bir kayit ornegi uretir
import { STANDART } from "./tarifeler/standart.mjs";

export class TarifeKaydi {
  #tarifeler = new Map();

  kaydet(tarifeNesnesi) {
    this.#tarifeler.set(tarifeNesnesi.ad, tarifeNesnesi);
    return this;
  }

  tarife(ad) {
    const t = this.#tarifeler.get(ad);
    if (t === undefined) throw new RangeError(`bilinmeyen tarife: ${ad}`);
    return t;
  }

  adlar() {
    return [...this.#tarifeler.keys()];
  }
}

export const yeniKayit = () => new TarifeKaydi().kaydet(STANDART);
```

Uygulamanın tek kayıtla çalışması gereksinimi ortadan kalkmıyor; yer değiştiriyor. Bileşim kökü
`yeniKayit` çağrısını bir kez yapar ve elde ettiği örneği kullanacak modüllere verir. Yani
"tek örnek" kararı nesnenin kendisinden bileşim köküne taşınmıştır.

## Aynı Sınamaların İki Sürüme Uygulanması

İki sürümün dışa verdiği yöntem adları aynıdır: `kaydet`, `tarife`, `adlar`. Bu yüzden aynı
sınama kümesi ikisine de uygulanabilir. Sınamalar kayıt sağlayıcısını parametre olarak alır;
tekil sürümde sağlayıcı her çağrıda aynı modülü, örnek sürümünde her çağrıda yeni bir örnek
döndürür.

```js
// sinamalar.mjs — dort sinama; her biri bir kayit saglayicisi alir
import assert from "node:assert/strict";

export const SINAMALAR = [
  ["yuklenen tarife sayisi", (kayitAl) => assert.equal(kayitAl().adlar().length, 1)],
  ["yeni tarife kaydedilir", (kayitAl) => {
    const k = kayitAl();
    k.kaydet({ ad: "indirimli", taban: () => 3200 });
    assert.equal(k.tarife("indirimli").taban(), 3200);
  }],
  ["kayit yalniz yuklenenleri icerir", (kayitAl) => assert.equal(kayitAl().adlar().length, 1)],
  ["bilinmeyen tarife hata verir",
    (kayitAl) => assert.throws(() => kayitAl().tarife("indirimli"), RangeError)],
];
```

```js
// duz.test.mjs — dort sinama tanimlandigi sirada iki surume uygulanir
import test from "node:test";
import { SINAMALAR } from "./sinamalar.mjs";
import * as tekilKayit from "./tekil/kayit.mjs";
import { yeniKayit } from "./ornek/kayit.mjs";

const SURUM = [["tekil", () => tekilKayit], ["ornek", () => yeniKayit()]];

for (const [surum, kayitAl] of SURUM)
  for (const [ad, sinama] of SINAMALAR) test(`${surum}: ${ad}`, () => sinama(kayitAl));
```

```sh
node --test --test-reporter=tap duz.test.mjs 2>&1 | grep -E '^(not ok|# (pass|fail))'
```

```
not ok 3 - tekil: kayit yalniz yuklenenleri icerir
not ok 4 - tekil: bilinmeyen tarife hata verir
# pass 6
# fail 2
```

Sekiz sınamanın ikisi başarısız ve ikisi de tekil sürüme ait. Örnek sürümünde dördün dördü
geçti. Başarısız iki sınamanın kendi içinde bir kusuru yok: üçüncü sınama kayıtta yalnız
yüklenen tarifelerin bulunmasını, dördüncüsü bilinmeyen bir tarifenin hata vermesini bekliyor.
İkisini de bozan şey ikinci sınamanın çalışmış olmasıdır — ikinci sınama kayda `indirimli`
tarifesini yazdı ve o yazma süreç boyunca kaldı.

Bu, Bağlaşım Türleri dersindeki ortak bağlaşımın tanımının aynısıdır: bir birimin davranışı,
onu hiç ithal etmemiş bir birim tarafından değiştirilebiliyor. Üçüncü sınama ikinci sınamayı
ithal etmez, adını bilmez, sırasını seçmez; buna karşın sonucu ona bağlıdır.

## Sıranın Değiştirilmesi

Bağın gerçekten sıraya bağlı olduğu, aynı dört sınama ters sırada koşturularak sınanır.

```js
// ters.test.mjs — ayni dort sinama ters sirada kosar
import test from "node:test";
import { SINAMALAR } from "./sinamalar.mjs";
import * as tekilKayit from "./tekil/kayit.mjs";
import { yeniKayit } from "./ornek/kayit.mjs";

const SURUM = [["tekil", () => tekilKayit], ["ornek", () => yeniKayit()]];

for (const [surum, kayitAl] of SURUM)
  for (const [ad, sinama] of [...SINAMALAR].reverse())
    test(`${surum}: ${ad}`, () => sinama(kayitAl));
```

```sh
node --test --test-reporter=tap ters.test.mjs 2>&1 | grep -E '^(not ok|# (pass|fail))'
```

```
not ok 4 - tekil: yuklenen tarife sayisi
# pass 7
# fail 1
```

Başarısız sınama sayısı 2'den 1'e indi ve başarısız olan sınama değişti: düz sırada üçüncü ve
dördüncü sınamalar, ters sırada birinci sınama çöktü. Sınamaların metnine, kayıt modüllerine ve
tarife tanımına dokunulmadı; değişen tek şey sıra oldu. Frontend Kalitesi kursunda tanımlanan
kırılgan test tam olarak budur ve sayısı tekil sürümde 2, örnek sürümünde 0'dır.

Örnek sürümü iki koşumda da dört sınamanın dördünü geçti, çünkü her sınama kendi kaydını kurar
ve bitince bırakır.

## Sıfırlamanın Bedeli

Tekil sürüm sınanabilir hâle getirilebilir; bunun için durumu sıfırlayan bir yöntem ve her
sınamadan önce onu çağıran bir adım gerekir.

```js
// sifirlamali.test.mjs — tekil surum her sinamadan once sifirlanir
import test, { beforeEach } from "node:test";
import { SINAMALAR } from "./sinamalar.mjs";
import * as tekilKayit from "./tekil/kayit.mjs";

beforeEach(() => tekilKayit.sifirla());

for (const [ad, sinama] of SINAMALAR) test(`tekil: ${ad}`, () => sinama(() => tekilKayit));
```

```sh
node --test --test-reporter=tap sifirlamali.test.mjs 2>&1 | grep -E '^(not ok|# (pass|fail))'
for d in tekil/kayit.mjs ornek/kayit.mjs; do
  echo "$d: $(grep -c 'export ' $d) disa acilan ad"
done
```

```
# pass 4
# fail 0
tekil/kayit.mjs: 4 disa acilan ad
ornek/kayit.mjs: 2 disa acilan ad
```

Dördün dördü geçti; çözüm işliyor. Bedeli iki sayıda. Birincisi dışa açılan ad sayısıdır: tekil
sürüm dört ad veriyor, örnek sürümü iki. Fazladan çıkan `sifirla` adı üretimde hiçbir yerde
çağrılmaz; yalnız sınamalar için vardır. Temiz Kod kursundaki en dar görünürlük ilkesine göre bu
ad ölü yüzeydir ve üretim kodundan da çağrılabilir olması yeni bir hata biçimi açar.

İkinci bedel sayının kendisinde değil, sürdürülmesinde. Sıfırlama listesi elle tutulur: kayda
ikinci bir küresel alan eklendiğinde `sifirla` de düzenlenmelidir, yoksa sınamalar yeniden
sıraya bağlı hâle gelir ve bu bağ ancak sıra değiştiğinde görünür. Örnek sürümünde böyle bir
liste yoktur, çünkü sıfırlanacak durum yeni örnekle birlikte zaten yoktur.

## Tekil Ne Zaman Uygulanır

Ölçüler kalıbı tümden yasaklamıyor, uygulanabildiği bağlamı daraltıyor. Tekil, tuttuğu durum
süreç boyunca **değişmiyorsa** ölçüleri bozmaz: sabit bir tablo, bir kez okunan bir yapılandırma
ya da değiştirilemez bir eşleme modül düzeyinde durabilir, çünkü sınamalar arasında taşınacak
bir değişiklik yoktur. Ölçüleri bozan şey tekillik değil, tekil örneğin **değişebilir**
olmasıdır.

Kayıt bu ölçüte uymuyor: `kaydet` yöntemi durumu değiştirir. Kitaplıkta tekil olarak
bırakılabilecek şey tarife nesnelerinin kendisidir — dondurulmuş, davranışı sabit nesneler —
kayıt değil.

## Özet

- Tekil, tek örneği nesnenin kendi sorumluluğuna yazar ve ona küresel erişim verir; modül
  sistemi olan bir dilde modül düzeyindeki durum bu kalıbı zaten gerçekleştirir.
- Aynı dört sınama iki sürüme uygulandığında tekil sürümde 2 sınama başarısız oldu, örnek
  sürümünde 4 sınamanın dördü geçti.
- Başarısızlığın nedeni sınamaların kendisi değil sırasıdır: sınamalar ters sırada koşturulunca
  başarısız sayısı 2'den 1'e indi ve başarısız olan sınama değişti.
- Tekil sürüm sıfırlama yöntemiyle sınanabilir hâle geldi ve dört sınamanın dördü geçti; bedel
  dışa açılan ad sayısının 2'den 4'e çıkması ve elle sürdürülen bir sıfırlama listesidir.
- Tekil, tuttuğu durum değiştirilemez olduğunda ölçüleri bozmaz; bozan şey tekillik değil, tek
  örneğin değişebilir olmasıdır.

## Sonraki Adım

Örnek sürümü sınamaları geçirdi, ama bir soruyu açıkta bıraktı: kaydı kullanan modül o örneği
nereden alacak. Modülün içinde `yeniKayit` çağrılırsa her modül kendi kaydını kurar ve tek örnek
gereksinimi çiğnenir; modül tekil sürümü ithal ederse ölçüler geri gelir. Sonraki ders bu
soruyu yaratım sorumluluğunun taşınması olarak ele alır: bir modülün kendi bağımlılığını
kurduğu sürümle bağımlılığı dışarıdan aldığı sürümde ücret modülünün doğrudan bağ sayısını ve
ithal kapanışının boyutunu karşılaştırır.
