Mert Özen Her Satırda Daha İleri
Yazılım Tasarımı Design Patterns — #01

Design Pattern Nedir? GoF Kataloğu, Pattern Anatomisi ve Modern Dillerde Kaybolan Pattern'ler

Mert Özen 7 Kas 2026 14 dk 5 görüntülenme
Design Pattern Nedir? GoF Kataloğu, Pattern Anatomisi ve Modern Dillerde Kaybolan Pattern'ler

Design pattern, belli bir bağlamda tekrar eden bir tasarım problemine verilmiş, adı, rolleri ve bedeli (consequences) belli bir çözümdür; ezberlenecek bir diyagram değildir. Alexander'dan GoF'a pattern fikri, GoF'un 13 bölümlük template'i, purpose × scope haritası ve Norvig'in tespitiyle C#'ta yield, event ve lambda'ya dönüşüp görünmez olan pattern'ler.

Sürüm notu: Bu yazıdaki kodlar C# 14 ve .NET 10 ile derlenip denenmiştir.

Önce Tahmin Et

Bookstore'un katalog ekranı stokta olan kitapları listeliyor. Shelf.InStock bir IEnumerable<Book> döndürüyor ve içinde ne bir List ne de bir array var; yalnızca yield return. Kod çalışınca ilk satırda hangi tip adı yazar, o tip hangi interface'leri implement eder? List`1 mi, Book[] mi, yoksa başka bir şey mi?

using Bookstore.Catalog;

Book[] books =
[
    new("978-0441013593", "Dune", 3),
    new("978-0141439518", "Emma", 0),
    new("978-0547928227", "The Hobbit", 5)
];

IEnumerable<Book> available = Shelf.InStock(books);

Type type = available.GetType();
Console.WriteLine($"Type: {type.Name}");
Console.WriteLine($"Compiler generated: {type.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false)}");
Console.WriteLine("Interfaces:");
foreach (Type i in type.GetInterfaces().OrderBy(i => i.Name))
    Console.WriteLine($"  {i.Name}");

foreach (Book book in available)
    Console.WriteLine(book.Title);

namespace Bookstore.Catalog
{
    public record Book(string Isbn, string Title, int Stock);

    public static class Shelf
    {
        public static IEnumerable<Book> InStock(IEnumerable<Book> books)
        {
            foreach (Book book in books)
            {
                if (book.Stock > 0)
                    yield return book;
            }
        }
    }
}

Pattern Kaybolmadı, Derleyiciye Taşındı

Çıktı:

Type: <InStock>d__0
Compiler generated: True
Interfaces:
  IDisposable
  IEnumerable
  IEnumerable`1
  IEnumerator
  IEnumerator`1
Dune
The Hobbit

Ne list ne array: derleyicinin senin için yazdığı, adı <InStock>d__0 olan bir class. Hem IEnumerable<Book> hem IEnumerator<Book>; yani hem gezilecek collection hem de gezinme durumunu tutan cursor. Bu, 1994'te yayımlanan Design Patterns kitabındaki Iterator pattern'inin ta kendisi. Elle yazsaydın aşağı yukarı şöyle olurdu:

using System.Collections;
using Bookstore.Catalog;

Book[] books =
[
    new("978-0441013593", "Dune", 3),
    new("978-0141439518", "Emma", 0),
    new("978-0547928227", "The Hobbit", 5)
];

foreach (Book book in new InStockBooks(books))
    Console.WriteLine(book.Title);

namespace Bookstore.Catalog
{
    public record Book(string Isbn, string Title, int Stock);

    // GoF'taki Aggregate rolü: iterator'ı üreten collection
    public sealed class InStockBooks(IReadOnlyList<Book> source) : IEnumerable<Book>
    {
        public IEnumerator<Book> GetEnumerator() => new Iterator(source);
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        // GoF'taki ConcreteIterator rolü: gezinme durumunu (index) kendisi tutar
        private sealed class Iterator(IReadOnlyList<Book> source) : IEnumerator<Book>
        {
            private int _index = -1;

            public Book Current => source[_index];
            object IEnumerator.Current => Current;

            public bool MoveNext()
            {
                while (++_index < source.Count)
                {
                    if (source[_index].Stock > 0)
                        return true;
                }
                return false;
            }

            public void Reset() => _index = -1;
            public void Dispose() { }
        }
    }
}
Dune
The Hobbit

Kitaptaki rol adlarıyla: InStockBooks aggregate, iç class'taki Iterator ise concrete iterator. Gezinme durumu (_index) collection'da değil iterator'da yaşıyor; bu sayede aynı collection üzerinde aynı anda iki foreach birbirini bozmadan dönebiliyor. C# 2.0 ile gelen yield return bu yaklaşık kırk satırı dört satıra indirdi. Pattern kodundan kayboldu ama programdan kaybolmadı: derleyici aynı yapıyı, üstüne bir de state machine ekleyerek üretiyor.

Bu yazı serinin girişi ve bu gözlemle başlaması bilinçli. Design pattern'leri ezberlenecek 23 diyagram olarak öğrenirsen her yerde onları aramaya başlarsın. Problemin şeklini tanımayı öğrenirsen pattern'in bazen bir interface hiyerarşisi, bazen bir lambda, bazen de derleyicinin senin yerine yazdığı bir class olduğunu görürsün. Seri boyunca hedef ikincisi.

Alexander'dan GoF'a: Bir Fikrin Kısa Tarihi

"Pattern" kelimesi yazılıma mimarlıktan geldi. Mimar Christopher Alexander 1977'de yayımlanan A Pattern Language kitabında şehirlerden pencere kenarlarına kadar 253 pattern anlatır. Her biri aynı kalıpta yazılmıştır: belli bir bağlamda tekrar tekrar ortaya çıkan bir problem ve o problemin çözümünün, her seferinde farklı biçimde uygulanabilecek çekirdeği. Alexander'a göre bir pattern'i milyon kez kullanıp hiçbirinde aynı şeyi yapmamak mümkündür; pattern bir kalıp değil, bir problem-çözüm ilişkisidir.

Fikri yazılıma ilk taşıyanlardan ikisi Kent Beck (ilerde Extreme Programming ve TDD (Test-Driven Development) ile tanınacak) ve Ward Cunningham'dı (wiki'nin mucidi). 1987'deki OOPSLA (Object-Oriented Programming, Systems, Languages and Applications) konferansına sundukları kısa çalışmada Smalltalk ile kullanıcı arayüzü tasarlayan kişiler için beş pattern'lik küçük bir pattern language önerdiler. Asıl kırılma ise 1994'te geldi: Erich Gamma, Richard Helm, Ralph Johnson ve John Vlissides'in Design Patterns: Elements of Reusable Object-Oriented Software kitabı. Dört yazarlı olduğu için kitap kısa sürede GoF (Gang of Four, "dörtlü çete") diye anılmaya başladı; bu seride de kitabı ve içindeki 23 pattern'i bu adla anacağız.

GoF'a göre bir pattern'in dört temel öğesi var: bir ad, çözdüğü problem, çözüm ve sonuçlar (consequences). Ad, ekibe ortak bir dil verir; "buraya bir decorator koyalım" cümlesi on dakikalık bir anlatımın yerini tutar. Problem, pattern'in ne zaman uygulanacağını söyler. Çözüm somut bir kod değil, bir template'tir; class'ların ve object'lerin rollerini, ilişkilerini ve sorumluluklarını anlatır. Sonuçlar ise en çok atlanan ama en değerli kısımdır: pattern'in bedeli, esneklik karşılığında neyi pahalılaştırdığı.

Pattern Anatomisi: GoF'un Template'i

GoF her pattern'i aynı on üç bölümle anlatır. Bu yapıyı bilmek kitabı ve diğer pattern kataloglarını okumayı kolaylaştırır; bir pattern'i tartışırken hangi soruyu cevapladığını da netleştirir. Aşağıdaki tabloda bölümlerin her birini .NET'in en eski decorator'larından Stream sarmalayıcıları üzerinden örnekledim:

BölümCevapladığı soruDecorator ve .NET Stream örneği
Pattern Name and ClassificationAdı ne, hangi grupta?Decorator, structural, object scope
IntentTek cümlede ne yapar?Bir object'e runtime'da, alt class açmadan sorumluluk eklemek
Also Known AsBaşka hangi adlarla bilinir?Wrapper
MotivationHangi senaryo onu doğurur?Dosyaya hem buffer hem sıkıştırma eklemek istiyorsun ama her kombinasyon için bir alt class istemiyorsun
ApplicabilityNe zaman uygulanır?Eklenecek davranışlar bağımsız ve birleştirilebilir olduğunda
StructureClass diyagramı nasıl?Component, ConcreteComponent, Decorator, ConcreteDecorator
ParticipantsRollere hangi tipler oturur?Stream, FileStream, BufferedStream, GZipStream
CollaborationsRoller nasıl konuşur?Decorator çağrıyı sardığı stream'e iletir, öncesinde ya da sonrasında kendi işini yapar
ConsequencesKazanç ve bedel ne?Esnek birleştirme; buna karşılık çok sayıda küçük object ve zor takip edilen çağrı zinciri
ImplementationUygularken nelere dikkat?Interface'i birebir korumak, iç stream'in sahipliği (leaveOpen)
Sample CodeKodu nasıl görünür?Kitapta C++ ve Smalltalk, bu seride C# 14
Known UsesGerçek sistemlerde nerede?CryptoStream, DelegatingHandler
Related PatternsHangi pattern'lerle karışır ya da birlikte kullanılır?Adapter (interface'i değiştirir), Proxy (erişimi kontrol eder), Composite

Kitabın en çok alıntılanan iki cümlesi de bu bölümlerin arasından değil, girişinden çıkar: bir implementation'a değil bir interface'e göre programla ve class inheritance yerine object composition'ı tercih et. Yirmi üç pattern'in büyük kısmı bu iki fikrin farklı problemlere uygulanmasıdır. İkinci fikri Composition over Inheritance yazısında ayrıntılı konuşmuştuk; o yazı SOLID (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) ve Design Principles serisinin parçası.

Pattern, Principle, Idiom, Framework

Bu dört kelime code review'da sık sık birbirinin yerine kullanılıyor; oysa farklı seviyelerde, farklı soruları cevaplıyorlar.

Principle bir ölçüttür: bir tasarımın iyi olup olmadığını değerlendirmek için kullandığın soru. "Bu class'ın kaç değişim nedeni var?" SRP'nin (Single Responsibility Principle) sorusudur ve tek başına hiçbir kod üretmez. Pattern ise belli bir problem için adı konmuş bir çözümdür; bir principle'ı karşılamanın yollarından biri olabilir. Strategy, OCP'nin (Open/Closed Principle) tarif ettiği "değiştirmeden genişletme"nin en bilinen uygulamasıdır ama OCP'yi karşılamanın tek yolu değildir.

Idiom bir dile özgü, daha küçük ölçekli bir alışkanlıktır. James Coplien 1991'deki Advanced C++ Programming Styles and Idioms kitabıyla bu kavramı yaygınlaştırdı. C#'ta using ile kaynak serbest bırakmak, TryParse ailesindeki bool dönüp sonucu out ile vermek ya da event'i ?.Invoke ile tetiklemek idiom'dur. Başka bir dile taşındığında anlamını kaybeder ya da gereksizleşir. GoF bu ayrımın göreceli olduğunu kitabın başında açıkça söyler: kendi pattern'leri Smalltalk ve C++ seviyesinde bir dili varsayar; prosedürel bir dil varsaysalardı inheritance, encapsulation ve polymorphism'i de pattern olarak listeleyebileceklerini yazarlar. Bir dilde pattern olan şey, daha güçlü bir dilde sıradan bir dil özelliği olabilir. Yazının ilerisinde bu cümlenin C# için ne anlama geldiğine bakacağız.

Framework ise çalışan, büyük ölçekli bir koddur ve senin kodunu o çağırır. ASP.NET Core'un middleware pipeline'ı bir framework parçası; içinde Chain of Responsibility pattern'i yaşıyor ama sen pattern'i değil, framework'ü kullanıyorsun. GoF farkı üç maddede toplar: pattern'ler framework'lerden daha soyuttur (koda değil fikre karşılık gelir), daha küçük yapı taşlarıdır (bir framework genelde birkaç pattern içerir) ve daha az uzmanlaşmıştır (belli bir uygulama alanına bağlı değildir).

23 Pattern'in Haritası: Purpose ve Scope

GoF pattern'leri iki eksende sınıflandırır. Birincisi purpose, yani pattern'in ne iş yaptığı: creational pattern'ler object oluşturma sürecini, structural pattern'ler class ve object'lerin nasıl bir araya geldiğini, behavioral pattern'ler ise sorumlulukların nasıl dağıtıldığını ve object'lerin nasıl haberleştiğini konu alır. İkinci eksen scope: class scope'taki pattern'ler ilişkiyi inheritance ile kurar, ilişki compile time'da sabitlenir; object scope'takiler ilişkiyi object'ler arasında kurar ve runtime'da değiştirilebilir.

ScopeCreationalStructuralBehavioral
ClassFactory MethodAdapter (class)Interpreter, Template Method
ObjectAbstract Factory, Builder, Prototype, SingletonAdapter (object), Bridge, Composite, Decorator, Facade, Flyweight, ProxyChain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Visitor

Tablonun dikkat çeken yanı, class scope'un ne kadar küçük kaldığı. Yirmi üç pattern'in yalnızca dördü ilişkiyi inheritance ile kuruyor; geri kalanı object composition ile. Adapter iki satırda birden görünüyor çünkü hem alt class'la hem de sarmalanan bir object ile yazılabiliyor; C#'ta çoklu class inheritance olmadığı için pratikte object adapter yazacaksın, Adapter yazısında nedenine bakacağız.

Aşağıdaki harita aynı yirmi üç pattern'i gruplarıyla ve .NET'te ilk karşına çıkacakları yerle birlikte gösteriyor. Renkli çerçeveli olanlar, bir .NET projesinde haberin olsun olmasın her gün kullandıkların:

01-gof-map-tr.png

Pattern adlarının BCL (Base Class Library, .NET'in temel kütüphanesi) içinde ne kadar yaygın olduğunu merak edip kendi makinemde bir sayım yaptım. Shared framework'teki bütün assembly'lerin public tiplerini gezip adının sonuna bakan küçük bir program:

using System.Reflection;
using System.Runtime.InteropServices;

// Shared framework'teki (Microsoft.NETCore.App) public tipler; CoreLib ayrıca eklenir
string runtimeDir = RuntimeEnvironment.GetRuntimeDirectory();
List<Assembly> assemblies = [typeof(object).Assembly];
foreach (string path in Directory.EnumerateFiles(runtimeDir, "System.*.dll"))
{
    try
    {
        assemblies.Add(Assembly.LoadFrom(path));
    }
    catch (Exception ex) when (ex is BadImageFormatException or FileNotFoundException)
    {
        // native ya da yüklenemeyen dosyaları atla
    }
}

string[] suffixes = ["Factory", "Builder", "Visitor", "Provider", "Adapter", "Proxy", "Handler", "Observer", "Iterator", "Enumerator"];
HashSet<string> seen = [];
Dictionary<string, List<string>> hits = suffixes.ToDictionary(s => s, _ => new List<string>());

foreach (Type type in assemblies.Distinct().SelectMany(a => a.GetExportedTypes()))
{
    if (!seen.Add(type.FullName!))
        continue;

    int tick = type.Name.IndexOf('`');
    string name = tick < 0 ? type.Name : type.Name[..tick];
    foreach (string suffix in suffixes.Where(s => name.EndsWith(s, StringComparison.Ordinal)))
        hits[suffix].Add(name);
}

Console.WriteLine($".NET {Environment.Version}: {assemblies.Distinct().Count()} assemblies, {seen.Count} public types");
foreach ((string suffix, List<string> names) in hits.OrderByDescending(h => h.Value.Count))
    Console.WriteLine($"{suffix,-10} {names.Count,4}  e.g. {string.Join(", ", names.Distinct().Order().Take(3))}");
.NET 10.0.13: 163 assemblies, 3954 public types
Enumerator   95  e.g. AceEnumerator, ArrayEnumerator, AsnEncodedDataEnumerator
Handler      84  e.g. ActiveDesignerEventHandler, AddingNewEventHandler, AppendInterpolatedStringHandler
Builder      49  e.g. AssemblyBuilder, AsyncIteratorMethodBuilder, AsyncTaskMethodBuilder
Provider     42  e.g. AesCryptoServiceProvider, AssociatedMetadataTypeTypeDescriptionProvider, CaseInsensitiveHashCodeProvider
Iterator     29  e.g. AncestorDocOrderIterator, AncestorIterator, ArgIterator
Factory      10  e.g. DbProviderFactory, FormattableStringFactory, ICustomFactory
Adapter       5  e.g. DataAdapter, DbDataAdapter, ICustomAdapter
Proxy         4  e.g. AssemblyNameProxy, DispatchProxy, IWebProxy
Visitor       2  e.g. DynamicExpressionVisitor, ExpressionVisitor
Observer      1  e.g. IObserver

Sayılar tek başına bir şey kanıtlamıyor ve asıl ders de burada. Handler ile biten 84 tipin neredeyse hepsi event handler delegate'i; Chain of Responsibility ile ilgileri yok. Provider'ların çoğu GoF'ta karşılığı olmayan bir plug-in noktası. Listenin tepesindeki 95 Enumerator ise List<T>.Enumerator gibi her collection'ın içindeki nested struct'lar: Iterator, adında Iterator geçmese de .NET'in açık ara en yaygın pattern'i. Bir pattern'i adından değil, rollerden ve problemden tanırsın.

Norvig'in Tespiti: Modern Dillerde Kaybolan Pattern'ler

Peter Norvig (yapay zekâ alanının temel ders kitabı Artificial Intelligence: A Modern Approach'un yazarlarından, o dönem Harlequin şirketinde, sonra Google'ın araştırma direktörü) 1996'da Object World konferansında "Design Patterns in Dynamic Languages" başlıklı bir sunum yaptı. Sunumun en çok alıntılanan slaytı şunu söyler: GoF'un 23 pattern'inden 16'sı Dylan ya da Lisp gibi dinamik dillerde ya görünmez olur ya da çok basitleşir. Norvig nedenleri dil özelliklerine göre gruplar: first-class type'lar altı pattern'i (Abstract Factory, Flyweight, Factory Method, State, Proxy, Chain of Responsibility), first-class function'lar dört pattern'i (Command, Strategy, Template Method, Visitor), macro'lar Interpreter ve Iterator'ı, method combination Mediator ve Observer'ı, multimethod'lar Builder'ı, module'ler de Facade'ı sadeleştirir.

Norvig bir pattern'in dilde üç seviyede bulunabileceğini de söyler. Invisible: pattern dilin o kadar parçasıdır ki fark etmezsin. Informal: adıyla anılır ama her kullanımda baştan elle yazılır. Formal: pattern dilin içinde bir kez tanımlanır, sen yalnızca çağırırsın. Yazının başındaki yield return örneği, Iterator'ın C#'ta informal seviyeden invisible seviyeye nasıl taşındığının kanıtı.

C# dinamik bir dil değil ama yirmi beş yılda Norvig'in saydığı özelliklerin önemli bir kısmını kazandı. Delegate'ler ve lambda'lar first-class function'ı, event keyword'ü Observer'ı, yield Iterator'ı, expression tree'ler Interpreter'ın bir kısmını, record ve with Prototype'ı, pattern matching'li switch ise Visitor'ın çoğu kullanımını dilin içine aldı. Bunların her birini kendi yazısında ayrıntılı göreceğiz; burada tek bir örnekle bir sınırı çizelim. Command pattern'i, yani bir isteği object'e çevirmek, C#'ta bir Action'a kadar küçülebilir:

using System.Globalization;
using System.Text.Json;
using Bookstore.Catalog;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

PriceList prices = new();

// 1) Pattern'in "görünmez" hâli: command yalnızca bir lambda
Queue<Action> pending = new();
pending.Enqueue(() => prices.Change("978-0441013593", 14.99m));
pending.Enqueue(() => prices.Change("978-0141439518", 6.50m));
while (pending.TryDequeue(out Action? run))
    run();

// 2) Pattern'in açık hâli: command adı olan, veri taşıyan bir object
ChangePrice command = new("978-0547928227", 11.25m);
command.Execute(prices);

Console.WriteLine(JsonSerializer.Serialize(command));

Action lambda = () => prices.Change("978-0547928227", 9.99m);
Console.WriteLine($"Lambda method: {lambda.Method.Name}");
try
{
    Console.WriteLine(JsonSerializer.Serialize(lambda));
}
catch (NotSupportedException ex)
{
    Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}

namespace Bookstore.Catalog
{
    public sealed class PriceList
    {
        private readonly Dictionary<string, decimal> _prices = [];

        public void Change(string isbn, decimal newPrice)
        {
            _prices[isbn] = newPrice;
            Console.WriteLine($"{isbn} -> {newPrice:0.00}");
        }
    }

    public sealed record ChangePrice(string Isbn, decimal NewPrice)
    {
        public void Execute(PriceList prices) => prices.Change(Isbn, NewPrice);
    }
}
978-0441013593 -> 14.99
978-0141439518 -> 6.50
978-0547928227 -> 11.25
{"Isbn":"978-0547928227","NewPrice":11.25}
Lambda method: <<Main>$>b__2
NotSupportedException: Serialization and deserialization of 'System.Action' instances is not supported. Path: $.

İki biçim de fiyatı değiştirdi. Fark, işi bir queue'ya yazmak, log'lamak ya da başka bir process'e göndermek istediğinde ortaya çıkıyor. ChangePrice record'u adı olan, veri taşıyan bir object; JSON'a (JavaScript Object Notation) dönüşüp bir message queue'ya gidebilir, ertesi gün tekrar okunabilir. Lambda ise derleyicinin <<Main>$>b__2 diye adlandırdığı bir method'a ve yakaladığı değişkenlere işaret ediyor; serialize edilemez, log'da anlamlı bir adı yoktur, geri alınamaz. Norvig'in tespiti doğru: pattern'in yapısı (interface, alt class, invoker) dil özelliğine dönüşünce kayboluyor. Ama pattern'in GoF'taki consequences bölümünde yazan bazı kazançlar (kuyruğa atma, log'lama, undo) ancak açık object biçiminde kalıyor. Seçimi sen yapıyorsun ve bu seri her yazıda o seçimin ölçütlerini vermeye çalışacak.

Bu Serideki Her Yazının Template'i

GoF'un on üç bölümlük template'i bir katalog için ideal ama blog yazısı için ağır. Bu seride her pattern aynı altı adımla ilerleyecek:

  • Problem: Bookstore'da pattern'i gerçekten gerektiren bir senaryo; pattern'den değil acıdan başlıyoruz.
  • Naif kod: çoğumuzun ilk yazdığı çözüm ve tam olarak nerede kırıldığı, gerçek çıktısıyla.
  • Pattern: GoF'un tanımı, rolleri ve C# 14 ile yazılmış hâli.
  • .NET'teki gerçek örnek: aynı pattern'in BCL ya da ASP.NET Core içinde nerede yaşadığı.
  • Modern C# alternatifi: lambda, record, pattern matching, keyed DI (Dependency Injection) gibi dil ve framework özellikleriyle aynı sonuca daha az törenle varmak.
  • Ne zaman kullanma: pattern'in zarar verdiği yerler ve "hiç pattern kullanmamak" seçeneği.

Bu yapı, Çözüm Desenleri serisinin Pattern Template'iyle akraba ama ondan farklı. O seri GoF'ta olmayan, veritabanından kullanıcı arayüzüne kadar birden fazla katmanı kesen uygulama problemlerini (soft delete, audit trail, numaralandırma) anlatıyordu. Bu seri ise kodun içindeki yapıya, yani class'ların ve object'lerin nasıl kurulduğuna, birleştiğine ve konuştuğuna bakıyor. C# OOP (Object-Oriented Programming) serisindeki "C#'ta Birkaç Design Pattern: Factory, Strategy ve Observer" yazısı bu serinin küçük bir ön izlemesiydi; burada aynı pattern'lere farklı örneklerle ve çok daha derin bakacağız.

Sık Düşülen Tuzaklar

Pattern'i çözüm diye değil hedef diye kullanmak. "Bu module'e bir factory ekleyelim" cümlesi bir problem tarif etmiyorsa tasarım değil dekorasyondur. GoF'ta her pattern'in bir motivation bölümü vardır; o bölümdeki senaryo senin kodunda yoksa pattern'e de gerek yoktur.

Pattern'i adından tanımaya çalışmak. Sayımın gösterdiği gibi Handler, Provider ya da Manager ile biten bir class'ın hangi pattern olduğunu adı söylemez. Tersine, adında hiçbir pattern geçmeyen bir IComparer<T> parametresi saf bir Strategy'dir.

Kitaptaki diyagramı birebir kopyalamak. GoF'un örnekleri C++ ve Smalltalk ile yazıldı; o dillerde delegate, lambda, generic ya da yield yoktu. C#'ta aynı pattern'i yazarken önce dilin onu zaten verip vermediğine bak. Abstract base class, iki interface ve dört alt class ile yazılmış bir Strategy, tek bir Func<T, TResult> parametresiyle aynı işi görüyorsa fazlalık okuyana yük olur.

Pattern'in consequences bölümünü atlamak. Her pattern bir şeyi ucuzlatırken başka bir şeyi pahalılaştırır. Decorator birleştirmeyi kolaylaştırır ama hata ayıklarken çağrı zincirini uzatır; Abstract Factory aile değiştirmeyi kolaylaştırır ama aileye yeni ürün eklemeyi zorlaştırır. Bir pattern'i önerirken bedelini de söyleyemiyorsan henüz tam anlamamışsındır.

Anti-pattern'i yalnızca kötü kod sanmak. Singleton gibi bazı pattern'ler doğru yerde kullanıldığında makul, yaygın kullanıldığında ise anti-pattern'e yakın. Serinin son bölümünde anti-pattern'leri ayrıca ele alacağız; bir sonraki yazıdaki Singleton bunun ilk örneği olacak.

Küçük Bir Deneme

★☆☆ İlk örnekteki Shelf.InStock'a ikinci bir yield return içeren bir method ekle (ör. fiyatı 10'un altındaki kitaplar). Yeni method'un döndürdüğü tipin adı ne? Derleyicinin bu adları nasıl numaralandırdığını gözle.

★★☆ Command örneğine geri al (undo) desteği ekle: ChangePrice çalışmadan önceki fiyatı saklasın ve bir Undo method'u olsun. Aynı şeyi Queue<Action> biçimiyle yapmayı dene; hangi noktada lambda'nın yetmediğini not et.

★★★ Sayım programını Microsoft.AspNetCore.App shared framework'ünü de kapsayacak şekilde genişlet ve Middleware, Builder ve Factory ile biten tipleri listele. Çıkan tiplerden üçünü seç ve her biri için GoF'taki hangi rolü üstlendiğini (ya da hiçbirini üstlenmediğini) bir cümleyle yaz.

Bir sonraki yazıda creational pattern'lere en tartışmalı olanından başlıyoruz: Singleton. Double-checked locking'in neden yıllarca yanlış yazıldığını, static readonly field'ların ne zaman initialize edildiğini belirleyen beforefieldinit ayrıntısını ve Lazy<T>'nin exception'ları neden sakladığını göreceğiz; sonunda da aynı garantiyi DI container'ın singleton lifetime'ı ile nasıl daha temiz aldığımızı.