Bir sınıf özelliği yeni bir sınıf örneği oluşturup döndürürse, bu bir kalıp karşıtı mı?


24

HeadingBirkaç şey yapan denilen bir sınıfa sahibim , fakat aynı zamanda Headingsınıfın kendisinin yeni bir örneğini oluşturarak kullanılması gereken mevcut başlık değerinin tersini de getirebilmelidir .

reciprocalGeçerli değerin karşıt başlığını döndürmek için çağrılan basit bir özelliğe sahip olabilirim ve daha sonra el ile Heading sınıfının yeni bir örneğini el ile oluşturabilir veya createReciprocalHeading()otomatik olarak Heading sınıfının yeni bir örneğini oluşturmak ve bu değere geri dönmek gibi bir yöntem oluşturabilirim. kullanıcı.

Ancak, iş arkadaşlarımdan biri sadece bir sınıf oluşturmak için beni tavsiye özelliğini denilen reciprocalkendi alıcı yöntemi ile sınıfının kendisi yeni bir örneği döndürdüğü.

Sorum şu: Bir sınıfın mülkünün böyle davranması bir kalıp karşıtı değil midir?

Özellikle bunu daha az sezgisel buluyorum çünkü:

  1. Aklımda, bir sınıfın özelliği, yeni bir sınıf örneği döndürmemeli ve
  2. Mülkün adı reciprocal, geliştiricinin, IDE'den yardım almadan veya alıcı imzasını kontrol etmeden davranışını tam olarak anlamasına yardımcı olmaz.

Bir sınıf mülkünün ne yapması gerektiği konusunda çok katı mıyım yoksa geçerli bir endişe mi? Her zaman sınıfın durumunu alanları ve özellikleri ile yönetmeye çalıştım, davranışları ve yöntemleri ile davranışlarını gördüm ve bunun sınıf özellik tanımına nasıl uyduğunu göremiyorum.


6
@Ewan'ın dediği gibi, cevabının son paragrafında, anti-patern olmaktan uzak, Headingdeğişmez bir türe sahip olmak ve reciprocalyeni bir Headingşey döndürmek , "başarı çukuru" iyi bir uygulamadır. (iki çağrıdaki ihtirasla reciprocal"aynı şeyi" geri getirmeli, yani eşitlik testini geçmelidirler.)
David Arno

20
"sınıfım değiştirilemez". İşte gerçek anti-paternin, işte orada. ;)
David Arno

3
@DavidArno Bazen, bazı şeyleri değiştirilemez hale getirme konusunda büyük bir performans cezası var, özellikle de dil doğal olarak desteklemiyorsa, ama aksi takdirde değişmezliğin birçok durumda iyi bir uygulama olduğuna katılıyorum.
53777A,

2
@dcorking sınıfı hem C # hem de TypeScript'te uygulanır ancak bu dili agnostik tutmayı tercih ederim.
53777A

2
@Kaiserludi bir cevap gibi geliyor (harika bir cevap) - yorumlar açıklık istemek içindir
dcorking

Yanıtlar:


22

Copy () veya Clone () gibi şeylere sahip olduğu bilinmiyor ama evet, bu konuda endişelenmekte haklı olduğunuzu düşünüyorum.

örneğin:

h2 = h1.reciprocal
h2.Name = "hello world"
h1.reciprocal.Name = ?

Özelliğin her seferinde yeni bir nesne olduğu konusunda biraz uyarmak güzel olurdu.

Ayrıca şunu varsayabilirsin:

h2.reciprocal == h1

Ancak. Eğer sınıfınız değişmez bir değer tipi olsaydı, o zaman bu ilişkileri uygulayabileceksiniz ve karşılıklı işlem işlem için iyi bir isim olabilirdi.


1
Sadece not edin Copy()ve Clone()özellik değil yöntemlerdir. OP'nin mülk alıcılarına yeni örnekler döndüren bir kaynak olduğuna inanıyorum.
Lynn

6
biliyorum. ama özellikleri de (tür) c #
Ewan

4
Bu durumda, C # teklif daha sunduğu: String.Substring(), Array.Reverse(), Int32.GetHashCode()vb
Lynn

If your heading class was an immutable value type- Değişmez nesneler üzerindeki özellik ayarlayıcılarının her zaman (veya en azından yeni değer eski değerle aynı değilse) yeni örnekleri döndürmesi gerektiğini eklemelisiniz, çünkü değişmez nesnelerin tanımı budur. :)
Ivan Kolmychek

17

Bir sınıfın arayüzü, sınıfın kullanıcısına nasıl çalıştığı hakkında varsayımlarda bulunmasını sağlar.

Bu varsayımların birçoğu doğru ve birkaçı yanlışsa, arayüz iyidir.

Bu varsayımların birçoğu yanlışsa ve birkaçı doğru ise, arayüz çöptür.

Özellikler ile ilgili yaygın bir varsayım, get işlevini çağırmanın ucuz olmasıdır. Özellikler hakkındaki bir başka yaygın varsayım, get işlevini iki defa üst üste çağırmanın aynı şeyi döndürmesidir.


Beklentileri değiştirmek için tutarlılığı kullanarak bu sorunu çözebilirsiniz. Örneğin, ihtiyacınız küçük 3D kütüphanesi için Vector, Ray Matrixvb Eğer böyle gibi getters olduğunu yapabilir Vector.normalve Matrix.inversehemen hemen her zaman pahalıdır. Ne yazık ki, arayüzleriniz sürekli olarak pahalı özellikler kullanıyor olsa bile, arayüzler en iyi şekilde kullanılanlar kadar sezgisel olacaktır Vector.create_normal()ve Matrix.create_inverse()- özellikleri kullanmanın beklentileri değiştirdikten sonra bile daha sezgisel bir arayüz oluşturduğuna dair güçlü bir argüman olmadığını biliyorum.


10

Özellikler çok hızlı geri dönmeli, tekrarlanan çağrılarla aynı değeri döndürmeli ve değerlerini almanın hiçbir yan etkisi olmamalıdır. Burada tarif ettiğiniz şey bir mülk olarak uygulanmamalıdır.

Sezginiz tamamen doğrudur. Bir fabrika yöntemi tekrarlanan çağrılar ile aynı değeri döndürmez. Her seferinde yeni bir örnek döndürür. Bu hemen hemen bir özellik olarak diskalifiye olur. Daha sonra geliştirme, örneğin oluşturmaya, örneğin ağ bağımlılıklarına ağırlık eklerse hızlı değildir.

Özellikler genellikle sınıfın mevcut durumunun bir bölümünü alan / ayarlayan çok basit işlemler olmalıdır. Fabrika benzeri işlemler bu kritere uymuyor.


1
DateTime.NowÖzelliği genel olarak kullanılan ve yeni bir nesne anlamına gelmektedir. Bir özelliğin adının, aynı nesnenin her seferinde döndürülüp döndürülmeyeceği konusundaki belirsizliğin giderilmesine yardımcı olabileceğini düşünüyorum. Hepsi isimde ve nasıl kullanıldığı.
Greg Burghardt,

3
DateTime.Now bir hata olarak kabul edilir. Bkz stackoverflow.com/questions/5437972/...
Brad Thomas

@GregBurghardt Yeni bir nesnenin yan etkisi, kendi başına bir sorun olmayabilir, çünkü DateTimebir değer türüdür ve nesne kimliği kavramına sahip değildir. Doğru anlarsam, mesele bunun deterministik olmaması ve her seferinde yeni bir değer döndürmesidir.
Zev Spitz

1
@GregBurghardt DateTime.Newstatik ve bu nedenle nesnenin durumunu temsil etmesini bekleyemezsiniz.
Tsahi Asher,

5

“Mülkiyet” i oluşturan, dile özgü bir soru olduğu ve bir “mülkün” arayıcısının ne beklediği, dile özgü bir soru olduğu için buna dille ilgili yanıt olmadığını düşünüyorum. Bunun hakkında düşünmenin en verimli yolunun, arayanın bakış açısından neye benzediğini düşünmektir.

C # 'da, özellikler (geleneksel olarak) büyük harf kullanımı (benzer yöntemler), ancak parantez (kamu örneği değişkenleri gibi) olmaları bakımından belirgindir. Aşağıdaki kodu görürseniz, dokümantasyon yoksa, ne bekliyorsunuz?

var reciprocalHeading = myHeading.Reciprocal;

Göreceli bir C # acemi, ama Microsoft'un okumuş biri olarak Mülkiyet Kullanım Kuralları , ben beklenebilir Reciprocaldiğer şeylerin yanı sıra için:

  1. Headingsınıfın mantıksal veri üyesi olmak
  2. aramaya ucuz olmak, böylece değeri önbelleğe almama gerek kalmayacak
  3. gözlemlenebilir yan etkilerin olmaması
  4. art arda iki kez çağrılırsa aynı sonucu verir
  5. (belki) bir ReciprocalChangedetkinlik teklif et

Bu varsayımlardan, (3) ve (4) muhtemelen doğrudur ( Ewan'ın cevabındaHeading olduğu gibi değişmez bir değer olduğu varsayılmaktadır ), (1) tartışılabilir, (2) bilinmemektedir ancak aynı zamanda tartışmalı ve (5) muhtemel değildir. (her ne olsa semantik anlam vardır bir başlık belki olması gereken olay). Bu, bir C # API, “olsun ya da tersi ile hesaplamak” gerektiğini bana gösteriyor değil bir özellik olarak uygulanacak, ancak hesaplama en ucuz ve özellikle değişmez, bu sınırda bir dava.HeadingChangedHeading

(Bununla birlikte, bu kaygılardan hiçbirinin mülkün çağrılmasının bile yeni bir örnek yaratıp yaratmadığına dair bir şey olmadığını unutmayın.

Java'da, özellikler bir adlandırma kuralıdır. Görürsem

Heading reciprocalHeading = myHeading.getReciprocal();

Beklentilerim yukarıdakilere benzer (daha az açık bir şekilde belirtilmişse): Çağrının ucuz, cüretkar ve yan etkilerden yoksun olmasını bekliyorum. Bununla birlikte, JavaBeans çerçevesi dışında bir “mülkiyet” kavramı Java'da pek de anlamlı değildir ve özellikle buna karşılık gelmeyen değişmez bir özellik söz konusu olduğunda setReciprocal(), getXXX()sözleşme şimdi biraz eski modadır. Gönderen Etkili Java , ikinci baskısında (şimdi zaten fazla sekiz yaşında):

Çalıştıkları booleannesnenin işlevsiz veya özniteliğini döndüren yöntemler genellikle isim, isim veya fiil ile başlayan bir fiil cümlesi olarak adlandırılır get. Sadece üçüncü formun (başlangıçtan itibaren get) kabul edilebilir olduğunu iddia eden vokal bir koşul vardır, ancak bu iddia için çok az temel vardır. İlk iki form genellikle daha okunabilir kodlara yol açar… (s. 239)

Çağdaş, daha akıcı bir API'de görmeyi beklerdim

Heading reciprocalHeading = myHeading.reciprocal();

- bu da çağrının ucuz olduğunu, önemsiz olduğunu ve yan etkilerin bulunmadığını, ancak yeni bir hesaplamanın yapılıp yapılmayacağına ya da yeni bir nesnenin yaratılıp yaratılmadığına dair hiçbir şey söylemediğini gösterir. Bu iyi; iyi bir API’de umrumda olmamalı.

Ruby'de mülk diye bir şey yoktur. “Nitelikler” var, ama görürsem

reciprocalHeading = my_heading.reciprocal

Örnek bir değişkene basit bir erişimci yöntemi @reciprocalile mi erişiyorum attr_reader, yoksa pahalı bir hesaplama yapan bir yöntem mi aradığımı bilmenin hiçbir yolu yok . Yöntem adının basit bir isim olması gerçeği, söylemekten ziyade calcReciprocal, çağrının en azından ucuz olduğunu ve muhtemelen yan etkileri olmadığını öne sürüyor.

Scala’da, adlandırma kuralı, yan etkileri olan yöntemlerin parantez alıp almadıklarını belirtir.

val reciprocal = heading.reciprocal

herhangi biri olabilir:

// immutable public value initialized at creation time
val reciprocal: Heading = … 

// immutable public value initialized on first use
lazy val reciprocal: Heading = … 

// public method, probably recalculating on each invocation
def reciprocal: Heading = …

// as above, with parentheses that, by convention, the caller
// should only omit if they know the method has no side effects
def reciprocal(): Heading = …

(Scala'nın yine de stil rehberi tarafından önerilmeyen çeşitli şeylere izin verdiğine dikkat edin . Bu, Scala'daki en büyük sıkıntılarımdan biri.)

Parantez eksikliği, çağrının yan etkileri olmadığını söylüyor; isim, yine görüşmenin nispeten ucuz olması gerektiğini ileri sürüyor. Bunun ötesinde, bana değeri nasıl elde edeceği umrumda değil .

Kısacası: Kullandığınız dili ve diğer programcıların API'nize ne gibi beklentiler getireceğini bilin. Geriye kalan her şey bir uygulama detayıdır.


1
En sevdiğim cevaplardan birini + 1'leyin, bunu yazmaya zaman ayırdığınız için teşekkürler.
53777A

2

Başkalarının söylediği gibi, aynı sınıfın örneklerini döndürmek oldukça yaygın bir kalıptır.

Adlandırma dilin adlandırma kuralları ile birlikte yapılmalıdır.

Örneğin, Java’da muhtemelen çağrılmasını beklerdim getReciprocal();

Olduğu söyleniyor, değişken vs değişmez nesneler olarak kabul ediyorum .

İle değişmez olanlar, işler çok kolay ve aynı nesneyi döndürmek ya da değil eğer zarar vermez.

İle değişken olanlar, bu çok korkutucu alabilirsiniz.

b = a.reciprocal
a += 1
b = what?

Şimdi, b neyi kastediyor? A'nın orijinal değerinin karşılığı mı? Ya da değişmiş olanın karşılığı? Bu bir örnek için çok kısa olabilir, ancak bunu anladınız.

Bu durumlarda , ne olduğunu bildiren daha iyi bir isim arayın , örneğin createReciprocal()daha iyi bir seçim olabilir.

Ama bu gerçekten de içeriğe de bağlı.


Değişken mi demek istedin ?
Carsten

@CarstenS Evet, elbette, teşekkürler. Kafamın nerede olduğu hakkında hiçbir fikrim yok. :)
Eiko

Biraz getReciprocal()" katılmıyorum , çünkü" normal "JavaBeans tarzı bir alıcı gibi" sesler ". "ProgramXXX ()" veya büyük olasılıkla "hesaplamakXXX ()" yi, diğer programcılara, farklı bir şey olduğunu gösteren veya ima eden
tercih edin

@ user949300 Endişelerinize biraz katılıyorum - ve yarat ... isminden daha aşağıda bahsetmiştim. Yine de olumsuz tarafları var. create ... her zaman olamayacak yeni örnekleri ima eder (bazı özel durumlar için tekil özel nesneler düşünün), hesaplayın ... bir tür kaçak uygulama detayı - fiyat etiketinde yanlış varsayımları teşvik edebilir.
Eiko,

1

Tek sorumluluk ve netlik adına, nesneyi alan ve tersini döndüren bir Ters Çevirme Fabrikası olurdu. Bu, yeni bir nesnenin iade edildiğini daha net bir şekilde ortaya koyar ve tersini üretecek kodun diğer kodlardan uzağa yerleştirildiği anlamına gelir.


1
İsim netliği için +1, fakat diğer çözümlerde SRP'nin nesi yanlış?
53777A,

3
Neden bir fabrika yöntemini kullanarak bir fabrika sınıfını öneriyorsunuz? (Bence, bir nesnenin yararlı bir sorumluluk böyle iterator.next olarak kendisi gibi yayarlar nesneler, genellikle olduğu mu diğer yazılım mühendisliği problemlerinin nedeni SRP bu hafif ihlali.?)
dcorking

2
@dcorking Fabrika sınıfına karşı bir yöntem (bence) genellikle "Nesne karşılaştırılabilir mi yoksa karşılaştırıcı mı yapmalıyım?" sorusuyla aynı sorudur. Karşılaştırıcı yapmak her zaman iyidir , ancak bunları karşılaştırmanın oldukça evrensel bir yoluysa, karşılaştırılabilir yapmak için tamam. (Örneğin, Büyük sayılar, küçük sayılardan daha büyüktür, bu karşılaştırılabilirlik için iyidir, ancak tüm eşlerin tüm olasılıklardan daha büyük olduğunu söyleyebilirsin, bunu karşılaştırıcı yapardım) - Yani bunun için , sadece bir tane olduğunu düşünürdüm. Üstbilgiyi ters çevirme yolu, bu yüzden fazladan bir sınıftan kaçınmak için bir yöntem yapmaya doğru eğilirim.
Yüzbaşı Man

0

Değişir. Genelde özelliklerin bir örneğin parçası olan şeyleri döndürmesini beklerim, bu yüzden aynı sınıfın farklı bir örneğini döndürmek biraz sıra dışı olurdu.

Fakat örneğin, bir dize sınıfı "firstWord", "lastWord", ya da tam dize nesneleri olan Unicode "firstLetter", "lastLetter" 'ı kullanırsa, sadece genellikle daha küçük olanları olabilir.


0

Diğer cevaplar Mülkiyet bölümünü kapsadığından, sadece 2 ile ilgili olarak reciprocal:

Dışında bir şey kullanmayın reciprocal. Tanımladığınız şey için tek doğru terimdir (ve resmi terimdir). Bunlar. Gelen navigasyon çalışıyorsanız etki alanını öğrenme adresinin geliştiriciler kaydetmek için yanlış bir yazılım yazmayın, terimler çok özel anlamlara sahip gibi görünüşte zararsız olarak bir şey kullanarak reverseveya oppositebelirli bağlamlarda karışıklığa yol açabilir.


0

Mantıksal olarak hayır, bir başlığın özelliği değildir. Öyle olsaydı, bir sayının negatifinin o sayının mülkiyeti olduğunu ve açıkçası "mülkiyeti" nin bütün anlamını yitireceğini, neredeyse her şeyin bir mülk olacağını söyleyebilirsiniz.

Karşılıklı, bir başlığın saf bir işlevidir, bir sayının negatifiyle aynıdır, bu sayının saf bir işlevidir.

Genel bir kural olarak, eğer ayarlanması mantıklı değilse, muhtemelen bir özellik olmamalıdır. Ayarlama işlemine hala izin verilmez, ancak bir ayarlayıcı eklemek teorik olarak mümkün ve anlamlı olmalıdır.

Şimdi, bazı dillerde, eğer o dilin mekaniği nedeniyle bazı avantajlar getirirse, yine de o dil bağlamında belirtilen bir özellik olarak sahip olmak mantıklı gelebilir. Örneğin, bir veritabanında saklamak istiyorsanız, ORM çerçevesi tarafından otomatik olarak kullanılmasına izin veriyorsa, özellik olarak kullanılması muhtemelen mantıklı olacaktır. Veya belki de bir özellik ile parametresiz üye işlevi arasında ayrım olmadığı bir dildir (Böyle bir dil bilmiyorum ama bazılarının olduğundan eminim). Ardından, işlevleri ve özellikleri birbirinden ayırmak API tasarımcısı ve belgeseline kalmıştır.


Düzenleme: Özellikle C # için, var olan sınıflara, özellikle de Standart Kütüphanenin sınıflarına bakardım.

  • Var BigIntegerve Complexyapılar var. Ancak bunlar yapılardır ve yalnızca statik işlevlere sahiptir ve tüm özellikler farklı türdedir. HeadingSınıfınız için orada pek fazla tasarım yardımı yoktur .
  • Math.NET Numerics , çok az özellik içeren bir Vectorsınıfa sahiptir ve size oldukça yakın görünmektedir Heading. Eğer buna uyursanız, karşılıklı olarak bir işleve sahip olursunuz, mülk değil.

Kodunuz tarafından gerçekten kullanılan kütüphanelere veya mevcut benzer sınıflara bakmak isteyebilirsiniz. Tutarlı kalmaya çalışın, eğer bir yol açıkça doğru değilse ve başka bir yol yanlışsa bu en iyisidir.


-1

Gerçekten çok ilginç bir soru. Teklif ettiğiniz hiçbir SOLID + DRY + KISS ihlali görmüyorum, ama yine de kötü kokuyor.

Sınıfın bir örneğini döndüren yönteme yapıcı denir , değil mi? yani yapıcı olmayan bir yöntemden yeni bir örnek yaratıyorsunuz. Dünyanın sonu değil, bir müşteri olarak bunu beklemem . Bu genellikle belirli durumlarda yapılır: bir fabrika veya bazen aynı sınıfın statik bir metodu (singletonlar ve ... nesne odaklı olmayan programcılar için faydalıdır?)

Artı: getReciprocal()Döndürülen nesneyi çağırırsanız ne olur ? sınıfın başka bir örneğini elde edersiniz, bu ilki tam bir kopyası olabilir! Ve eğer bunu getReciprocal()bir tane üzerine çağırırsanız? Kimseyi yayan nesneler?

Yine: ya invoker karşılıklı değere ihtiyaç duyarsa (skaler olarak demek istiyorum)? getReciprocal()->getValue()? Demeter Yasasını küçük kazançlar için ihlal ediyoruz .


Memnuniyetle aşağı-seçmen biçimindeki karşı-argümanları kabul ediyorum, teşekkürler!
Dr. Gianluigi Zane Zanettini

1
Oy vermedim, ancak sebebin, cevapların geri kalanına fazla bir şey katmamasının nedeni olabileceğinden şüpheleniyorum, ancak hatalı olabilirim.
53777A

2
KATI ve KISS genellikle birbirinin zıddıdır.
whatsisname,
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.