C programları için OO en iyi uygulamaları [kapalı]


19

"Eğer gerçekten OO şeker istiyorsan - git C ++ kullan" - bunu sorduğumda bir arkadaşımdan aldığım acil yanıt oldu. Burada iki şeyin yanlış olduğunu biliyorum. Birincisi OO 'şeker' DEĞİL, ikincisi C ++ C'yi EMMEZ.

Biz C (Python olacak ön uç) bir sunucu yazmak gerekir, bu yüzden büyük C programları yönetmek için daha iyi yollar araştırıyorum.

Büyük bir sistemi nesneler ve nesne etkileşimleri açısından modellemek, sistemi daha yönetilebilir, bakımı ve genişletilebilir hale getirir. Ancak bu modeli, nesneleri (ve diğer her şeyi) taşımayan C'ye çevirmeye çalıştığınızda, bazı önemli kararlarla karşı karşıya kalırsınız.

Sisteminizin ihtiyaç duyduğu OO soyutlamalarını sağlamak için özel bir kitaplık oluşturuyor musunuz? Nesneler, kapsülleme, kalıtım, polimorfizm, istisnalar, pub / sub (olaylar / sinyaller), ad alanları, içgözlem vb. (Örneğin GObject veya COS ) gibi şeyler .

Veya structtüm nesne sınıflarınızı (ve diğer soyutlamalarınızı) geçici yollarla yaklaşık olarak belirlemek için temel C yapılarını ( ve işlevlerini) kullanırsınız. (örneğin, SO'da bu sorunun bazı cevapları )

İlk yaklaşım, tüm modelinizi C'de uygulamak için yapılandırılmış bir yol sağlar. Ancak aynı zamanda sürdürmeniz gereken bir karmaşıklık katmanı ekler. (Unutmayın, karmaşıklık ilk etapta nesneleri kullanarak azaltmak istedik.)

İkinci yaklaşımı ve ihtiyacınız olabilecek tüm soyutlamaları tahmin etmenin ne kadar etkili olduğunu bilmiyorum.

Yani, basit sorularım: C'de bir nesne yönelimli tasarımın gerçekleştirilmesinde en iyi uygulamalar nelerdir. Bu ve bu sorular bunun hakkında konuşuyor ve bu konuda bir kitap bile var . Daha fazla ilgilendiğim şey, dong olduğunda ortaya çıkan gerçek sorunları ele alan bazı gerçekçi tavsiyeler / örnekler.

Not: Lütfen C'nin neden C ++ lehine kullanılmaması gerektiğini önermeyin. O aşamayı geçtik.


3
Dış arabirim olması extern "C"ve python'dan kullanılabilmesi için C ++ sunucusu yazabilirsiniz . Manuel olarak yapabilir veya SWIG'nin size bu konuda yardımcı olmasını sağlayabilirsiniz. Python frontend için arzu C ++ kullanmamak için bir neden değildir. Bu, C ile kalmak istemek için geçerli bir neden olmadığını söylüyor
Jan Hudec

1
Bu sorunun açıklığa kavuşturulması gerekiyor. Şu anda, 4. ve 5. paragraflar temel olarak hangi yaklaşımı benimsemesi gerektiğini sormaktadır, ancak daha sonra "bunu NASIL yapmasını istemediğinizi" ve bunun yerine en iyi uygulamaları (bir liste?) İstediğinizi söylersiniz. C'de bunu nasıl yapacağınızı arıyorsanız, genel olarak OOP ile ilgili bir "en iyi uygulamalar" listesi mi istiyorsunuz? Öyleyse bunu söyleyin, ancak sorunun öznel olması nedeniyle muhtemelen kapatılacağını unutmayın .
Caleb

:) Gerçek örnekler (kod veya başka türlü) nerede yapıldığı - ve bunu yaparken karşılaştıkları sorunları soruyorum.
treecoder

4
Gereksinimleriniz kafa karıştırıcı görünüyor. Nesne yönünü göremediğim için kullanmama konusunda ısrar ediyorsunuz (bazı dillerde programları daha sürdürülebilir hale getirmenin bir yoludur, ancak C'de değil) ve C'yi kullanmakta ısrar ediyorsunuz. Nesne yönlendirme bir amaç, bir son veya her derde deva değil . Dahası, dil desteğinden büyük ölçüde faydalanmaktadır. Aslında OO istiyorsanız, dil seçimi sırasında bunu düşünmelisiniz. C ile büyük bir yazılım sisteminin nasıl oluşturulacağı sorusu çok daha mantıklı olacaktır.
David Thornley

"Nesneye Dayalı Modelleme ve Tasarım" a göz atmak isteyebilirsiniz. (Rumbaugh ve ark.): OO tasarımlarının C. gibi dillerle eşleştirilmesiyle ilgili bölüm vardır
Giorgio

Yanıtlar:


16

Cevabımdan C'deki karmaşık projeleri nasıl yapılandırmalıyım (OO değil, C'deki karmaşıklığı yönetme hakkında):

Anahtar modülerlik. Bunu tasarlamak, uygulamak, derlemek ve bakımını yapmak daha kolaydır.

  • Bir OO uygulamasındaki sınıflar gibi uygulamanızdaki modülleri tanımlayın.
  • Her modül için ayrı arayüz ve uygulama, sadece diğer modüller için ihtiyaç duyulanları arayüze koyun. C'de bir ad alanı olmadığını unutmayın, bu nedenle arayüzlerinizdeki her şeyi benzersiz hale getirmelisiniz (örneğin, bir önekle).
  • Uygulamada genel değişkenleri gizleyin ve okuma / yazma için erişimci işlevlerini kullanın.
  • Miras açısından değil, kompozisyon açısından düşünün. Genel bir kural olarak, C ++ 'da C'yi taklit etmeye çalışmayın, bu okumak ve korumak çok zor olacaktır.

Benim cevabımdan OO C genel ve özel işlevleri için tipik adlandırma kuralları nelerdir (Bence bu en iyi uygulamadır):

Kullandığım sözleşme:

  • Genel işlev (başlık dosyasında):

    struct Classname;
    Classname_functionname(struct Classname * me, other args...);
  • Özel işlev (uygulama dosyasında statik)

    static functionname(struct Classname * me, other args...)

Ayrıca, birçok UML aracı, UML diyagramlarından C kodu üretebilir. Açık kaynaklı olanı Topcased'dir .



1
Mükemmel cevap. Modülerliği hedefleyin. OO'nun bunu sağlaması gerekiyordu, ancak 1) pratikte OO spagetti ile sonuçlanmak için çok yaygın ve 2) tek yol bu değil. Gerçek hayattaki bazı örnekler için linux çekirdeğine (C-tarzı modülerlik) ve glib (C-tarzı OO) kullanan projelere bakın. Her iki stil ile de çalışma fırsatım oldu ve IMO C tarzı modülerlik kazanıyor.
Joh

Ve neden kompozisyon tam olarak kalıtımdan daha iyi bir yaklaşımdır? Gerekçe ve destekleyici referanslar kabul edilir. Yoksa sadece C programlarından mı bahsediyordunuz?
Aleksandr Blekh

1
@AleksandrBlekh - Evet sadece C'den bahsediyorum.
mouviciel

16

Bu tartışmada OO ve C ++ 'ı ayırt etmeniz gerektiğini düşünüyorum.

C'de nesneleri uygulamak mümkündür ve oldukça kolaydır - sadece işlev işaretçileri olan yapılar oluşturun. Bu senin "ikinci yaklaşımın", ve ben de onunla giderdim. Başka bir seçenek, yapıda işlev işaretçileri kullanmak değil, daha ziyade veri yapısını doğrudan çağrılardaki işlevlere bir "bağlam" işaretçisi olarak aktarmak olacaktır. Daha iyi, daha okunabilir, daha kolay izlenebilir olduğu ve yapıyı değiştirmeden işlev eklemeye izin verdiği için (daha fazla veri eklenmezse mirasta kolaylık) IMHO daha iyidir. Aslında C ++ genellikle thisişaretçiyi bu şekilde uygular .

Polimorfizm daha karmaşık hale gelir, çünkü yerleşik bir miras ve soyutlama desteği yoktur, bu nedenle ya alt sınıfınızı çocuk sınıfınıza dahil etmeniz ya da çok sayıda kopya macunu yapmanız gerekir, teknik olarak da olsa bu seçeneklerden herhangi biri açıkçası korkunçtur basit. Muazzam miktarda hata olmasını bekliyor.

Sanal işlevler, gerektiğinde farklı işlevlere işaret eden işlev işaretçileriyle kolayca elde edilebilir - manuel olarak yapıldığında çok hata eğilimli, bu işaretçileri doğru şekilde başlatma konusunda çok sıkıcı bir çalışma.

İsim alanları, istisnalar, şablonlar vb. İle ilgili olarak - C ile sınırlıysanız, bunlardan vazgeçmeniz gerektiğini düşünüyorum. C'de OO yazma üzerine çalıştım, eğer bir seçeneğim olsaydı yapmazdım (iş yerinde tam anlamıyla C ++ ile tanıştığım şekilde savaştım ve yöneticiler onu entegre etmenin ne kadar kolay olduğuna "şaşırdılar" sonunda C modüllerinin geri kalanı ile.).

C ++ kullanabiliyorsanız, C ++ kullanın. Yapmamak için gerçek bir neden yok.


Aslında, bir yapıdan miras alabilir ve veri ekleyebilirsiniz: alt yapının ilk öğesini türü üst yapı olan bir değişken olarak bildirmeniz yeterlidir. Sonra ihtiyacınız kadar döküm.
mouviciel

1
@mouviciel - evet. Dedim ki. " ... yani ana sınıfınızı çocuk sınıfınıza dahil etmeniz veya ... "
littleadv

5
Kalıtım uygulamak için bir neden yok. Kodun yeniden kullanımını sağlamanın bir yolu olarak, başlamak için kusurlu bir fikir. Nesne kompozisyonu daha kolay ve daha iyi.
KaptajnKold

@KaptajnKold - katılıyorum.
littleadv

8

Burada nesne yönelimi C

1. Nesneler Yaratmak ve Kapsülleme

Genellikle - kişi

object_instance = create_object_typex(parameter);

Yöntemler buradaki iki yoldan biriyle tanımlanabilir.

object_type_method_function(object_instance,parameter1)
OR
object_instance->method_function(object_instance_private_data,parameter1)

Çoğu durumda, object_instance (or object_instance_private_data)döndürülen tür olduğunu unutmayın void *.Uygulama bireysel üyeleri veya bunun işlevleri başvuru olamaz.

Ayrıca her yöntem, sonraki nesne için bu object_instance öğesini kullanır.

2. Çok biçimlilik

Çalışma zamanında belirli işlevleri geçersiz kılmak için birçok işlev ve işlev işaretçisi kullanabiliriz.

örneğin - tüm object_methods, özel yöntemlerin yanı sıra herkese açık olarak genişletilebilen bir işlev işaretçisi olarak tanımlanır.

Ayrıca, fonksiyon aşırı yüklemesini sınırlı bir şekilde, var_args , printf'de değişken sayıda bağımsız değişkenin tanımlanmasına çok benzeyen . Evet, bu C ++ 'da bu kadar esnek değil - ama bu en yakın yol.

3. Kalıtımın tanımlanması

Kalıtımın tanımlanması biraz zordur, ancak yapılarla aşağıdakileri yapabilirsiniz.

typedef struct { 
     int age,
     int sex,
} person; 

typedef struct { 
     person p,
     enum specialty s;
} doctor;

typedef struct { 
     person p,
     enum subject s;
} engineer;

// use it like
engineer e1 = create_engineer(); 
get_person_age( (person *)e1); 

burada doctorve engineerkişiden türetilmiştir ve daha yüksek bir seviyeye yazılması mümkündür.person .

Bunun en iyi örneği GObject ve ondan türetilmiş nesnelerde kullanılır.

4. Sanal sınıflar oluşturma Tüm tarayıcılar tarafından jpeg kod çözme için kullanılan libjpeg adlı bir kütüphane tarafından gerçek hayattan bir örnek veriyorum. Uygulamanın somut örnek oluşturabileceği ve geri sağlayabileceği error_manager adlı bir sanal sınıf oluşturur.

struct djpeg_dest_struct {
  /* start_output is called after jpeg_start_decompress finishes.
   * The color map will be ready at this time, if one is needed.
   */
  JMETHOD(void, start_output, (j_decompress_ptr cinfo,
                               djpeg_dest_ptr dinfo));
  /* Emit the specified number of pixel rows from the buffer. */
  JMETHOD(void, put_pixel_rows, (j_decompress_ptr cinfo,
                                 djpeg_dest_ptr dinfo,
                                 JDIMENSION rows_supplied));
  /* Finish up at the end of the image. */
  JMETHOD(void, finish_output, (j_decompress_ptr cinfo,
                                djpeg_dest_ptr dinfo));

  /* Target file spec; filled in by djpeg.c after object is created. */
  FILE * output_file;

  /* Output pixel-row buffer.  Created by module init or start_output.
   * Width is cinfo->output_width * cinfo->output_components;
   * height is buffer_height.
   */
  JSAMPARRAY buffer;
  JDIMENSION buffer_height;
};

Burada, JMETHOD'un doğru yöntemlerle yüklenmesi gereken bir makro aracılığıyla bir işlev işaretçisinde genişlediğini unutmayın.


Çok fazla bireysel açıklama yapmadan çok şey söylemeye çalıştım. Ama umarım insanlar kendi şeylerini deneyebilirler. Ancak, niyetim sadece şeylerin nasıl haritalandığını göstermektir.

Ayrıca, bunun tam olarak doğru mülkiyet olmayacağına dair birçok argüman olacak C ++ eşdeğerinin . C-OO'nun tanımı kadar katı olmayacağını biliyorum. Ancak bu şekilde çalışmak, bazı temel ilkeleri anlayacaktır.

Önemli olan, OO'nun C ++ ve JAVA'daki kadar katı olmamasıdır. Kod, OO düşüncesi düşünülerek yapısal olarak düzenlenebilir ve bu şekilde çalıştırılabilir.

İnsanlara libjpeg'in gerçek tasarımını ve kaynakları takip etmelerini şiddetle tavsiye ediyorum

a. C dilinde nesne yönelimli programlama
b. burası insanların fikir alışverişinde bulunduğu iyi bir yerdir
c. ve işte tam kitap


3

Nesne yönelimi üç şeye kadar kaynar:

1) Otonom sınıflarla modüler program tasarımı.

2) Özel kapsülleme ile verilerin korunması.

3) Miras / polimorfizm ve yapıcılar / yıkıcılar, şablonlar vb. Gibi diğer çeşitli yararlı sözdizimleri.

1 açık ara en önemlisidir ve tamamen dilden bağımsızdır, hepsi program tasarımı ile ilgilidir. C ile bunu bir .h dosyası ve bir .c dosyasından oluşan otonom "kod modülleri" oluşturarak yaparsınız. Bunu bir OO sınıfının eşdeğeri olarak kabul edin. Sağduyu, UML veya C ++ programları için kullandığınız herhangi bir OO tasarımı yöntemi ile bu modülün içine ne yerleştirilmesi gerektiğine karar verebilirsiniz.

2, sadece özel verilere karşı kasıtlı erişimi korumak için değil, aynı zamanda istem dışı erişime, yani "ad alanı karmaşasına" karşı da koruma sağlamak için oldukça önemlidir. C ++ bunu C'den daha zarif bir şekilde yapar, ancak yine de statik anahtar kelimeyi kullanarak C'de elde edilebilir. Bir C ++ sınıfında özel olarak bildirdiğiniz tüm değişkenler, C içinde statik olarak bildirilmeli ve dosya kapsamına yerleştirilmelidir. Bunlara yalnızca kendi kod modüllerinden (sınıf) erişilebilir. "Setters / getters" komutunu C ++ 'da yaptığınız gibi yazabilirsiniz.

3 yardımcı olur, ancak gerekli değildir. OO programlarını kalıtım olmadan veya kurucular / yıkıcılar olmadan yazabilirsiniz. Bunların olması güzel, programları kesinlikle daha zarif ve belki de daha güvenli hale getirebilirler (veya dikkatsizce kullanılırsa tersi). Ama gerekli değiller. C, bu yararlı özelliklerin hiçbirini desteklemediğinden, bunlar olmadan yapmanız yeterlidir. Yapıcılar init / destruct işlevleriyle değiştirilebilir.

Kalıtım çeşitli yapı hileleriyle yapılabilir, ancak buna karşı tavsiyede bulunacağım, çünkü programınızı herhangi bir kazanç olmadan daha karmaşık hale getirecektir (kalıtım sadece C değil, herhangi bir dilde genel olarak dikkatlice uygulanmalıdır).

Son olarak, her OO hile yapabilirsiniz 90'ların gelen C. Axel-Tobias Schreiner kitabı "ANSI C nesne tabanlı programlama" bu kanıtlıyor yapılabilir. Ancak, bu kitabı kimseye tavsiye etmem: C programlarınıza yaygara değmeyecek hoş olmayan, garip bir karmaşıklık ekler. (Kitap buradan ücretsiz olarak edinilebilir) hala uyarıma rağmen ilgilenen kişiler için.)

Bu yüzden tavsiyem yukarıdaki 1) ve 2) 'yi uygulamak ve gerisini atlamaktır. 20 yılı aşkın süredir başarılı olduğu kanıtlanmış C programları yazmanın bir yoludur.


2

Çeşitli Objective-C çalışma zamanlarından bir miktar ödünç almak, C'de dinamik, polimorfik bir OO yeteneği yazmak çok zor değildir (diğer yandan, hızlı ve kullanımı kolay hale getirmek, 25 yıl sonra hala devam etmektedir). Ancak, dil sözdizimini genişletmeden bir Objective-C stili nesne yeteneği uygularsanız, sonuçta ortaya çıkan kod oldukça dağınık olur:

  • her sınıf, üst sınıfını, uyduğu arabirimleri, uyguladığı iletileri ("seçici" haritası, ileti adı, "uygulama", davranışı sağlayan işlev) ve sınıfın örneğini bildiren bir yapı ile tanımlanır. değişken düzen.
  • her örnek, sınıfına bir işaretçi, sonra da örnek değişkenleri içeren bir yapı ile tanımlanır.
  • Mesaj gönderme, benzer bir işlev kullanılarak uygulanır (bazı özel durumlar verin veya alın) objc_msgSend(object, selector, …). Nesnenin hangi sınıfın bir örneği olduğunu bilerek, seçiciyle eşleşen uygulamayı bulabilir ve böylece doğru işlevi yürütebilir.

Bu, birden fazla geliştiricinin birbirlerinin sınıflarını kullanmasına ve genişletmesine izin vermek için tasarlanmış genel amaçlı bir OO kütüphanesinin bir parçasıdır, bu yüzden kendi projeniz için aşırı olabilir. Genellikle C projelerini yapıları ve işlevleri kullanarak "statik" sınıf odaklı projeler olarak tasarladım: - her sınıf, ivar düzenini belirten bir C yapısının tanımıdır - her örnek karşılık gelen yapının bir örneğidir - nesneler olamaz "messaged", ancak yöntem benzeri işlevler MyClass_doSomething(struct MyClass *object, …)tanımlanmıştır. Bu, koddaki şeyleri ObjC yaklaşımından daha net hale getirir, ancak daha az esnekliğe sahiptir.

Yalanların nerede takas edileceği kendi projenize bağlıdır: Diğer programcılar C arayüzlerinizi kullanmayacak gibi görünüyor, bu yüzden seçim iç tercihlere geliyor. Tabii ki, objc çalışma zamanı kitaplığı gibi bir şey istediğinize karar verirseniz, hizmet verecek çapraz platform objc çalışma zamanı kitaplıkları vardır.


1

GObject herhangi bir karmaşıklığı gerçekten gizlemez ve kendine özgü bir karmaşıklık getirir. Sinyaller veya arayüz makineleri gibi gelişmiş GObject öğelerine ihtiyacınız yoksa, geçici şeyin GObject'ten daha kolay olduğunu söyleyebilirim.

COS ile durum biraz farklıdır, çünkü bu, bazı OO yapılarıyla C sözdizimini genişleten bir ön işlemciyle birlikte gelir. G Nesne Oluşturucu olan GObject için benzer bir önişlemci var .

Ayrıca C'ye derlenen tam yüksek düzeyli bir dil olan Vala programlama dilini ve düz C kodundan Vala kitaplıklarının kullanılmasına izin verecek şekilde deneyebilirsiniz . GObject, kendi nesne çerçevesi veya özel yol (sınırlı özelliklere sahip) kullanabilir.


1

Öncelikle, bu ödev olmadığı veya hedef cihaz için C ++ derleyicisi olmadığı sürece C kullanmanız gerektiğini düşünmüyorum, C ++ 'dan C bağlantısına sahip bir C arayüzü sağlayabilirsiniz.

İkincisi, polimorfizme ve istisnalara ve çerçevelerin sağlayabileceği diğer özelliklere ne kadar güveneceğinize bakacağım, eğer ilgili işlevlere sahip çok basit yapılar tam özellikli bir çerçeveden çok daha kolay olacaksa, tasarım onlara ihtiyaç duyar, sonra mermiyi ısırır ve bir çerçeve kullanır, böylece özellikleri kendiniz uygulamak zorunda kalmazsınız.

Karar vermek için henüz bir tasarımınız yoksa, ani bir artış yapın ve kodun size ne söylediğini görün.

Son olarak, bir veya başka bir seçenek olmak zorunda değildir (başlangıçtan itibaren çerçeveye giderseniz, göze çarpan bir şekilde yapışabilirsiniz), basit parçalar için basit yapılarla başlamak ve sadece kütüphanelere eklemek mümkün olmalıdır. ihyaç olduğu gibi.

EDIT: Yani bu yönetim hakkı tarafından bir karar o zaman ilk noktayı göz ardı sağlar.

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.