Numaralamalar kırılgan arayüzler yaratıyor mu?


17

Aşağıdaki örneği düşünün. ColorChoice enum öğesinde yapılan tüm değişiklikler tüm IWindowColor alt sınıflarını etkiler.

Numaralamalar kırılgan arayüzlere neden olma eğiliminde mi? Daha polimorfik esnekliğe izin vermek için bir numaradan daha iyi bir şey var mı?

enum class ColorChoice
{
    Blue = 0,
    Red = 1
};

class IWindowColor
{
public:
    ColorChoice getColor() const=0;
    void setColor( const ColorChoice value )=0;
};

Düzenleme: Örneğim olarak rengi kullandığım için üzgünüm, sorunun konusu bu değil. İşte kırmızı ringadan kaçınan ve esneklikle kastettiğim hakkında daha fazla bilgi sağlayan farklı bir örnek.

enum class CharacterType
{
    orc = 0,
    elf = 1
};

class ISomethingThatNeedsToKnowWhatTypeOfCharacter
{
public:
    CharacterType getCharacterType() const;
    void setCharacterType( const CharacterType value );
};

Ayrıca, uygun ISomethingThatNeedsToKnowWhatTypeOfCharacter alt sınıfındaki tanıtıcıların bir fabrika tasarım deseni tarafından verildiğini düşünün. Şimdi, izin verilen karakter türlerinin {human, cüce} olduğu farklı bir uygulama için gelecekte genişletilemeyen bir API var.

Edit: Sadece üzerinde çalıştığım şey hakkında daha somut olmak. Bu ( MusicXML ) belirtimine güçlü bir bağ tasarlıyorum ve xs: enumeration ile belirtilen belirtimdeki türleri temsil etmek için enum sınıflarını kullanıyorum. Bir sonraki sürüm (4.0) çıktığında ne olacağını düşünmeye çalışıyorum. Sınıf kütüphanem 3.0 modunda ve 4.0 modunda çalışabilir mi? Bir sonraki sürüm% 100 geriye dönük uyumluysa, belki de. Ancak numaralandırma değerleri spesifikasyondan çıkarıldıysa suda öldüm.


2
"Polimorfik esneklik" dediğinizde, tam olarak hangi yeteneklere sahipsiniz?
Ixrec


3
Renkler için bir numaralandırma kullanmak , yalnızca "bir numaralandırma kullanarak" değil, kırılgan arabirimler oluşturur.
Doc Brown

3
Yeni bir enum varyantı eklemek, bu numaralandırmayı kullanarak kodu sonlandırır. Diğer yandan, bir enum'a yeni bir işlem eklemek oldukça bağımsızdır, çünkü ele alınması gereken tüm durumlar oradadır (bunu, varsayılan olmayan bir yöntem eklemenin ciddi bir kırılma değişikliği olduğu arayüzler ve üst sınıflarla karşılaştırın). Gerçekten gerekli olan değişikliklerin türüne bağlıdır.

1
Re MusicXML: XML dosyalarından her birinin hangi şemanın sürümünü kullandığını söylemenin kolay bir yolu yoksa, bu şartnamede kritik bir tasarım hatası olarak bana çarpıyor. Bir şekilde geçici olarak çözümlemeniz gerekiyorsa, 4.0'da ne kırmayı seçeceklerini tam olarak öğrenene ve bunun neden olduğu belirli bir soruyu sorabilmemize yardımcı olmanın muhtemelen bir yolu yoktur.
Ixrec

Yanıtlar:


25

Düzgün kullanıldığında, numaralandırmalar değiştirdikleri "sihirli numaralardan" çok daha okunaklı ve sağlamdır. Normalde kodları daha kırılgan hale getirmediklerini görmüyorum. Örneğin:

  • setColor () valuegeçerli bir renk değeri olup olmadığını kontrol etmek için zaman kaybetmek zorunda değildir. Derleyici bunu zaten yaptı.
  • SetColor (0) yerine setColor (Color :: Red) yazabilirsiniz. enum classModern C ++ 'daki özelliğin, insanları her zaman birincisi yerine ilkini yazmaya zorlamanıza izin verdiğine inanıyorum .
  • Genellikle önemli değildir, ancak çoğu numaralandırma herhangi bir boyut integrali türüyle uygulanabilir, böylece derleyici sizi bu tür şeyler hakkında düşünmeye zorlamadan en uygun olanı seçebilir.

Bununla birlikte, renk için bir numaralandırma kullanmak tartışmalıdır çünkü birçok (en çok?) Durumda kullanıcıyı bu kadar küçük renklerle sınırlamak için bir neden yoktur; bunların rastgele RGB değerlerinden geçmesine izin verebilirsiniz. Çalıştığım projelerde, bunun gibi küçük bir renk listesi, sadece somut renkler üzerinde ince bir soyutlama görevi görmesi gereken bir dizi "temanın" veya "stilin" bir parçası olarak ortaya çıkacaktı.

"Polimorfik esneklik" sorunuzun ne anlama geldiğinden emin değilim. Numaralandırmaların yürütülebilir kodu yoktur, bu nedenle polimorfik yapmak için hiçbir şey yoktur. Belki de komut paternini mi arıyorsunuz?

Düzenleme: Post-edit, hala ne tür bir uzatılabilirlik arıyorsanız açık değilim, ama yine de komut desen bir "polimorfik enum" alacağınız en yakın şey olduğunu düşünüyorum.


0'ın enum olarak geçmesine izin vermeme hakkında daha fazla bilgiyi nerede bulabilirim?
TankorSmash

5
@TankorSmash C ++ 11, altta yatan sayısal türlerine örtük olarak dönüştürülemeyen "kapsamlandırılmış numaralandırmalar" olarak da adlandırılan "enum sınıfı" nı tanıttı. Ayrıca eski C tarzı "enum" türleri gibi ad alanını kirletmekten de kaçınırlar.
Matthew James Briggs

2
Numaralandırmalar genellikle tamsayılarla desteklenir. Tamsayılar ve numaralandırmalar arasında serileştirme / serileştirme veya dökümde olabilecek birçok garip şey var. Bir numaralandırmanın her zaman geçerli bir değere sahip olacağını varsaymak her zaman güvenli değildir.
Eric

1
Haklısın Eric (ve bu birkaç kez karşılaştığım bir sorun). Ancak, sadece serileştirme aşamasında geçersiz bir değer hakkında endişelenmeniz gerekir. Diğer numaralar kullandığınızda, değerin geçerli olduğunu varsayabilirsiniz (en azından Scala gibi dillerdeki numaralandırmalar için - bazı dillerde numaralandırmalar için çok güçlü bir tür denetimi yoktur).
Kat

1
@Ixrec "çünkü birçok (en çok?) Durumda kullanıcıyı bu kadar küçük renklerle sınırlamak için bir neden yoktur" Meşru durumlar vardır. Net Konsolu, yalnızca 16 renkten birinde (CGA standardının 16 renginden) metin içerebilen eski stil Windows konsoluna öykünür msdn.microsoft.com/en-us/library/…
Pharap

15

ColorChoice enum öğesinde yapılan tüm değişiklikler tüm IWindowColor alt sınıflarını etkiler.

Hayır değil. İki durum söz konusudur: uygulayıcılar

  • numaralandırma değerlerini saklamayın, döndürmeyin ve iletmeyin, asla üzerinde çalışmaz, bu durumda numaralandırmadaki değişikliklerden etkilenmezler veya

  • münferit enum değerleri üzerinde çalışır, bu durumda enumdaki herhangi bir değişiklik doğal olarak kaçınılmaz olarak zorunlu olarak uygulayıcının mantığındaki karşılık gelen bir değişiklikle açıklanmalıdır.

"Küre", "Dikdörtgen" ve "Piramit" i "Şekil" numarasına koyarsanız drawSolid()ve karşılık gelen katı çizmek için yazdığınız bir işleve böyle bir numaralandırma iletirseniz ve sonra bir sabah bir " Ikositetrahedron "değeri enum için, drawSolid()fonksiyonun etkilenmeden kalamazsınız ; Eğer bir şekilde ilk önce icositetrahedrons çizmek için gerçek kodu yazmak zorunda kalmadan icositetrahedrons çizmek için beklediyseniz, bu senin hatan değil, bu senin hatan. Yani:

Numaralamalar kırılgan arayüzlere neden olma eğiliminde mi?

Hayır. Gevrek arayüzlere neden olan şey, kendilerini yeterince ninjaları etkinleştirmeden kodlarını derlemeye çalışan, kendilerini ninja olarak düşünen programcılardır. Sonra derleyici, drawSolid()işlevlerinin yeni eklenen "Ikositetrahedron" numaralandırma değeri için switchbir casemadde eksik olan bir ifade içerdiği konusunda onları uyarmaz.

Çalışması gereken yol, bir temel sınıfa yeni bir saf sanal yöntem eklemeye benzer: daha sonra bu yöntemi her tek mirasçıya uygulamanız gerekir, aksi takdirde proje oluşturmaz ve yapmamalıdır.


Şimdi doğruyu söylemek gerekirse, sıralamalar nesne yönelimli bir yapı değildir. Bunlar daha çok nesne yönelimli paradigma ve yapılandırılmış programlama paradigması arasında pragmatik bir uzlaşmadır.

Bir şeyleri yapmanın saf nesneye yönelik yolu, hiç numaralandırma yapmak değil, onun yerine nesnelerin sonuna kadar sahip olmaktır.

Dolayısıyla, örneği katı maddeler ile uygulamanın tamamen nesneye yönelik yolu elbette polimorfizm kullanmaktır: her şeyi nasıl çizeceğini bilen ve hangi katıya çizileceğini söylemek zorunda kalan tek bir canavarca merkezileştirilmiş yöntem yerine, Soyut (saf sanal) bir draw()yöntemle katı "sınıf ve daha sonra" Sphere "," Rectangle "ve" Pyramid "alt sınıflarını eklersiniz, her biri kendi uygulamasına sahip draw()olup, nasıl çizileceğini bilir.

Bu şekilde, "Ikositetrahedron" alt sınıfını tanıttığınızda, bunun için sadece bir draw()işlev sağlamanız gerekecek ve derleyici bunu size "Icositetrahedron" örneğini başlatmanıza izin vermeyecek şekilde hatırlatacaktır.


Bu anahtarlar için derleme süresi uyarısı atmaya ilişkin herhangi bir ipucu var mı? Genellikle çalışma zamanı istisnaları atarım; ama derleme zamanı harika olabilir! Kadar büyük değil .. ama birim testleri akla geliyor.
Vaughan Hilts

1
C ++ 'ı en son kullandığımdan bu yana bir süre geçti, bu yüzden emin değilim, ancak derleyiciye -Wall parametresini sağlamayı ve default:yan tümcesini atlamayı beklemeliyim . Konuyla ilgili hızlı bir araştırma daha kesin sonuç vermedi, bu nedenle bu başka bir programmers SEsorunun konusu olarak uygun olabilir .
Mike Nakis

C ++ 'da, etkinleştirirseniz derleme zamanı denetimi olabilir. C # 'da herhangi bir kontrolün çalışma zamanında olması gerekir. Her zaman bir bayrak olmayan enum parametre olarak alırsanız, Enum.IsDefined ile doğruladığınızdan emin olun, ancak yine de yeni bir değer eklediğinizde enum'un tüm kullanımlarını manuel olarak güncellemeniz gerektiği anlamına gelir. Bakınız: stackoverflow.com/questions/12531132/…
Mike Monica

1
Ben gibi bir nesne yönelimli programcı, kendi veri aktarım nesnesi yapmak asla umut ediyorum Pyramidaslında nasıl bilmek draw()bir piramit. En iyi ihtimalle Solidbir GetTriangles()yöntemden türetilebilir ve bir yönteme sahip olabilir ve bunu bir SolidDrawerservise iletebilirsiniz . OOP'taki nesnelerin örnekleri olarak fiziksel nesnelerin örneklerinden uzaklaştığımızı sanıyordum.
Scott Whitlock

1
Tamam, sadece iyi eski nesne odaklı programlama dayak kimse gibi değildi. :) Özellikle çoğumuz artık fonksiyonel programlama ve OOP'yi kesinlikle OOP'tan daha fazla birleştirdiğimizden beri.
Scott Whitlock

14

Numaralandırmalar kırılgan arabirimler oluşturmaz. Numaralandırmaların kötüye kullanılması .

Numaralandırmalar ne için?

Numaralandırmalar, anlamlı olarak adlandırılmış sabitler kümesi olarak kullanılmak üzere tasarlanmıştır. Aşağıdaki durumlarda kullanılmalıdırlar:

  • Hiçbir değerin kaldırılmayacağını biliyorsunuz.
  • (Ve) Yeni bir değere ihtiyaç duyulmasının pek olası olmadığını biliyorsunuz.
  • (Veya) Yeni bir değere ihtiyaç duyulacağını kabul edersiniz, ancak nadiren bu nedenle kırılan tüm kodların düzeltilmesini garanti etmek için yeterlidir.

Numaralandırmaların iyi kullanımları:

  • Haftanın günleri: (.Net'lere göre System.DayOfWeek) İnanılmaz derecede belirsiz bazı takvimlerle uğraşmazsanız, haftanın sadece 7 günü olacaktır.
  • Genişletilemeyen renkler: (.Net'e göre System.ConsoleColor) Bazıları buna katılmayabilir, ancak .Net bunu bir nedenden dolayı yapmayı seçti. Net'in konsol sisteminde, konsol tarafından kullanılabilecek yalnızca 16 renk vardır. Bu 16 renk, CGA veya 'Renkli Grafik Bağdaştırıcısı' olarak bilinen eski renk paletine karşılık gelir . Hiçbir yeni değer eklenmeyecektir, bu aslında bunun bir enumun makul bir uygulaması olduğu anlamına gelir.
  • Sabit durumları temsil eden numaralandırmalar: (Java'ya göre Thread.State) Java tasarımcıları, Java iş parçacığı modelinde, yalnızca a'nın içinde olabileceği sabit bir durum kümesi olacağına karar verdiler Threadve bu nedenle konuları basitleştirmek için bu farklı durumlar numaralandırma olarak temsil edildi . Bu, programcı değerlerin gerçekte ne olduğu konusunda endişelenmek zorunda kalmadan pratikte tamsayı değerler üzerinde çalışan birçok durum tabanlı kontrolün basit ifs ve anahtarların olduğu anlamına gelir.
  • Karşılıklı olarak münhasır olmayan seçenekleri temsil eden bitflags: (.Net'e göre System.Text.RegularExpressions.RegexOptions) Bitflags, enumların çok yaygın bir kullanımıdır. Aslında, .NET'te tüm numaralandırmaların HasFlag(Enum flag)yerleşik bir yöntemi vardır. Bitsel operatörleri de desteklerler ve FlagsAttributebir enum'u bir bitflag seti olarak kullanılmak üzere tasarlanmış olarak işaretlerler. Bir enum'u bir bayrak kümesi olarak kullanarak, kolaylık sağlamak için bayrakların açıkça adlandırılmasını sağlamanın yanı sıra, bir grup boole değerini tek bir değerde temsil edebilirsiniz. Bu, bir öykünücüdeki durum kaydının bayraklarını temsil etmek veya bir dosyanın izinlerini (okumak, yazmak, yürütmek) veya ilgili bir dizi seçeneğin birbirini dışlamadığı herhangi bir durumu temsil etmek için son derece yararlı olacaktır.

Numaralandırmaların kötü kullanımları:

  • Oyundaki karakter sınıfları / türleri: Oyun tekrar kullanmanız mümkün olmayan tek seferlik bir demo değilse, enumlar karakter sınıfları için kullanılmamalıdır çünkü şansınız çok daha fazla sınıf eklemek isteyecektir. Karakteri temsil eden tek bir sınıfa sahip olmak ve aksi takdirde temsil edilen bir oyun içi 'tip' karakterine sahip olmak daha iyidir. Bunu işlemenin bir yolu TypeObject kalıbıdır, diğer çözümler arasında karakter türlerini sözlük / tür kayıt defterine kaydetme veya ikisinin bir karışımı bulunur.
  • Genişletilebilir renkler: Daha sonra eklenebilecek renkler için bir enum kullanıyorsanız, bunu enum formunda temsil etmek iyi bir fikir değildir, aksi takdirde sonsuza kadar renk eklenirsiniz. Bu, yukarıdaki soruna benzer, bu nedenle benzer bir çözüm kullanılmalıdır (yani, TypeObject'in bir varyasyonu).
  • Genişletilebilir durum: Çok daha fazla durum getirebilecek bir durum makineniz varsa, bu durumları numaralandırmalarla temsil etmek iyi bir fikir değildir. Tercih edilen yöntem, makinenin durumu için bir arabirim tanımlamak, arabirimi saran ve yöntem çağrılarını ( Strateji modeline benzer şekilde ) devreten bir uygulama veya sınıf sağlamak ve ardından sağlanan uygulamanın şu anda etkin olan durumunu değiştirerek durumunu değiştirmektir.
  • Birbirini dışlayan seçenekleri temsil eden bitflags: Bayrakları temsil etmek için numaralandırmalar kullanıyorsanız ve bu bayraklardan ikisi asla bir arada olmamalıdır, o zaman kendinizi ayağınıza vurdunuz. Bayraklardan birine tepki vermek üzere programlanan her şey, ilk olarak yanıt vermek üzere programlanmış hangi bayrağa aniden tepki verir - ya da daha kötüsü, her ikisine de yanıt verebilir. Bu tür bir durum sadece sorun istiyor. En iyi yaklaşım, bir bayrağın yokluğunu mümkünse alternatif koşul olarak ele almaktır (yani Truebayrağın yokluğu anlamına gelir False). Bu davranış özel işlevler (yani IsTrue(flags)ve IsFalse(flags)) kullanılarak daha da desteklenebilir .

Herkes bitflags olarak kullanılan enums çalışan veya iyi bilinen bir örnek bulabilirsiniz bitflag örnekleri ekleyeceğiz. Var olduklarının farkındayım ama maalesef şu an hatırlayamıyorum.
Pharap


@BLSully Mükemmel örnek. Onları hiç kullandığımı söyleyemem, ama bir nedenden dolayı varlar.
Pharap

4

Numaralamalar, kendileriyle ilişkili çok fazla işlevselliğe sahip olmayan kapalı değer kümeleri için sihirli kimlik numaralarına göre büyük bir gelişmedir. Genellikle hangi sayının enum ile ilişkili olduğunu umursamazsınız; bu durumda, sonunda yeni girişler ekleyerek genişletmek kolaydır, kırılganlığa neden olmamalıdır.

Sorun, numaralandırma ile ilişkili önemli işlevsellik olduğundadır. Yani, etrafta bu tür bir kod var:

switch (my_enum) {
case orc: growl(); break;
case elf: sing(); break;
...
}

Bunlardan biri veya ikisi tamam, ancak bu tür anlamlı numaralara sahip olduğunuzda, bu switchifadeler kabileler gibi çoğalma eğilimindedir. Şimdi numaralandırmayı her genişlettiğinizde, switchher şeyi kapsadığınızdan emin olmak için tüm ilişkili es'leri avlamanız gerekir. Bu kırılgandır. Temiz Kod gibi kaynaklarswitch , numaralandırma başına en fazla bir tane olması gerektiğini önerecektir .

Bu durumda yapmanız gereken şey OO prensiplerini kullanmak ve bu tip için bir arayüz oluşturmaktır . Bu tür iletişim için enum hala tutabilirsiniz, ancak onunla bir şey yapmanız gerektiğinde, muhtemelen bir Fabrika kullanarak enum ile ilişkili bir nesne oluşturursunuz. Güncellemek için sadece bir yer aramanız gerektiğinden, bu işlemin bakımı çok daha kolaydır: Fabrikanız ve yeni bir sınıf ekleyerek.


1

Bunları nasıl kullandığınız konusunda dikkatli olursanız, enumları zararlı olarak düşünmem. Ancak, bunları tek bir uygulamanın aksine, bazı kütüphane kodlarında kullanmak istiyorsanız dikkate almanız gereken birkaç nokta vardır.

  1. Asla değerleri kaldırmayın veya yeniden sıralamayın. Bir noktada listenizde bir enum değeri varsa, bu değer tüm sonsuzluk için bu adla ilişkilendirilmelidir. İsterseniz, bir noktada değerleri deprecated_orcveya herhangi bir şeyi yeniden adlandırabilirsiniz , ancak bunları kaldırmayarak eski numaralandırma kümesine karşı derlenen eski kodla uyumluluğu daha kolay bir şekilde koruyabilirsiniz. Bazı yeni kodlar eski enum sabitleriyle başa çıkamıyorsa, orada uygun bir hata oluşturun veya bu değerin bu kod parçasına ulaşmadığından emin olun.

  2. Onlara aritmetik yapmayın. Özellikle, sipariş karşılaştırmaları yapmayın. Neden? Çünkü o zaman mevcut değerleri tutamayacağınız ve aynı zamanda aklı başında bir sırayı koruyamayacağınız bir durumla karşılaşabilirsiniz. Örneğin, pusula yönleri için bir numaralandırma alın: N = 0, NE = 1, E = 2, SE = 3,… Şimdi bazı güncellemelerin ardından mevcut yönler arasında NNE vb. Eklerseniz, bunları sonuna ekleyebilirsiniz. ve böylece mevcut anahtarlar ile serpiştirin ve böylece eski kodda kullanılan yerleşik eşlemeleri kırın. Ya da tüm eski anahtarları kullanımdan kaldırırsınız ve eski kod uğruna eski ve yeni anahtarlar arasında çeviri yapan bazı uyumluluk kodlarıyla birlikte tamamen yeni bir anahtar kümesine sahip olursunuz.

  3. Yeterli bir boyut seçin. Varsayılan olarak derleyici, tüm numaralandırma değerlerini içerebilecek en küçük tamsayıyı kullanır. Bu, bazı güncellemelerde olası numaralandırma kümeniz 254'ten 259'a genişlerse, aniden her numaralandırma değeri için bir yerine 2 bayta ihtiyacınız olduğu anlamına gelir. Bu, her yerde yapı ve sınıf düzenlerini kırabilir, bu nedenle ilk tasarımda yeterli bir boyut kullanarak bundan kaçınmaya çalışın. C ++ 11 burada size çok fazla kontrol sağlar, ancak aksi takdirde bir giriş belirtmek LAST_SPECIES_VALUE=65535de yardımcı olacaktır.

  4. Merkezi bir sicile sahip olun. Adlar ve değerler arasındaki eşlemeyi düzeltmek istediğiniz için, kodunuzun üçüncü taraf kullanıcılarının yeni sabitler eklemesine izin vermek kötüdür. Kodunuzu kullanan hiçbir projenin yeni eşlemeler eklemek için bu başlığı değiştirmesine izin verilmemelidir. Bunun yerine onları eklemeniz için sizi rahatsız etmelidirler. Bu, bahsettiğiniz insan ve cüce örnek için, numaralandırmaların gerçekten zayıf bir uyum olduğu anlamına gelir. Kodunuzun kullanıcılarının bir dize ekleyebileceği ve bazı opak türlerde güzelce sarılmış benzersiz bir sayı geri alabileceği çalışma zamanında bir tür kayıt defteri olması daha iyi olurdu. “Sayı” söz konusu dizenin bir işaretçisi bile olabilir, önemli değil.

Yukarıdaki son noktamın enumları varsayımsal durumunuz için zayıf bir uyum haline getirmesine rağmen, bazı spesifikasyonların değişebileceği ve bazı kodları güncellemeniz gereken gerçek durumunuz, kütüphaneniz için merkezi bir kayıt defterine oldukça iyi uyuyor gibi görünüyor. Eğer diğer önerilerimi kalpten alırsanız, enums orada uygun olmalıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.