Yapıcı genellikle yöntem çağırmamalıdır


12

Bir meslektaşımla, yöntemi çağıran bir kurucunun neden bir karşıt-madde olabileceğini anlattım.

örnek (paslı C ++'ımda)

class C {
public :
    C(int foo);
    void setFoo(int foo);
private:
    int foo;
}

C::C(int foo) {
    setFoo(foo);
}

void C::setFoo(int foo) {
    this->foo = foo
}

Ek katkınızla bu gerçeği daha iyi motive etmek istiyorum. Örnekleriniz, kitap referanslarınız, blog sayfalarınız veya ilkelerin adlarınız varsa, bunlar çok memnuniyetle karşılanır.

Edit: Ben genel olarak konuşuyorum, ama biz python kodlama.


Bu genel bir kural mı yoksa belirli dillere mi özgü?
ChrisF

Hangi dil? C ++ 'da bir anti-kalıptan daha fazlasıdır: parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.5
LennyProgrammers

@ Lenny222'de, OP en azından benim için örnek olmayan yöntemler anlamına gelen "sınıf yöntemleri" hakkında konuşuyor . Bu nedenle sanal olamaz.
Péter Török

3
@Alb Java'da gayet iyi. Yapmamanız gereken şey thisyapıcıdan çağırdığınız yöntemlerden herhangi birine açıkça geçmektir .
biziclop

3
@Stefano Borini: Python'da kod yazıyorsanız, neden paslı C ++ yerine Python'da örneği göstermiyorsunuz? Ayrıca, lütfen bunun neden kötü bir şey olduğunu açıklayın. Bunu her zaman yapıyoruz.
S.Lott

Yanıtlar:


26

Bir dil belirtmediniz.

C ++ 'da, bir yapıcı sanal bir işlevi çağırırken dikkat etmelidir, çağıran gerçek işlev sınıf uygulamasıdır. Uygulama olmadan saf bir sanal yöntemse, bu bir erişim ihlali olacaktır.

Bir kurucu sanal olmayan fonksiyonları çağırabilir.

Diliniz, işlevlerin varsayılan olarak sanal olduğu Java ise, çok dikkatli olmanız mantıklıdır.

C # durumu beklediğiniz gibi ele alıyor gibi görünüyor: yapıcılarda sanal yöntemleri çağırabilirsiniz ve en son sürümü çağırır. Yani C # 'da bir anti-desen değil.

Yöntemleri kuruculardan çağırmanın yaygın bir nedeni, ortak bir "init" yöntemi çağırmak isteyen birden çok kurucuya sahip olmanızdır.

Yıkıcıların sanal yöntemlerle aynı sorunu yaşayacağına dikkat edin, böylece yıkıcınızın dışında duran ve temel sınıf yıkıcı tarafından çağrılmasını bekleyecek sanal bir "temizleme" yöntemine sahip olamayacağınızı unutmayın.

Java ve C # 'nin yıkıcıları yoktur, sonlandırıcıları vardır. Java ile olan davranışı bilmiyorum.

Bu konuda C # temizliğini doğru işlemektedir.

(Her ne kadar Java ve C # çöp toplama özelliğine sahip olsalar da, bu sadece bellek tahsisini yönetir. Yıkıcısının yapması gereken başka bir temizleme işlemi vardır, bu da belleği serbest bırakmaz).


13
Burada birkaç küçük hata var. C # 'daki yöntemler varsayılan olarak sanal değildir. Bir kurucuda sanal yöntem çağrılırken C #, C ++ 'dan farklı semantiklere sahiptir; en çok türetilen türdeki sanal yöntem çağrılır, şu anda yapılandırılmakta olan türün bölümündeki sanal yöntem değil. C #, sonlandırma yöntemlerini "yıkıcılar" olarak adlandırır, ancak kesinleştiricilerin anlambilimine sahip olduğunuz konusunda haklısınız. C # yıkıcıları olarak adlandırılan sanal yöntemler, yapıcılarda olduğu gibi çalışır; en türetilmiş yöntem denir.
Eric Lippert

@ Péter: Örnek yöntemleri amaçladım. karışıklık için özür dilerim.
Stefano Borini

1
@Eric Lippert. C # konusundaki uzmanlığınız için teşekkür ederim, cevabımı buna göre düzenledim. Ben bu dil hakkında bilinmeyen, C ++ çok iyi ve Java daha az iyi biliyorum.
CashCow

5
Rica ederim. C # 'da bir temel sınıf yapıcısında sanal bir yöntem çağırmanın hala oldukça kötü bir fikir olduğunu unutmayın.
Eric Lippert

Java'da bir yapıcıdan bir (sanal) yöntem çağırırsanız, her zaman en türetilen geçersiz kılma çağrılır. Ancak, “beklediğiniz gibi” dediğiniz şey, kafa karıştırıcı dediğim şeydir. Java en türetilmiş geçersiz kılmayı çağırırken, bu yöntem yalnızca dosyalanmış başlatıcıları görür, ancak kendi sınıfının yapıcısını görmez. Değişmezi henüz oluşturulmamış bir sınıfa bir yöntem çağırmak tehlikeli olabilir. Bence burada C ++ daha iyi bir seçim yaptı.
5gon12eder

18

Tamam, şimdi sınıf yöntemleri vs örnek yöntemleri ile ilgili karışıklık temizlendi, ben bir cevap verebilir :-)

Sorun genel olarak bir kurucudan çağrı örnek yöntemleri ile değil; (doğrudan veya dolaylı olarak) sanal yöntemleri çağırma ile . Ve ana nedeni, kurucu içinde iken, nesnenin henüz tam olarak inşa edilmemiş olmasıdır . Ve özellikle alt sınıf parçaları, temel sınıf yapıcısı yürütülürken hiç oluşturulmamıştır. Bu nedenle iç durumu dile bağlı bir şekilde tutarsızdır ve bu farklı dillerde farklı küçük hatalara neden olabilir.

C ++ ve C # zaten başkaları tarafından tartışılmıştır. Java'da en türetilmiş türün sanal yöntemi çağrılır, ancak bu tür henüz başlatılmaz. Dolayısıyla, bu yöntem türetilmiş türden herhangi bir alan kullanıyorsa, bu alanlar o anda henüz doğru şekilde başlatılamayabilir. Bu sorun, Effecive Java 2nd Edition , Madde 17: Kalıtım için tasarım ve dokümantasyonda ayrıntılı olarak tartışılmıştır veya başka şekilde yasaklanmıştır .

Bunun, nesne referanslarını erken yayınlamanın genel sorunu için özel bir durum olduğunu unutmayın . Örnek yöntemlerinin örtük bir thisparametresi vardır, ancak thisaçıkça bir yönteme geçmek benzer sorunlara neden olabilir. Özellikle, nesne başvurusunun başka bir iş parçacığına erken yayınlanması durumunda, bu iş parçacığı, ilk iş parçacığındaki kurucu bitmeden önce üzerinde zaten yöntemler çağırabilir.


3
(+1) "kurucudayken nesne henüz tam olarak oluşturulmamış." "Sınıf yöntemleri vs örnek" ile aynı. Bazı programlama dilleri, yapıcıya girerken yapıcıya değer atadığı programcı gibi yapıldığını düşünmektedir.
umlcat

7

Burada yöntem çağrıları kendi içinde bir antipattern olarak düşünmezdim, daha fazla bir kod kokusu. Bir sınıf reset, bir nesneyi orijinal durumuna döndüren bir yöntem reset()sağlarsa, yapıcıyı çağırmak DRY olur. (Sıfırlama yöntemleri hakkında herhangi bir açıklama yapmıyorum).

Yetki için itirazınızı yerine getirmenize yardımcı olabilecek bir makale: http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

Bu gerçekten çağrı yöntemleri değil, çok fazla şey yapan inşaatçılar hakkında. IMHO, bir kurucudaki yöntemleri çağırmak, bir kurucunun çok ağır olduğunu gösterebilecek bir kokudur.

Bu, kodunuzu test etmenin ne kadar kolay olduğu ile ilgilidir. Nedenleri şunları içerir:

  1. Birim testi çok sayıda yaratım ve yıkımı içerir - bu nedenle inşaat hızlı olmalıdır.

  2. Bu yöntemlerin ne yaptığına bağlı olarak, yapıcıda ayarlanmış bazı (potansiyel olarak denenemez) ön koşullara dayanmadan ayrı kod birimlerinin test edilmesini zorlaştırabilir (örn. Bir ağdan bilgi alın).


3

Felsefi olarak kurucunun amacı, ham bir bellek yığınını bir örneğe dönüştürmektir. Yapıcı yürütülürken, nesne henüz mevcut değildir, bu nedenle yöntemlerini çağırmak kötü bir fikirdir. Ne de olsa dahili olarak ne yaptıklarını bilemeyebilirsiniz ve çağrıldığında nesneyi en azından varlığını (duh!) Haklı olarak düşünebilirler.

Teknik olarak, bu konuda yanlış bir şey olmayabilir, C ++ ve özellikle Python'da dikkatli olmak size kalmış.

Pratik olarak, çağrıları yalnızca sınıf üyelerini başlatan yöntemlerle sınırlamalısınız.


2

Genel amaçlı bir konu değil. C ++ 'da, özellikle miras ve sanal yöntemler kullanılırken bir sorun vardır , çünkü nesne yapısı geriye doğru gerçekleşir ve vtable işaretçileri miras hiyerarşisindeki her kurucu katmanla sıfırlanır, bu nedenle sanal bir yöntem çağırıyorsanız, aslında sanal yöntemlerin kullanım amacını ortadan kaldıran, yaratmaya çalıştığınız sınıfa tekabül eden sınıfı elde edin.

Vtable işaretçisini başlangıçtan itibaren doğru ayarlayan aklı başında OOP desteği olan dillerde, bu sorun yoktur.


2

Bir yöntemi çağırmakla ilgili iki sorun vardır:

  • beklenmedik bir şey (C ++) yapabilen veya henüz başlatılmamış nesnelerin parçalarını kullanabilen sanal bir yöntem çağırmak
  • Nesne henüz tam olarak tamamlanmadığından (ve dolayısıyla değişmezi tutamayabileceğinden) genel bir yöntemi çağırmak (sınıf değişmezlerini zorlaması gerekir)

Önceki iki durumda düşmediği sürece yardımcı fonksiyon çağırma konusunda yanlış bir şey yoktur.


1

Bunu satın almıyorum. Nesne yönelimli bir sistemde, bir yöntemi çağırmak yapabileceğiniz tek şeydir. Aslında bu aşağı yukarı “nesne yönelimli” nin tanımıdır . Eğer bir kurucu herhangi bir yöntem çağıramazsa, ne yapabilir ?


Nesneyi başlatın.
Stefano Borini

@Stefano Borini: Nasıl? Nesneye yönelik bir sistemde yapabileceğiniz tek şey çağrı yöntemleri. Ya da ona ters açıdan bakmak: her şey çağrı yöntemleri ile yapılır. Ve "her şey" açıkça nesne başlatma içerir. Öyleyse, nesneyi başlatmak için yöntemleri çağırmanız gerekiyor, ancak yapıcılar yöntemleri çağıramıyorsa, bir kurucu nesneyi nasıl başlatabilir?
Jörg W Mittag

Yapabileceğiniz tek şeyin yöntemleri aramak olduğu kesinlikle doğru değil. Durumu herhangi bir çağrı yapmadan, doğrudan nesnenizin iç kısımlarına başlatabilirsiniz ... Yapıcı, bir nesneyi tutarlı bir durumda yapmaktır. Başka yöntemler arama onlar yöntem, aşağıda belirtilen, kısmi halde bir nesne taşıma sorunları olabilir , özellikle (genellikle yardımcı yöntemler gibi) yapıcı çağrılabilir yapılan
Stefano Borini

@Stefano Borini: "Durumu herhangi bir çağrı yapmadan, doğrudan nesnenizin iç kısımlarına başlatabilirsiniz." Ne yazık ki, bu bir yöntem içerdiğinde ne yaparsınız? Kodu kopyalayıp geçtiniz mi?
S.Lott

1
@ S.Lott: hayır, onu çağırıyorum, ancak bir nesne yöntemi yerine bir modül işlevi tutmaya çalışıyorum ve yapıcıdaki nesne durumuna koyabildiğim dönüş verilerini sağlıyor. Gerçekten bir nesne yöntemim olması gerekiyorsa, bunu özel hale getireceğim ve bunun uygun bir ad vermek gibi başlatma için açıklayacağım. Ancak asla yapıcıdan nesne durumunu ayarlamak için genel bir yöntem çağırmaz.
Stefano Borini

0

OOP teorisinde önemli değil, ama pratikte her OOP programlama dili yapıcıları farklı şekilde ele alıyor . Statik yöntemleri çok sık kullanmıyorum.

C ++ & Delphi, bazı özellikleri ("alan üyeleri") için başlangıç ​​değerleri vermek zorunda kaldı ve kod çok genişletilmiş, ben yapıcıların uzantısı olarak bazı ikincil yöntemleri ekleyin.

Ve daha karmaşık şeyler yapan başka yöntemler de demeyin.

"Getters" ve "setters" özelliklerine gelince, genellikle durumlarını saklamak için özel / korumalı değişkenleri ve "getters" & "setters" yöntemlerini kullanırım.

Yapıcıda , "erişimciler" çağrılmadan, özellikler durum alanlarına "varsayılan" değerler atarım .

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.