Bir nesneyi, nesneyi değiştiren bir yönteme geçirmek, ortak (anti-) bir kalıp mıdır?


17

Martin Fowler'in Refactoring kitabındaki ortak kod kokularını okuyorum . Bu bağlamda, bir kod tabanı içinde gördüğüm bir model merak ediyordum ve biri objektif bir anti-desen olarak düşünebilir olsun.

Desen, bir nesnenin, tümü nesnenin durumunu değiştiren ancak hiçbiri nesneyi döndürmeyen bir veya daha fazla yönteme argüman olarak iletildiği modeldir. Bu nedenle (bu durumda) C # /. NET'in referans niteliğine göre geçişe dayanmaktadır.

var something = new Thing();
// ...
Foo(something);
int result = Bar(something, 42);
Baz(something);

(Özellikle yöntemler uygun şekilde adlandırılmadığında), nesnenin durumunun değişip değişmediğini anlamak için bu tür yöntemlere bakmam gerektiğini buluyorum. Kod yığınını daha karmaşık hale getirir, çünkü çağrı yığınının birden fazla seviyesini izlemem gerekir.

Yeni durum ile başka bir (klonlanmış) nesne veya çağrı sitesinde nesneyi değiştirmek için gereken bir şey döndürmek için bu tür kodu geliştirmek öneriyoruz.

var something1 =  new Thing();
// ...

// Let's return a new instance of Thing
var something2 = Foo(something1);

// Let's use out param to 'return' other info about the operation
int result;
var something3 = Bar(something2, out result);

// If necessary, let's capture and make explicit complex changes
var changes = Baz(something3)
something3.Apply(changes);

Bana öyle geliyor ki varsayımlar üzerinde ilk kalıp seçilmiş

  • daha az iş olduğunu veya daha az kod satırı gerektirdiğini
  • Her iki değişim nesnesi bize imkan verdiğini ve bazı bilgileri diğer parçasını iade
  • daha az örneğimiz olduğundan daha verimli Thing.

Bir alternatifi açıklarım, ancak bunu önermek için orijinal çözüme karşı argümanların olması gerekir. Orijinal çözümün bir anti-desen olduğu gerçeğini ortaya koymak için varsa, argümanlar ne yapılabilir?

Ve alternatif çözümümde bir şey olursa ne olur?



1
@DaveHillier Teşekkürler, bu terimi biliyordum, ancak bağlantıyı kurmamıştım.
Michiel van Oosterhout

Yanıtlar:


9

Evet, orijinal çözüm tarif ettiğiniz nedenlerden dolayı bir anti-modeldir: neler olup bittiğini düşünmeyi zorlaştırır, nesne kendi durumundan / uygulamasından sorumlu değildir (kapsülleme kırılması). Ayrıca, tüm bu durum değişikliklerinin yöntemin örtülü sözleşmeleri olduğunu ve bu yöntemi değişen gereksinimler karşısında kırılgan hale getirdiğini de ekleyeceğim.

Bununla birlikte, çözümünüzün kendi dezavantajları vardır, en belirgin olanı klonlama nesnelerinin harika olmamasıdır. Büyük nesneler için yavaş olabilir. Kodun diğer bölümlerinin eski başvurularda tutulduğu hatalara yol açabilir (bu, muhtemelen tanımladığınız kod tabanındaki durumdur). Bu nesneleri açıkça değiştirilemez hale getirmek, bu sorunların en azından birkaçını çözer, ancak daha sert bir değişikliktir.

Nesneler küçük ve biraz geçici olmadıkça (bu da onları değişmezlik için iyi adaylar haline getirmediği sürece), daha fazla devlet geçişinin nesnelerin kendilerine taşınmasına meyilli olurum. Bu, bu geçişlerin uygulama ayrıntılarını gizlemenizi ve bu durum geçişlerinin kim / ne / nerede oluştuğu hakkında daha güçlü gereksinimler belirlemenizi sağlar.


1
Örneğin, bir "Dosya" nesnem varsa, herhangi bir durum değiştirme yöntemini bu nesneye taşımaya çalışmam - SRP'yi ihlal eder. Bu, "Dosya" gibi bir kitaplık sınıfı yerine kendi sınıflarınız olsa bile geçerli kalır - her durum geçiş mantığını nesnenin sınıfına koymak gerçekten mantıklı değildir.
Doc Brown

@Tetastyn Bunun eski bir cevap olduğunu biliyorum, ancak son paragraftaki önerinizi somut olarak görselleştirmekte sorun yaşıyorum. Bir örnek verebilir veya örnek verebilir misiniz?
AaronLS

@AaronLS - yerine Bar(something)(ve durumunu değiştirerek something) yapmak Barüyesi somethingçeşidini. something.Bar(42)daha muhtemel mutasyona etmektir somethingda korumak OO araçları (özel durum, arayüzler, vs) kullanmak için izin verirken, somethingdevletin
Telastyn

14

yöntemler uygun şekilde adlandırılmadığında

Aslında bu gerçek kod koku olduğunu. Değiştirilebilir bir nesneniz varsa, durumunu değiştirmek için yöntemler sağlar. Daha fazla ifadenin bir görevine katıştırılmış böyle bir yönteme bir çağrınız varsa, o görevi kendi başına bir yönteme yeniden yansıtmak iyi olur - bu da sizi tam olarak açıklanan durumda bırakır. Ancak , Foove gibi yöntem adlarınız yoksa Bar, ancak nesneyi değiştirdiklerini açıklayan yöntemler varsa, burada bir sorun görmüyorum. Düşün

void AddMessageToLog(Logger logger, string msg)
{
    //...
}

veya

void StripInvalidCharsFromName(Person p)
{
// ...
}

veya

void AddValueToRepo(Repository repo,int val)
{
// ...
}

veya

void TransferMoneyBetweenAccounts(Account source, Account destination, decimal amount)
{
// ...
}

veya böyle bir şey - Burada, bu yöntemler için klonlanmış bir nesneyi döndürmek için herhangi bir neden görmüyorum ve ayrıca, geçirilen nesnenin durumunu değiştireceklerini anlamak için uygulamalarına bakmak için bir neden yok.

Yan etkiler istemiyorsanız, nesnelerinizi değiştirilemez hale getirin, orijinal olanı değiştirmeden değiştirilmiş (klonlanmış) bir nesneyi döndürmek için yukarıdaki gibi yöntemleri zorlar.


Haklısınız, yeniden adlandırma yöntemi yeniden düzenleme, yan etkileri netleştirerek durumu iyileştirebilir. Değişiklikler, özlü bir yöntem adı mümkün olmayacaksa, zor olabilir.
Michiel van Oosterhout

2
@michielvoo: özlü yöntem adlandırma mümkün görünmüyorsa, yönteminiz yaptığı iş için işlevsel bir soyutlama yapmak yerine yanlış şeyleri birlikte gruplandırır (ve bu yan etkilerle veya yan etkiler olmadan doğrudur).
Doc Brown

4

Evet, beklenmedik yan etkilerin kötü olduğuna işaret eden birçok insan örneği için http://codebetter.com/matthewpodwysocki/2008/04/30/side-effecting-functions-are-code-smells/ adresine bakın .

Genel olarak temel ilke, yazılımın katmanlar halinde oluşturulmuş olması ve her katmanın bir sonrakine mümkün olan en temiz soyutlamayı sunmasıdır. Ve temiz bir soyutlama, onu kullanmak için mümkün olduğunca az tutmanız gereken yerdir. Buna modülerlik denir ve tek işlevlerden ağ protokollerine kadar her şey için geçerlidir.


OP'nin neyi "beklenen yan etkiler" olarak tanımladığını karakterize ederim. Örneğin, bir temsilci, listedeki her öğe üzerinde çalışan bir tür motora iletebilirsiniz. Temel olarak böyle ForEach<T>yapar.
Robert Harvey

@RobertHarvey Düzgün adlandırılmamış yöntemler ve yan etkileri anlamak için kodu okumak zorunda kaldığına dair şikayetler, onları kesinlikle beklenen yan etkiler haline getirmez.
btilly

Sana bunu vereceğim. Ancak, sonuç, beklenen site etkilerine sahip düzgün adlandırılmış bir yöntemin sonuçta bir anti-desen olmayabileceğidir.
Robert Harvey

@RobertHarvey katılıyorum. Anahtar, önemli yan etkilerin bilinmesi çok önemlidir ve dikkatle belgelenmesi gerektiğidir (tercihen yöntem adına).
btilly

Bunun beklenmedik ve açık olmayan yan etkilerin bir karışımı olduğunu söyleyebilirim. Bağlantı için teşekkürler.
Michiel van Oosterhout

3

Her şeyden önce, bu, "referans niteliğine göre geç" e bağlı değildir, nesnelerin değiştirilebilir referans türleri olmasına bağlıdır. İşlevsel olmayan dillerde neredeyse her zaman böyle olacak.

İkincisi, bunun bir sorun olup olmadığı, hem nesneye hem de farklı prosedürlerdeki değişikliklerin ne kadar sıkı bir şekilde bağlandığına bağlıdır - Foo'da bir değişiklik yapamazsanız ve bu da Bar'ın çökmesine neden olursa, bu bir sorundur. Bir kod kokusu olması gerekmez, ancak Foo veya Bar veya Bir Şey ile ilgili bir problemdir (muhtemelen Bar, girişini kontrol etmesi gerektiği gibi, ancak önlenmesi gereken geçersiz bir duruma konan bir şey olabilir).

Bunun bir anti-desen seviyesine yükseldiğini söylememeliyim, daha çok farkında olmak için bir şey.


2

A.Do(Something)Değiştirmek somethingve something.Do()değiştirmek arasında çok az fark olduğunu iddia ediyorum something. Her iki durumda da, somethingdeğiştirilecek çağrılan yöntemin adından anlaşılır olmalıdır. somethingBir parametre thisveya ortamın bir parçası olup olmadığına bakılmaksızın yöntem adından net değilse , değiştirilmemelidir.


1

Bazı senaryolarda nesnenin durumunu değiştirmek iyi olur. Örneğin, bir kullanıcı listem var ve istemciye geri göndermeden önce listeye farklı filtreler uygulamak istiyorum.

var users = Dependency.Resolve<IGetUsersQuery>().GetAll();

var excludeAdminUsersFilter = new ExcludeAdminUsersFilter();
var filterByAnotherCriteria = new AnotherCriteriaFilter();

excludeAdminUsersFilter.Apply(users);
filterByAnotherCriteria.Apply(users); 

Ve evet, filtrelemeyi başka bir yönteme taşıyarak bunu güzelleştirebilirsiniz, böylece satırlarda bir şey elde edersiniz:

var users = Dependency.Resolve<IGetUsersQuery>().GetAll();
Filter(users);

Filter(users)Yukarıdaki filtrelerin nerede çalıştırılacağı.

Daha önce bununla tam olarak nerede karşılaştığımı hatırlamıyorum, ancak filtreleme hattı olarak adlandırıldığını düşünüyorum.


0

Önerilen yeni çözümün (nesnelerin kopyalanması) bir kalıp olup olmadığından emin değilim. Sorun, belirttiğiniz gibi, işlevlerin kötü adlandırılmasıdır.

Diyelim ki f () işlevi olarak karmaşık bir matematiksel işlem yazıyorum . Bunu belgelemek f () eşleştiren bir işlevdir NXNiçin N, ve arkasındaki algoritması. İşlev uygun olmayan bir şekilde adlandırılmışsa ve belgelenmemişse ve eşlik eden test senaryoları yoksa ve kodu anlamalısınız, bu durumda kod işe yaramaz.

Çözümünüz hakkında bazı gözlemler:

  • Uygulamalar farklı açılardan tasarlanmıştır: bir nesne sadece değerleri tutmak için kullanıldığında veya bileşen sınırları üzerinden geçtiğinde, nesnenin iç kısımlarını nasıl değiştirileceğinin ayrıntılarıyla doldurmak yerine harici olarak değiştirmek akıllıca olur.
  • Klonlama nesneleri, bellek gereksinimlerinin şişmesine neden olur ve çoğu durumda, uyumsuz durumlardaki ( sonra , ancak aslında Xolmuştur) eşdeğer nesnelerin varlığına ve muhtemelen geçici olarak tutarsızlığa yol açar .Yf()XY

Ele almaya çalıştığınız sorun geçerli bir sorun; bununla birlikte, muazzam aşırı mühendislik yapıldığında bile, sorun çözülmez, çözülür.


2
Eğer gözleminizi OP'nin sorusuyla ilişkilendirirseniz, bu daha iyi bir cevap olacaktır. Olduğu gibi, bu bir cevaptan çok bir yorumdur.
Robert Harvey

1
@RobertHarvey +1, iyi gözlem, katılıyorum, düzenleyecek.
CMR
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.