Effective Java Madde 34: int Değişmezleri Yerine Enum Kullanın

Bir yılın içindeki mevsimler, haftanın günleri, güneş sistemindeki gezegenler gibi önceden belli bir takım değerler taşıyan türlere numaralandırılmış tür (enumerated type) denir. Enum türleri Java’ya eklenmeden önce numaralandırılmış türleri ifade edebilmek için aşağıdaki gibi gruplanmış int değişmezleri (constant) kullanılıyordu:

// int değişmezleri kullanımı - ciddi olarak kusurlu!
public static final int APPLE_FUJI         = 0;
public static final int APPLE_PIPPIN       = 1;
public static final int APPLE_GRANNY_SMITH = 2;

public static final int ORANGE_NAVEL  = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD  = 2;

Yukarıdaki gibi int değişmezlerinin gruplandığı bir kullanımın çok sayıda eksikliği vardır. Tür güvenliği sağlama noktasında hiçbir katkısı yoktur. Elma bekleyen bir metoda portakal geçtiğiniz zaman derleyici şikayet etmeyecektir ve == işlecini kullanarak elmayla portakalı karşılaştırabilirsiniz. Hatta bunları matematiksel işlemlere sokabilirsiniz:

// turunçgil aromalı elma püresi
int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN;

Dikkat ederseniz değişmezleri gruplarken elma grubu için APPLE_ portakal grubu içinse ORANGE_ öneklerini (prefix) kullandık. Bunun amacı farklı değişmez grupları için isim çakışmalarını engellemektir: ELEMENT_MERCURY ve PLANET_MERCURY gibi.

Bu şekilde int değişmezlerini kullanan programlar çok kırılgan olur. Bu değişmezlerin int değerleri derleme anında istemcilere gömülür. Eğer daha sonradan bu değerlerde bir değişiklik olursa istemcilerin yeniden derlenmesi gerekir. Derlenmezlerse istemciler yine de çalışır ama davranışları hatalı olur.

Bu değişmezleri yazdırabilecek bir biçime sokmak da kolay değildir. Yazdırmayı denediğinizde veya debug ederken göreceğiniz tek şey bir int değeri olacaktır ve bu da pek faydalı değildir. Bir grupta yer alan değişmezlerin toplam sayısını bulmak veya bunları taramak (iterate) için güvenli bir yöntem yoktur.

Bunun bir alternatifi olarak, int değişmezleri yerine String kullanılan örnekler görebilirsiniz ancak bu da benzer problemlere yol açacağından dolayı önerilmez.

Java dili bizlere bütün bu problemlerden arındırılmış bir seçenek olarak enum türlerini sunmaktadır. Enum türleri en basit haliyle aşağıdaki gibi yazılabilir. Burada Apple ve Orange türlerini temsil etmek için iki farklı enum tanımlanmıştır. Bunların içindeki FUJI, NAVEL, TEMPLE gibi alanlar enum sabiti olarak adlandırılır ve bu sabitlere Apple.FUJI, Orange.NAVEL, Orange.BLOOD şeklinde erişilir.

public enum Apple  { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }

Görüntü olarak enumlar C, C++ gibi dillerdeki karşılıklarına benzeseler de çok daha güçlü ve esnektirler.

Enumlar mantık olarak aslında çok basittir. Tanımlı her enum sabitini public static final bir alan olarak dışa açan sınıflar olarak düşünülebilirler. Enum türleri kalıtılamazlar ve dışarıdan erişilebilir yapıcı metotları olmadığından başka kod parçaları tarafından yaratılamazlar. Enum türü içerisinde tanımlı sabitler haricinde nesnelerinin olamayacağı garanti edilir. Enumlar için Madde 3’de anlatılan, özünde tek elemanlı enum olan singletonların genelleştirilmiş halidir de diyebiliriz.

Enumlar derleme anında tür güvenliği sağlarlar. Eğer bir metot Apple türünde bir parametre bekliyorsa, siz bu metoda Apple tanımında belirlenmiş 3 enum sabitinden birini veya null geçebilirsiniz. Orange gibi başka bir türden nesne referansı geçmeye çalışırsanız derleyici izin vermeyecektir. Bir enum türünü başka bir enum türüne atamaya çalışırsanız veya == işleci ile eşitlik kontrolü yapmaya çalışırsanız da yine derleyici izin vermez.

Farklı enum türleri içinde aynı isimli enum sabitleri tanımlayabilirsiniz çünkü her enum türü kendi isim uzayına (namespace) sahiptir. İstemcilere dokunmadan yeni enum sabitleri ekleyebilir veya bunların yerlerini değiştirebilirsiniz. Son olarak enum türlerinin toString metodunu çağırarak yazdırılabilir bir String elde edebilirsiniz.

Enum türleri özünde sınıf oldukları için içlerine alan (field) ve metotlar ekleyebilir hatta arayüzler uygulayabiliriz. Enumlar Object sınıfından gelen bütün metotları kaliteli bir biçimde geçersiz kılarlar ve Comparable ile Serializable arayüzlerini uygularlar. Bütün bunlar biz fazladan tek satır yazmadan derleyici tarafından yapılmaktadır.

Peki bir enum türüne neden metot eklemek isteyebiliriz? Bu genelde enum sabitiyle ona ait başka verileri ilişkilendirmek için yapılır. Bizim Apple ve Orange türlerini düşünürsek, örneğin enum sabitinin temsil ettiği meyvenin rengini veya görüntüsünü döndüren bir metot ekleyebiliriz. Bir enum türü peş peşe yazılmış enum sabitleri olarak hayatına başlayıp, sonradan yeni metotlar ve alanlar eklenerek geliştirilebilir.

Biraz daha gelişmiş bir enum türüne örnek olsun diye güneş sistemimizdeki 8 gezegeni ele alalım. Her gezegenin bir kütlesi ve çapı vardır, bunlardan faydalanarak her gezegen için yerçekimini hesaplayabiliriz. Bu da bize dünyadaki ağırlığını bildiğimiz bir cismin farklı gezegenlerdeki ağırlığını hesaplama imkanı verir. Şimdi bunlar bir enum türüyle nasıl ifade edilir ona bakalım. Her enum sabitinden sonra parantez içinde yazılı değerler enum türünün yapıcı metoduna geçilmektedir. Burada bu değerler gezegenin kütlesi ve çapıdır:

// ek veri ve davranış içeren enum türü 
public enum Planet {
    MERCURY(3.302e+23, 2.439e6),
    VENUS  (4.869e+24, 6.052e6),
    EARTH  (5.975e+24, 6.378e6),
    MARS   (6.419e+23, 3.393e6),
    JUPITER(1.899e+27, 7.149e7),
    SATURN (5.685e+26, 6.027e7),
    URANUS (8.683e+25, 2.556e7),
    NEPTUNE(1.024e+26, 2.477e7);

    private final double mass;      // kilogram
    private final double radius;    // metre
    private final double surfaceGravity;  //  m / s^2

    // evrensel yerçekimi sabiti in m^3 / kg s^2
    private static final double G = 6.67300E-11;
    
    // yapıcı metot
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
        surfaceGravity = G * mass / (radius * radius);
    }
    
    public double mass()           { return mass; }
    public double radius()         { return radius; }
    public double surfaceGravity() { return surfaceGravity; }
    public double surfaceWeight(double mass) {
        return mass * surfaceGravity;  // F = ma
    } 
}

Bu şekilde ek alanlar ve metotlarla zenginleştirilmiş enumlar yazmak çok zor değildir. Enum sabitlerini başka verilerle ilişkilendirmek için nesne alanları (instance field) tanımlayabilir ve yapıcı metot yardımıyla bunlara atama yapabilirsiniz. Enumlar doğası gereği değişmez (immutable) oldukları için bütün alanların final olması tavsiye edilir. (Madde 17) Alanları public tanımlamaya izin vardır ama private yapıp erişim metodu ile dışa açmak daha doğru olur. (Madde 16) Planet enumunda, yapıcı metot yerçekimini de hesaplayıp saklamaktadır ancak bu bir optimizasyondur. Bunun yerine surfaceWeight metodu çağrıldığında hesaplamak da mümkündür.

Planet enum türü basit olmasına karşın çok güçlüdür. Aşağıda, cisimlerin dünyadaki ağırlığını alıp bütün gezegenlerdeki ağırlığını hesaplayarak yazdıran bir istemci program görüyoruz:

public class WeightTable {
    public static void main(String[] args) {
        double earthWeight = Double.parseDouble(args[0]);
        double mass = earthWeight / Planet.EARTH.surfaceGravity(); 
        for (Planet p : Planet.values()) {
            System.out.printf("Weight on %s is %f%n",
                               p, p.surfaceWeight(mass));
        }
    } 
}

Şimdi bu program üzerinde biraz konuşalım. Planet bütün enumlar gibi statik values metoduna sahiptir. Bu metot enum içindeki tanımlı bütün sabitleri tanımlandıkları sırada bir dizi içerisinde döndürür ve böylece for döngüsünde bütün sabitleri tek tek tarayabiliriz. Enum türleri için toString metodu enum sabitinin adını döndürecek şekilde otomatik olarak eklenir ancak bunu değiştirmek için toString metodunu geçersiz kılabilirsiniz. Enumlar toString metoduna sahip oldukları için println veya printf gibi metotlarla kullanılabilirler. Yukarıda yazdığımız WeightTable programının çıktısı aşağıdaki gibi olacaktır:

Weight on MERCURY is 69.912739 
Weight on VENUS is 167.434436 
Weight on EARTH is 185.000000 
Weight on MARS is 70.226739 
Weight on JUPITER is 467.990696 
Weight on SATURN is 197.120111 
Weight on URANUS is 167.398264 
Weight on NEPTUNE is 210.208751

Bildiğiniz gibi bir zamanlar Pluto da gezegen zannediliyordu. Peki biz bu enum türünü o zamanlarda yazmış olsak ve Pluto için de Planet.PLUTO diye bir enum sabiti tanımlamış olsaydık, sonradan bunu silmemiz problem yaratır mıydı? Silinen enum sabitine direk olarak erişmeyen programlarda bir sorun çıkmazdı. Örneğin bizim WeightTable programımızda var olan enum türlerini taradığımız için Planet.PLUTO sabitini silseydik program 9 yerine 8 gezegen için çıktı üretecekti. Ancak silinen enum sabitine direk erişen programların tekrar derlenmesi gerekir. Derleme sonucunda hata mesajı açık bir biçimde artık var olmayan enum sabitini işaret eder. İstemci derlenmezse bu sefer çalışma zamanında benzer bir hata alınır. Bu da zaten bekleyebileceğimiz en makul sonuçtur.

Enum sabitleriyle ilişkilendirmek istediğiniz davranışların bir kısmına dışarıdan erişim gerekmeyebilir. Böyle metotların private veya package-private yazılması en doğrusu olur. Aynen sınıflarda olduğu gibi bir enum metodunu dışarı açmak için mantıklı bir sebep yoksa private veya bunu yapamıyorsanız package-private tanımlayın. (Madde 15)

Eğer bir enum türü birçok yerde faydalı olacaksa kendi başına tanımlanmalıdır. Ama kullanımı tek bir sınıfla sınırlı kalıyorsa üye sınıf (member class) olabilir. (Madde 24) Örneğin, java.math.RoundingMode enum türü küsuratlı sayılar için yuvarlamanın nasıl yapılacağını temsil eder. Bu yuvarlama modları BigDecimal sınıfı tarafından kullanılır ancak başka yerlerde de faydalı olabileceği için RoundingMode.java dosyasında üst seviye bir tür olarak tanımlanmıştır. Bu şekilde RoundingMode enum türünün yeniden kullanılabilir olması sağlanmıştır.

Planet örneğinde gördüğümüz kullanımlar birçok enum türünü tanımlayabilmek için fazlasıyla yeterlidir ancak bazen daha fazlası da gerekebilir. Her bir Planet enum sabiti için farklı mass ve radius gibi ilişkilendirilmiş alanlar vardır ancak bazen de bu enum sabitlerine farklı davranışlar yüklemek isteyebilirsiniz. Farzedelim ki dört işlem yapabilen bir hesap makinesinin işlevlerini tanımlamak için bir enum türü oluşturmak istiyoruz ve enum sabitlerinin bu işlevleri nasıl yapması gerektiğini bir şekilde kodlamak istiyoruz. Bunu yapmanın bir yolu aşağıdaki gibi switch deyimi kullanmaktır:

// kendisi (this) üzerinde switch uygulayan enum türü 
public enum Operation {
    PLUS, MINUS, TIMES, DIVIDE;
    // bu enum sabitinin temsil ettiği matematiksel işlemi uygula
    public double apply(double x, double y) {
        switch(this) {
            case PLUS:   return x + y;
            case MINUS:  return x - y;
            case TIMES:  return x * y;
            case DIVIDE: return x / y;
        }
        throw new AssertionError("Unknown op: " + this);
    }
}

Bu kod çalışır ama açıkçası pek de güzel durmuyor. Her ne kadar biz bir aykırı durumla karşılaşmayacağımızı bilsek de, AssertionError fırlattığımız 12. satırı yazmazsak derleme hatası alırız çünkü bu satır teknik olarak erişilebilirdir. İşin kötüsü, yeni bir enum sabiti eklediğimizde bunu switch bloğuna eklemeyi unutursak yeni işlemi uygulamaya çalıştığımızda kod çalışma zamanında çökecektir.

Neyse ki farklı enum sabitleri için farklı davranışlar tanımlayabilmemizi sağlayan daha iyi bir yol var. Enum türü içinde soyut (abstract) bir apply metodu tanımlayıp her bir sabit için bunu geçersiz kılabiliriz.

//Enum sabitlerine özel metot gerçekleştirimleri 
public enum Operation {
    PLUS {
        public double apply(double x, double y) { 
            return x+y;
        }
    }, 
    MINUS {
        public double apply(double x, double y) {
            return x - y;
        }
    }, 
    TIMES {
        public double apply(double x, double y) {
            return x * y;
        }
    }, 
    DIVIDE {
        public double apply(double x, double y) {
            return x / y;
        }
    };
    
    public abstract double apply(double x, double y);
}

Burada gördüğünüz gibi her enum sabiti için bir gövde içerisinde soyut apply metot olması gerektiği gibi gerçekleştirilmiştir. Yeni bir enum sabiti ekleme durumunda bu sabitin davranışını da tanımlamak zorundasınız çünkü her enum sabiti apply metodunu geçersiz kılmak zorundadır. Aksi taktirde kod derlenmeyecektir.

Bu şekilde enum sabitlerine davranış eklerken aynı zamanda veri de tutabiliriz. Örneğin, aşağıdaki gibi her enum sabitine bir sembol atayıp toString metodunu geçersiz kılarak bu sembolleri döndürebiliriz.

// Her enum sabiti için farklı davranış ve alan kullanan enum türü
public enum Operation {
    PLUS("+") {
        public double apply(double x, double y) { 
            return x + y; 
        } 
    },
    MINUS("-") {
        public double apply(double x, double y) { 
            return x - y; 
        }
    },
    TIMES("*") {
        public double apply(double x, double y) { 
            return x * y; 
        } 
    },
    DIVIDE("/") {
        public double apply(double x, double y) { 
            return x / y; 
        }
    };
    
    private final String symbol;
    Operation(String symbol) { 
        this.symbol = symbol; 
    }
    
    @Override 
    public String toString() { 
        return symbol; 
    }
    
    public abstract double apply(double x, double y);
}

Bu toString gerçekleştirimi sayesinde aritmetik işlemleri aşağıdaki gibi basit bir programla yazdırabiliriz:

public static void main(String[] args) {
    double x = Double.parseDouble(args[0]);
    double y = Double.parseDouble(args[1]);
    for (Operation op : Operation.values())
        System.out.printf("%f %s %f = %f%n",
                          x, op, y, op.apply(x, y));
}

Bu programı 2 ve 4 argümanlarını komut satırından geçerek çalıştırırsak aşağıdaki gibi bir sonuç üretir:

2.000000 + 4.000000 = 6.000000 
2.000000 - 4.000000 = -2.000000 
2.000000 * 4.000000 = 8.000000 
2.000000 / 4.000000 = 0.500000

Enum türleri otomatik üretilen bir valueOf(String) metoduna sahiptir. Bu metot enum sabitinin adını geçtiğiniz zaman size sabitin kendisini döndürür. Eğer toString metodunu geçersiz kılarsanız bunun yanında bir de fromString yazmayı düşünün ki istemciler enum sabitinin kendisi ile toString ile belirlenmiş String formu arasında geçiş yapabilsinler. Operation türü için toString metodu enum sabitini temsil eden sembolü döndürecek şekilde yazıldığı için, fromString metodunun da verilen bir sembol değerine karşılık gelen enum sabitini döndürmesi beklenir. Aşağıdaki kod bu amaçla Operation enum türü için yazılmıştır ama başka enumlar için de kullanılabilir (her enum sabitinin kendine özgü – unique – bir String karşılığı olduğu sürece):

// Enum türleri için fromString gerçekleştirimi 
private static final Map<String, Operation> stringToEnum =
        Stream.of(values()).collect(toMap(Object::toString, e -> e));

// symbol değerine göre varsa Operation döndürür 
public static Optional<Operation> fromString(String symbol) {
    return Optional.ofNullable(stringToEnum.get(symbol));
}

Burada dikkat edilecek birkaç husus var. Öncelikle Operation enum sabitleri statik bir Map olan stringToEnum içerisine bu alan tanımlanırken ekleniyor. Bu ilklendirme (initialization) işlemi enum sabitleri yaratıldıktan sonra yapılır, dolayısıyla values() çağrısı bize bütün enum sabitlerini içeren bir dizi döndürecektir. Bu kod daha sonra dizi üzerinden bir stream yaratmakta ve enum sabitlerini temsil edildikleri sembollere denk gelecek şekilde stringToEnum içine Map girdileri (entry) olarak eklemektedir. Streamler kitabın 7. bölümünde ayrıca ele alınmaktadır.

Java 8’den önce bunu yapabilmek için önce boş bir HashMap yaratıp daha sonra values dizisinin üzerinde tarama yaparak bütün elemanları tek tek eklemeniz gerekirdi. Aklınıza yapıcı metot işletilirken her enum sabitini stringToEnum içerisine kaydetmek gibi bir çözüm gelebilir ancak bu çalışmayacaktır çünkü enum sabitlerinin yapıcı metotlarından statik alanlara erişimi bazı istisnalar haricinde yasaktır. Bunun sebebi daha önce de bahsedildiği gibi enum sabitlerinin statik alanlardan daha önce yaratılmasıdır.

fromString metodunun Optional<String> döndürdüğünü de gözden kaçırmayın. Bu sayede enum karşılığı olmayan bir sembol fromString metoduna verildiğinde dönüş türü bunu ifade edebilecektir ve istemci de bu durumu ele almak için zorlanmış olacaktır. (Madde 55)

Operation gerçekleştiriminde uyguladığımız yöntemin bir dezavantajı enum sabitleri arasında kod paylaşımı yapmayı zorlaştırmasıdır. Örneğin, bir maaş ödeme sistemi içerisinde haftanın günlerini temsil eden bir enum türümüz olsun. Bu enum her gün için çalışana ödenmesi gereken maaş miktarını hesaplarken o gün toplamda kaç dakika çalıştığına bakmaktadır. Hafta içi günlerde mesai saatleri haricinde çalışılan dakikalar için ek mesai ücreti ödenmekte, haftasonları içinse çalışılan her dakika için ek mesai ödenmektedir. switch deyimi kullanırsak bunu aşağıdaki gibi kodlamak aslında kolaydır:

// switch deyimi ile kod paylaşımı sağlayan enum - tartışmaya açık
enum PayrollDay {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
    SATURDAY, SUNDAY;

    private static final int MINS_PER_SHIFT = 8 * 60;
    int pay(int minutesWorked, int payRate) {
        int basePay = minutesWorked * payRate;
        int overtimePay;
        switch(this) {
            case SATURDAY: 
            case SUNDAY: // haftasonu
                overtimePay = basePay / 2;
                break;
            default: // haftaiçi
                overtimePay = minutesWorked <= MINS_PER_SHIFT ?
                  0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;
        }
        return basePay + overtimePay;
    }   
}

Bu kod kısa olmasına rağmen bakımı zor olduğundan ötürü tehlikelidir. Enum türüne tatil günlerini temsil etmek için yeni bir değer eklediğimizi düşünelim. switch bloğuna bu yeni değer için gerekli kodu eklemeyi unutursak kod yine de çalışacaktır ama tatil günlerinde hesaplanan ödeme miktarı hafta içi ile aynı olacaktır.

Eğer switch deyimi kullanmayıp Operation türünde yaptığımız gibi her enum sabiti için metot tanımlarsak da bu sefer aynı kodu farklı günler için kopyalamamız gerekir. Bundan kurtulmak için hafta içi ve hafta sonu günlerin ödemesini hesaplayan iki tane yardımcı metot yazıp enum sabitindeki geçersiz kılınmış metotlardan bunları çağırabiliriz ancak bu durumda bile bir sürü gereksiz kod yazmak zorunda kalırız. Bu da hem kodun okunabilirliğini azaltır hem de hata yapmayı kolaylaştırır.

Kodumuzu her enum sabiti için bir ek mesai ödeme stratejisi seçmek zorunda olacak şekilde tasarlarsak buradaki problemlerden kurtulabiliriz. Bunu yapmak için kullanabileceğimiz bir yöntem var. Ek mesai ücreti hesaplaması yapan kısımlar hafta içi ve hafta sonu günler için farklılık gösterdiğinden dolayı bunları bir gömülü enum (private nested enum) içerisine taşıyabiliriz. PayrollDay sabitlerini yaratırken de bu gömülü enumda tanımlı ek mesai hesaplama stratejilerinden birini seçebiliriz. Bunu daha iyi anlamak için koda bakalım. Her ne kadar daha karmaşık ve uzun görünse de aslında daha güvenli ve esnektir:

// Strateji enum deseni
enum PayrollDay {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
    SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);

    private final PayType payType;
    PayrollDay(PayType payType) { 
        this.payType = payType; 
    }
    
    PayrollDay() { 
        this(PayType.WEEKDAY); // varsayılan/default
    }  
    
    int pay(int minutesWorked, int payRate) {
        return payType.pay(minutesWorked, payRate);
    }
    // strateji enum türü
    private enum PayType {
        WEEKDAY {
            int overtimePay(int minsWorked, int payRate) {
                return minsWorked <= MINS_PER_SHIFT ? 0 :
                     (minsWorked - MINS_PER_SHIFT) * payRate / 2;
            } 
        },
        WEEKEND {
            int overtimePay(int minsWorked, int payRate) {
                return minsWorked * payRate / 2;
            }
        };
        
        abstract int overtimePay(int mins, int payRate);
        private static final int MINS_PER_SHIFT = 8 * 60;
        int pay(int minsWorked, int payRate) {
            int basePay = minsWorked * payRate;
            return basePay + overtimePay(minsWorked, payRate);
        } 
    }
}

Burada farklı günler için değişkenlik gösteren iki farklı ek mesai (overtime) hesaplama algoritması PayType gömülü enumu içine kodlanmıştır. PayrollDay enum sabitleri yaratılırken de PayType.WEEKDAY veya PayType.WEEKEND stratejilerinden birini ek mesai hesaplama stratejisi olarak seçmektedirler. Bu kullanım mantık olarak Strateji Tasarım Desenine çok benzemektedir.

Peki switch deyimlerini enum sabitlerine davranış eklemek için kullanmak uygun değilse ne amaçla kullanabiliriz? Farzedelim ki Operation türü sizin kontrolünüzde bir enum değil ve siz keşke tanımlı her işlevin tersini döndüren bir metot daha olsaydı diye düşünüyorsunuz. (toplama için çıkarma döndürsün, çarpma için bölme döndürsün gibi) Bunu simüle etmek için switch deyimini aşağıdaki gibi kullanabilirsiniz:

// Enumda eksik olan metodu simule etmek için switch kullanımı
public static Operation inverse(Operation op) {
    switch(op) {
        case PLUS:   return Operation.MINUS;
        case MINUS:  return Operation.PLUS;
        case TIMES:  return Operation.DIVIDE;
        case DIVIDE: return Operation.TIMES;
        default: thrownewAssertionError("Unknown op:"+op); }
}

Enum türleri genel olarak int değişmezleri ile benzer performans sergilerler. Enum sabitlerinin yaratılması sırasında ufak bir zaman ve bellek kaybı olsa da pratikte bu fark anlaşılmaz.

Peki enumları ne zaman kullanmalıyız? Derleme anında değerlerini bildiğiniz bir grup değişmeze ne zaman ihtiyaç duyarsanız enum kullanın. Bunlar gezegenler, günler, satranç taşları gibi doğadan örnekler olabileceği gibi sizin uygulamanıza özgü olarak derleme anında değerlerini bildiğiniz bir menüdeki seçenekler, işlem kodları, komut satırı bayrakları (command line flags) gibi öğeleri de temsil edebilirler. Bir enum türünde tanımlı sabitlerin her zaman aynı kalması gibi bir şart yoktur. Enum türleri ekleme ve çıkarmalar yapılırken kullanıcılarını minimum düzeyde etkileyecek şekilde tasarlanmıştır.

Özetle, int değişmezlerine kıyasla enum türlerinin avantajları çok fazladır. Enumlar daha güvenli, okunabilir ve güçlüdür. Birçok enum için ek alanlar veya metotlar tanımlamanıza gerek kalmaz ama tanımladığınız sabitlerle ilişkilendirmek istediğiniz veriler veya davranışlar varsa bunu yapabilirsiniz. Her enum sabiti için farklı davranışlar belirlemeniz gereken durumlarda switch deyimi ile farklı davranışları kodlamak yerine enum içinde soyut bir metot tanımlayıp her enum sabiti için bu metodu geçersiz kılın. Eğer bazı enum sabitlerinin davranışları birbiriyle aynı ise strateji enum desenini uygulayın.

Share

Strateji Tasarım Deseni

Uzun zamandır yazmak istediğim konulardan birisi de tasarım desenleri. Strateji deseni çok sık kullanılan ve çok yararlı olduğunu düşündüğüm bir tasarım deseni olduğu için bununla başlamak istedim. Bu desenin kitaplardaki tanımını verip kafa karıştırmaktansa bir örnekle başlamak istiyorum. Bir şirketimiz olduğunu ve bu şirkette çalışan teknisyen, mühendis ve yönetici türünde elemanlarımız olduğunu varsayalım. Her bir eleman türü için de ayrı ayrı maaş hesaplama algoritmalarımız olsun. Bu durumu koda döktüğümüzde aşağıdaki gibi bir sonuç karşımıza çıkabilir.
Read more “Strateji Tasarım Deseni”

Share