Statik üyeler dışında fayda sınıfları C ++ 'da anti-patern midir?


39

Bir sınıfa bağlı olmayan işlevleri nereye koymalıyım sorusu , C ++ 'da bir faydası işlevlerini bir sınıfta birleştirmenin bir anlam ifade edip etmediği ya da sadece bir isim alanında boş işlevler olarak var olmalarına ilişkin bazı tartışmalara yol açtı.

İkinci seçeneğin bulunmadığı bir C # arkaplanından geliyorum ve bu yüzden yazdığım küçük C ++ kodunda statik sınıfları kullanmaya yöneliyorum. Ancak en yüksek oyu bu soruya verilen yanıtın yanı sıra birkaç yorumun yanı sıra, statik sınıfların bir kalıp karşıtı olduğunu öne sürerek, serbest işlevlerin tercih edilmesi gerektiğini söylüyor. Neden böyle C ++ 'da? En azından yüzeyde, bir sınıftaki statik yöntemler, bir ad alanındaki boş işlevlerden ayırt edilemez görünüyor. Neden bu ikinci tercih?

Yardımcı program işlevlerinin toplanması bazı paylaşılan verilere ihtiyaç duyuyorsa, örneğin özel bir statik alanda depolanabilecek bir önbellek, işler farklı mı olurdu?


"Fonksiyonel ayrışma" antipattern gibi biraz geliyor.
user281377

7
Kısa cevap: Bu işlevleri kaydırmak için bir sınıfa ihtiyacınız yoktur. Özgür işlevler, yalnızca "tamamen OO" dillerinde ihtiyacınız olan geçici bir çözüm olan bazı sözde-OO yapılarına sıkıştırmaktan çok sizin görevinize daha uygun.
Chris diyor ki, Monica

Yanıtlar:


39

Sanırım hem sınıfların hem de ad alanlarının niyetlerini karşılaştırmalıyız. Wikipedia'ya göre:

Sınıf

Nesne yönelimli programlamada, sınıf kendi örneklerini oluşturmak için bir plan olarak kullanılan - sınıf örnekleri, sınıf nesneleri, örnek nesneler veya basitçe nesneler olarak adlandırılan bir yapıdır. Bir sınıf, bu sınıf örneklerinin durum ve davranışa sahip olmalarını sağlayan kurucu üyeleri tanımlar. Veri alanı üyeleri (üye değişkenleri veya örnek değişkenleri) bir sınıf nesnesinin durumunu korumasını sağlar. Diğer üyeler, özellikle de yöntemler, bir sınıf nesnesinin davranışını mümkün kılar. Sınıf örnekleri, ilişkili sınıfın türündendir.

Ad alanı

Genel olarak, bir ad alanı, elinde bulunan tanımlayıcılara (adlar veya teknik terimler veya kelimeler) bağlam sağlayan ve farklı ad alanlarında bulunan homonym tanımlayıcılarının netleştirilmesine izin veren bir kaptır.

Şimdi, işlevleri bir sınıfa (statik olarak) veya bir ad boşluğuna koyarak neyi başarmaya çalışıyorsunuz? Bir isim alanının tanımının niyetinizi daha iyi tanımladığına bahse girerim - tek istediğiniz şey işlevleriniz için bir kapsayıcıdır. Sınıf tanımında açıklanan özelliklerin hiçbirine ihtiyacınız yok. Sınıf tanımının ilk kelimelerinin " Nesneye yönelik programlamada " olduğuna dikkat edin, ancak bir işlevler topluluğu hakkında nesneyi hedefleyen bir şey olmadığını unutmayın.

Muhtemelen teknik sebepler de var ama Java'dan gelen ve kafamı C ++ olan çoklu paradigma dili etrafında dolaşmaya çalışan biri olarak, benim için en açık cevap şudur: Çünkü bunu başarmak için OO'ya ihtiyacımız yok.


1
"Bunu başarmak için
OO'ya

Buna OO'nun hayranı olmadığına katılıyorum, ancak All-static bir sınıf kullanmanın tek bir çözüm olduğu, örneğin bir ad alanını değiştirmenin, bunun içinde iç içe geçmiş bir ad alanı tanımlayamadığınız için bazı birkaç durum olduğunu tahmin ediyorum. Bir sınıf.
Wael Boutglay

26

Buna anti-kalıp denirken çok dikkatli olurum. Ad alanları genellikle tercih edilir, ancak ad alanı şablonu olmadığından ve ad alanları şablon parametreleri olarak geçirilemez, statik üyeler dışında hiçbir şey içermeyen sınıflar kullanmak oldukça yaygındır.


3
Diğer cevaplar OP'nin son satırına değindi. Ad alanları hemen hemen kapsam olarak adlandırılır, bu nedenle erişim denetimine ihtiyacınız varsa, sınıflar kullanılabilen tek araçtır.
cmannett85

2
@ cbamber85 adil olmak gerekirse , ilk iki cevabı okuduktan sonra bu satırı koydum .
PersonalNexus

@PersonalNexus Ah yeterince adil, farkında değildim!
cmannett85

10
@ cbamber85: Bu tamamen doğru değil. Uygulama dosyasına "özel" işlevler koyarak daha iyi bir kapsülleme elde edersiniz, böylece kullanıcılar özel bildirimi bile göremezler.
AmcaBens

2
+1 Şablonlar aslında ad alanlarının hala özelliklerinin olmadığı bir noktadır.
Chris diyor ki, Monica

13

O zamanlar bir FORTRAN dersi almam gerekiyordu. O zamana kadar diğer zorunlu dilleri biliyordum, bu yüzden fazla çalışmadan FORTRAN yapabileceğimi düşündüm. İlk ödevime döndüğümde, profesör bana geri verdi ve tekrar yapmak istedi: FORTRAN sözdiziminde yazılan Pascal programlarının geçerli bildirimler olarak sayılmadığını söyledi.

Burada da benzer bir sorun var: C ++ 'da yardımcı işlevler barındırmak için statik sınıflar kullanmak, C ++' a yabancı bir deyimdir.

Sorunuzda bahsettiğiniz gibi, C # 'da yardımcı işlevler için statik sınıflar kullanmak bir zorunluluktur: serbest duran işlevler sadece C #' da bir seçenek değildir. Programcıların bağımsız işlevlerini başka bir yolla (statik hizmet sınıfları içinde) tanımlamasına izin vermek için bir kalıp geliştirmek için gereken dil. Bu kelime için alınan bir Java hilesiydi: örneğin, java.lang.Math ve .NET'in System.Math'ı neredeyse izomorfiktir.

Bununla birlikte, C ++ aynı amaca ulaşmak için farklı bir tesis olan ad alanları sunar ve standart kütüphanesinin uygulanmasında aktif olarak kullanır. Fazladan bir statik sınıf katmanı eklemek sadece gereksiz olmakla kalmaz, aynı zamanda C # veya Java arka planı olmayan okuyucular için de biraz sezgiseldir. Bir anlamda, yerel olarak ifade edilebilecek bir şey için dile bir "kredi çevirisi" başlatıyorsunuz.

İşlevlerinizin veri paylaşması gerektiğinde, durum farklıdır. İşlevleriniz artık alakasız olmadığı için , Singleton modeli bu gereksinimi ele almanın tercih edilen yolu olur.


6
+, Tekilleri önerme dışında. Onlar edilir değil C ++ tercih! İşlevler, durumu paylaşmaları gerekiyorsa bir sınıfta olabilir, ancak sınıflar gerçekten, gerçekten olmadıkça tekil olmamalıdır. Ve genellikle yapmazlar.
Saat

@Maxpm Beni yanlış anlamayın, singleton yalnızca global statik değişkenlere tercih edilir. Bundan başka, bunun hakkında "tercih edilen" bir şey yok.
dasblinkenlight

2
İşte bu güzel makale, Singletons: 1995'ten beri hiç bilmediğiniz problemleri çözme . Özetle, iyi bilinen vakayı sınıfa kendisiyle eşleştirmenin gereksiz yere esnekliğinizi sınırladığını ve size hiçbir şey vermeyeceğini söylüyor. Çoğu zaman sadece bir sınıfa ve bir yerlerde ortak bir örneğe sahip olmakla birlikte, bunu tekilleştirme.
Jan Hudec

"Yabancı deyim" in doğru terim olduğundan emin değilim. Çok yaygın bir deyimdir. Bence birkaç programlama ekibi Stroustrup'un e2'sini kullanıyor ...
Nick Keighley

10

Belki de neden statik bir sınıf istediğinizi sormanız gerekir?

Düşünebilmemin tek nedeni, 'her şey bir sınıf' olan diğer dillerin (Java ve C #) onları gerektirmesidir. Bu diller hiçbir zaman üst düzey işlevler yaratamazlar, bu yüzden onları tutabilecekleri bir numara yaptılar ve bu statik üye işleviydi. Biraz geçici bir çözüm olabilir, ancak C ++ böyle bir şeye ihtiyaç duymaz, doğrudan en üst düzeyde yeni, bağımsız işlevler oluşturabilirsiniz.

Belirli bir veri öğesinde çalışan işlevlere ihtiyacınız varsa, bunları verileri tutan bir sınıfa paketlemek mantıklı olur, ancak sonra bunlar işlev olmayı bırakır ve sınıf verisinde çalışan o sınıfın üyeleri olmaya başlar.

Belirli bir veri türünde çalışmayan bir işleve sahipseniz (buradaki kelimeyi sınıflar olarak yeni veri türlerini tanımlamak için kullanıyorum), o zaman gerçekten başka bir sınıf için onları bir sınıfa sokmak için bir kalıp değildir. anlamsal amaçlar.


10
Gerçekten de - Java’daki "tüm statik üyeli sınıf" ın aslında Java’nın sınırlarını aşmak için bir kesmek olduğunu söyleyebilirim .
Charles Salvia,

@CharlesSalvia: Nazik davranıyordum :) Bunu neden yaptıklarını anlasam da, main () 'in de bir java sınıfının parçası olma konusunda aynı kötü hislerim var.
gbjbaanb

Bu aslında neden tamamen statik bir sınıf isteyeceğinize cevap veriyor gibi görünüyor. Yoksa seni yanlış mı anladım? Örneğin, değeri bir sınıf tarafından okunan (ROM'da depolamak istediğim) ve başka bir sınıf tarafından yazılan (RAM'de olacak) bir değişken tutmam gerekiyor. Basitçe bilinen bir ram konumuna yerleştirmek yeterli değildir - onu (ve erişimcilerini) bir sınıfa sarmak, erişim kontrolüne ince ayar yapmama izin verir. İkinci paragrafında anlattıklarını hemen hemen.
iheanyi

5

En azından yüzeyde, bir sınıftaki statik yöntemler, bir ad alanındaki boş işlevlerden ayırt edilemez görünüyor.

Başka bir deyişle, ad alanı yerine bir sınıfa sahip olmanın hiçbir avantajı yoktur.

Neden bu ikinci tercih?

Birincisi static, her zaman yazmaya devam etmenizi sağlar , ancak bu oldukça küçük bir faydadır.

Asıl yararı, bu iş için en az güçlü araç olmasıdır. Sınıflar, nesneler oluşturmak için kullanılabilir, değişkenlerin türü veya şablon argümanları olarak kullanılabilir. Bunların hiçbiri, işlev koleksiyonunuz için istediğiniz özelliklerdir. Bu nedenle, bu özelliklere sahip olmayan bir araç kullanılması tercih edilir, böylece sınıf yanlışlıkla yanlış kullanılamaz.

Bundan sonra, bir ad boşluğu kullanmak, kodunuzun herhangi bir kullanıcısına, bunun bir işlev koleksiyonu olduğunu ve nesneler oluşturmak için bir plan olmadığını açıkça belirtir.


5

... birkaç yorum, ancak statik işlevlerin bir kalıp karşıtı olduğunu öne sürerek, serbest işlevlerin tercih edileceğini söylüyor. Neden böyle C ++ 'da? En azından yüzeyde, bir sınıftaki statik yöntemler, bir ad alanındaki boş işlevlerden ayırt edilemez görünüyor. Neden bu ikinci tercih?

Tamamen statik bir sınıf işi halledecek, ancak dört kapılı bir sedanın yapacağı cips ve salsa için markete 53 metrelik yarı bir kamyon sürmek gibi bir şey. Sınıflar ek bir miktar ek yük ile birlikte gelir ve varlıkları birine birisinin somutlaştırılmasının iyi bir fikir olabileceği izlenimini verebilir. C ++ tarafından sunulan ücretsiz işlevler (ve tüm işlevlerin ücretsiz olduğu C) bunu yapmaz; onlar sadece fonksiyonlar ve başka hiçbir şey.

Yardımcı program işlevlerinin toplanması bazı paylaşılan verilere ihtiyaç duyuyorsa, örneğin özel bir statik alanda depolanabilecek bir önbellek, işler farklı mı olurdu?

Pek sayılmaz. staticC'nin (ve daha sonra C ++ 'ın) asıl amacı , herhangi bir kapsam düzeyinde kullanılabilen kalıcı depolama sunmaktı:

int counter() {
    static int value = 0;
    value++;
}

Kapsamı bir dosyanın seviyesine çıkarabilir ve bu dosyayı daha dar tüm kapsamlarda görülebilir ancak dosyanın dışında göremezsiniz:

static int value = 0;

int up() { value++; }
int down() { value--; }

C ++ 'daki özel bir statik sınıf üyesi aynı amaca hizmet eder, ancak bir sınıfın kapsamı ile sınırlıdır. counter()Yukarıdaki örnekte kullanılan teknik, C ++ yöntemlerinde de çalışır ve değişkenin tüm sınıfa görünür olması gerekmiyorsa, aslında yapmamı önerdiğim bir şeydir.


Hiçbir veriyi paylaşmayan bir alakasız yardımcı program işlevinin bir ad alanında daha iyi gruplandırılması gerektiğini varsayalım. Ancak, paylaşılan verilere ve belki de bazı giriş kontrollerine erişmesi gereken bir grup sıkı bağlı yardımcı program işleviniz varsa, statik bir sınıf en iyi seçimdir. Bir fonksiyon içindeki bir statik cansız değildir. Global bir modül kullanarak ... bu bağlamda biraz daha iyi, ancak tanımladığım gereksinimlere sahipseniz, hala tüm statik bir sınıfın en iyi çözüm olduğunu düşünüyorum.
iheanyi

Verileri paylaşıyorlarsa, statik yöntemleri olmayan bir sınıf olarak daha iyi olabilirler mi?
Nick Keighley

@NickKeighley Bu, bir örnek oluşturmanın ve onu ihtiyaç duyan her şeye aktarmanın veya sınıfta statik hale getirmenin daha iyi olacağı konusunda tartışmayı kimin kazandığına bağlı.
Blrfl

1

Eğer bir işlev hiçbir devleti sürdürmezse ve tekrarlayıcı ise, onu bir sınıfın içine sokmanın pek bir anlamı yoktur (dilin zorlamadığı sürece). Eğer fonksiyon bazı durumları koruyorsa (örn. Sadece statik bir muteks vasıtasıyla iş parçacığı güvenli hale getirilebilir), o zaman bir sınıftaki statik yöntemler uygun görünüyor.


1

Yapar C ++ hakkında çok temel bir şey var olsa çok konu üzerine söylem burada, mantıklı namespacesve classes / structçok farklı.

Statik sınıflar (tüm üyelerin statik olduğu ve sınıfın hiçbir zaman başlatılmayacağı sınıflar) nesnelerdir. Bunlar sadece namespaceişlevler içermesi değildir .

Şablon meta-programlama, derleme zamanı nesnesi olarak statik bir sınıf kullanmamızı sağlar.

Bunu düşün:

template<typename allocator_type> class allocator
{
public:
    inline static void* allocate(size_t size)
    {
        return allocator_type::template allocate(size);
    }
    inline static void release(void* p)
    {
        allocator_type::template release(p);
    }
};

Bunu kullanmak için bir sınıf içinde yer alan fonksiyonlara ihtiyacımız var. Bir ad alanı burada çalışmayacak. Düşünmek:

class mallocator
{
    inline static void* allocate(size_t size)
    {
        return std::malloc(size);
    }
    inline static void release(void* p)
    {
        return std::free(p);
    }
};

Şimdi kullanmak için:

using my_allocator = allocator<mallocator>;

void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);

Yeni bir tahsisatçı ortaya çıkardığı sürece allocateve releaseuyumlu bir fonksiyon tanımladığında, yeni bir tahsisatçıya geçmek kolaydır.

Bu, ad alanları ile yapılamaz.

Her zaman bir sınıfın parçası olmak için işlevlere ihtiyacınız var mı? Hayır.

Statik bir sınıf anti-patern kullanıyor mu? Bu koşullara bağlıdır.

Yardımcı program işlevlerinin toplanması bazı paylaşılan verilere ihtiyaç duyuyorsa, örneğin özel bir statik alanda depolanabilecek bir önbellek, işler farklı mı olurdu?

Bu durumda, elde etmeye çalıştığınız şeyin en iyi şekilde nesne yönelimli programlama yoluyla sunulması muhtemeldir.


Sınıf içi bir tanımda tanımlanan yöntemler dolaylı olarak satır içi olduğundan, inline anahtar sözcüğünün kullanımı burada gereksizdir. Bkz en.cppreference.com/w/cpp/language/inline .
Trevor

1

John Carmack'in ünlü bir şekilde söylediği gibi:

"Bazen, şık uygulama sadece bir işlevdir. Bir yöntem değil. Sınıf değil. Bir çerçeve değil. Sadece bir işlev."

Bence bu hemen hemen özetliyor. Açıkça bir sınıf değilse, neden zorla bir sınıf haline getirdiniz? C ++, gerçekten işlevleri kullanabilme lüksüne sahiptir; Java'da her şey bir yöntemdir. O zaman yardımcı programlara ve çok fazlasına ihtiyacınız var.

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.