Daha güvenli ve daha temiz kod için sabit türleri kullanın

Bu eğitimde, Eric Armstrong'un "Java'da numaralandırılmış sabitler yaratma" adlı kitabında anlatıldığı gibi , numaralandırılmış sabitler fikrini genişleteceğiz . Numaralandırılmış sabitlerle ilgili kavramlara aşina olduğunuzu varsayacağım ve Eric'in sunduğu örnek kodlardan bazılarını genişleteceğim için, bu makaleye kendinizi kaptırmadan önce bu makaleyi okumanızı şiddetle tavsiye ederim.

Sabit kavramı

Numaralandırılmış sabitlerle uğraşırken , makalenin sonunda kavramın numaralandırılmış kısmını tartışacağım . Şimdilik, sadece sabit yöne odaklanacağız . Sabitler temelde değeri değiştirilemeyen değişkenlerdir. C / C ++ 'da, anahtar kelime constbu sabit değişkenleri bildirmek için kullanılır. Java'da anahtar kelimeyi kullanırsınız final. Bununla birlikte, burada tanıtılan araç sadece ilkel bir değişken değildir; bu gerçek bir nesne örneğidir. Nesne örnekleri değişmez ve değiştirilemez - iç durumları değiştirilemez. Bu, bir sınıfın yalnızca tek bir örneğe sahip olabileceği tekli modele benzer; ancak bu durumda, bir sınıf yalnızca sınırlı ve önceden tanımlanmış bir örnek kümesine sahip olabilir.

Sabitlerin kullanılmasının ana nedenleri açıklık ve güvenliktir. Örneğin, aşağıdaki kod parçası kendinden açıklamalı değildir:

public void setColor (int x) {...} public void someMethod () {setColor (5); }

Bu koddan bir rengin ayarlandığından emin olabiliriz. Ama 5 hangi rengi temsil ediyor? Bu kod, çalışmaları hakkında yorum yapan ender programcılardan biri tarafından yazıldıysa, cevabı dosyanın en üstünde bulabiliriz. Ancak daha büyük olasılıkla bir açıklama için bazı eski tasarım belgelerini (eğer varsa) araştırmamız gerekecek.

Daha açık bir çözüm, anlamlı bir adı olan bir değişkene 5 değerini atamaktır. Örneğin:

public static final int RED = 5; public void someMethod () {setColor (RED); }

Artık kodla ilgili neler olduğunu hemen söyleyebiliriz. Renk kırmızıya ayarlanıyor. Bu çok daha temiz ama daha güvenli mi? Ya başka bir kodlayıcının kafası karışırsa ve böyle farklı değerler bildirirse:

public static final int RED = 3; public static final int GREEN = 5;

Şimdi iki sorunumuz var. Her şeyden önce, REDartık doğru değere ayarlanmıyor. İkinci olarak, kırmızı değeri, adlı değişkenle temsil edilir GREEN. Belki de en korkutucu kısım, bu kodun gayet iyi bir şekilde derlenmesidir ve hata, ürün sevk edilene kadar tespit edilemeyebilir.

Bu sorunu kesin bir renk sınıfı oluşturarak çözebiliriz:

public class Renk {public static final int RED = 5; public static final int YEŞİL = 7; }

Ardından, dokümantasyon ve kod incelemesi yoluyla, programcıları aşağıdaki gibi kullanmaya teşvik ediyoruz:

public void someMethod () {setColor (Color.RED); }

Teşvik diyorum çünkü bu kod listesindeki tasarım, kodlayıcıyı uymaya zorlamamıza izin vermiyor; her şey yolunda gitmese bile kod yine de derlenecektir. Dolayısıyla, bu biraz daha güvenli olsa da, tamamen güvenli değildir. Programcılar rağmen gerektiğini kullanmak Colorsınıfı, onlar için gerekli değildir. Programcılar aşağıdaki kodu çok kolay bir şekilde yazabilir ve derleyebilir:

 setColor (3498910); 

Does setColoryöntem renk olmasını sayıda tanıyor musunuz? Muhtemelen değil. Öyleyse kendimizi bu haydut programcılardan nasıl koruyabiliriz? Sabit türlerin kurtarmaya geldiği yer burasıdır.

Yöntemin imzasını yeniden tanımlayarak başlıyoruz:

 public void setColor (Renk x) {...} 

Artık programcılar rastgele bir tamsayı değeri geçiremezler. Geçerli bir Colornesne sağlamaya zorlanırlar . Bunun örnek bir uygulaması şöyle görünebilir:

public void someMethod () {setColor (yeni Color ("Red")); }

Hala temiz, okunabilir kodla çalışıyoruz ve mutlak güvenliğe çok daha yakınız. Ama henüz tam olarak orada değiliz. Programcının hala ortalığı kasıp kavuracak bir yeri var ve keyfi olarak böyle yeni renkler yaratabilir:

public void someMethod () {setColor (yeni Color ("Merhaba, benim adım Ted.")); }

ColorSınıfı değişmez hale getirerek ve somutlaştırmayı programcıdan gizleyerek bu durumu önleriz. Her bir farklı renk türünü (kırmızı, yeşil, mavi) tek ton yapıyoruz. Bu, yapıcıyı özel hale getirerek ve ardından genel tanıtıcıları sınırlı ve iyi tanımlanmış bir örnek listesine maruz bırakarak gerçekleştirilir:

genel sınıf Color {private Color () {} public static final Color RED = new Color (); genel statik son Renk YEŞİL = yeni Renk (); genel statik son Renk MAVİ = yeni Renk (); }

Bu kodda nihayet mutlak güvenliğe ulaştık. Programcı sahte renkler üretemez. Yalnızca tanımlanan renkler kullanılabilir; aksi takdirde program derlenmez. Şimdi uygulamamız şu şekilde görünüyor:

public void someMethod () {setColor (Color.RED); }

Kalıcılık

Tamam, şimdi sabit türlerle başa çıkmanın temiz ve güvenli bir yoluna sahibiz. Renk niteliğine sahip bir nesne oluşturabilir ve renk değerinin her zaman geçerli olacağından emin olabiliriz. Peki ya bu nesneyi bir veritabanında depolamak veya bir dosyaya yazmak istersek? Renk değerini nasıl kaydederiz? Bu türleri değerlerle eşlemeliyiz.

In JavaWorld Yukarıda belirtilen makalede, Eric Armstrong dize değerleri kullanılır. Dizeleri kullanmak, size toString()yöntemde geri dönmeniz için anlamlı bir şey verme ek avantajını sağlar , bu da hata ayıklama çıktısını çok açık hale getirir.

Dizeleri saklamak pahalı olabilir. Bir tamsayı, değerini saklamak için 32 bit gerektirirken, bir dize karakter başına 16 bit gerektirir (Unicode desteğinden dolayı). Örneğin, 49858712 sayısı 32 bit olarak saklanabilir, ancak dizi TURQUOISE144 bit gerektirir. Renk özniteliklerine sahip binlerce nesne depoluyorsanız, bu nispeten küçük bit farkı (bu durumda 32 ile 144 arasında) hızlı bir şekilde toplanabilir. Onun yerine tamsayı değerlerini kullanalım. Bu sorunun çözümü nedir? Dize değerlerini saklayacağız çünkü bunlar sunum için önemlidir, ancak onları saklamayacağız.

Java'nın 1.1'den sonraki sürümleri, Serializablearayüzü uyguladıkları sürece nesneleri otomatik olarak serileştirebilir . Java'nın gereksiz verileri depolamasını önlemek için, bu tür değişkenleri transientanahtar sözcükle bildirmelisiniz . Bu nedenle, tamsayı değerlerini dize gösterimini saklamadan saklamak için, dize özniteliğini geçici olarak beyan ederiz. İşte tamsayı ve dize özniteliklerine erişimcilerle birlikte yeni sınıf:

public class Color, java.io.Serializable {private int value; özel geçici Dize adı; genel statik son Renk KIRMIZI = yeni Renk (0, "Kırmızı"); genel statik son Renk MAVİ = yeni Renk (1, "Mavi"); genel statik son Renk YEŞİL = yeni Renk (2, "Yeşil"); özel Renk (int değer, Dize adı) {this.value = değer; this.name = isim; } public int getValue () {dönüş değeri; } public String toString () {dönüş adı; }}

Artık sabit tipteki örnekleri verimli bir şekilde depolayabiliriz Color. Peki ya onları geri yüklemek? Bu biraz zor olacak. Daha ileri gitmeden önce, bunu, bizim için yukarıda belirtilen tüm tuzakları ele alacak bir çerçeveye genişletelim ve türleri tanımlamanın basit meselesine odaklanmamıza izin verin.

Sabit tip çerçeve

Sabit türler konusundaki sağlam anlayışımızla, şimdi bu ayın aracına geçebilirim. Araç çağrılır Typeve basit bir soyut sınıftır. Yapmanız gereken tek şey çok basit bir alt sınıf oluşturmak ve tam özellikli bir sabit tip kitaplığınız var. ColorSınıfımız şimdi nasıl görünecek:

public class Renk, Tür {korumalı Renk (int değer, Dize azalan) {süper (değer, azalan); } public static final Color RED = new Color (0, "Red"); genel statik son Renk MAVİ = yeni Renk (1, "Mavi"); genel statik son Renk YEŞİL = yeni Renk (2, "Yeşil"); }

The Color class consists of nothing but a constructor and a few publicly accessible instances. All of the logic discussed to this point will be defined and implemented in the superclass Type; we'll be adding more as we go along. Here's what Type looks like so far:

public class Type implements java.io.Serializable { private int value; private transient String name; protected Type( int value, String name ) { this.value = value; this.name = name; } public int getValue() { return value; } public String toString() { return name; } } 

Back to persistence

With our new framework in hand, we can continue where we left off in the discussion of persistence. Remember, we can save our types by storing their integer values, but now we want to restore them. This is going to require a lookup -- a reverse calculation to locate the object instance based on its value. In order to perform a lookup, we need a way to enumerate all of the possible types.

In Eric's article, he implemented his own enumeration by implementing the constants as nodes in a linked list. I'm going to forego this complexity and use a simple hashtable instead. The key for the hash will be the integer values of the type (wrapped in an Integer object), and the value of the hash will be a reference to the type instance. For example, the GREEN instance of Color would be stored like so:

 hashtable.put( new Integer( GREEN.getValue() ), GREEN ); 

Of course, we don't want to type this out for each possible type. There could be hundreds of different values, thus creating a typing nightmare and opening the doors to some nasty problems -- you might forget to put one of the values in the hashtable and then not be able to look it up later, for instance. So we'll declare a global hashtable within Type and modify the constructor to store the mapping upon creation:

 private static final Hashtable types = new Hashtable(); protected Type( int value, String desc ) { this.value = value; this.desc = desc; types.put( new Integer( value ), this ); } 

But this creates a problem. If we have a subclass called Color, which has a type (that is, Green) with a value of 5, and then we create another subclass called Shade, which also has a type (that is Dark) with a value of 5, only one of them will be stored in the hashtable -- the last one to be instantiated.

In order to avoid this, we have to store a handle to the type based on not only its value, but also its class. Let's create a new method to store the type references. We'll use a hashtable of hashtables. The inner hashtable will be a mapping of values to types for each specific subclass (Color, Shade, and so on). The outer hashtable will be a mapping of subclasses to inner tables.

This routine will first attempt to acquire the inner table from the outer table. If it receives a null, the inner table doesn't exist yet. So, we create a new inner table and put it into the outer table. Next, we add the value/type mapping to the inner table and we're done. Here's the code:

 private void storeType( Type type ) { String className = type.getClass().getName(); Hashtable values; synchronized( types ) // avoid race condition for creating inner table { values = (Hashtable) types.get( className ); if( values == null ) { values = new Hashtable(); types.put( className, values ); } } values.put( new Integer( type.getValue() ), type ); } 

And here's the new version of the constructor:

 protected Type( int value, String desc ) { this.value = value; this.desc = desc; storeType( this ); } 

Now that we are storing a road map of types and values, we can perform lookups and thus restore an instance based on a value. The lookup requires two things: the target subclass identity and the integer value. Using this information, we can extract the inner table and find the handle to the matching type instance. Here's the code:

 public static Type getByValue( Class classRef, int value ) { Type type = null; String className = classRef.getName(); Hashtable values = (Hashtable) types.get( className ); if( values != null ) { type = (Type) values.get( new Integer( value ) ); } return( type ); } 

Thus, restoring a value is as simple as this (note that the return value must be casted):

 int value = // read from file, database, etc. Color background = (ColorType) Type.findByValue( ColorType.class, value ); 

Enumerating the types

Hashtable-of-hashtables organizasyonumuz sayesinde, Eric'in uygulaması tarafından sunulan numaralandırma işlevini ortaya çıkarmak inanılmaz derecede basittir. Tek uyarı, Eric'in tasarımının sunduğu bu sınıflandırmanın garanti edilmemesidir. Java 2 kullanıyorsanız, sıralı haritayı iç hashtable'ların yerine kullanabilirsiniz. Ancak, bu sütunun başında da belirttiğim gibi, şu anda yalnızca JDK'nın 1.1 sürümü ile ilgileniyorum.

Türleri numaralandırmak için gereken tek mantık, iç tabloyu almak ve eleman listesini döndürmektir. İç tablo yoksa, sadece boş döndürürüz. İşte tüm yöntem: