Kod ve Karma Nesneleri Temizle ve Kıskançlık


10

Bu yüzden son zamanlarda benim kod bazı büyük refactorings yaptı. Yapmaya çalıştığım ana şeylerden biri, sınıflarımı veri nesnelerine ve çalışan nesnelere ayırmaktı. Bu, diğer şeylerin yanı sıra, Temiz Kod'un bu bölümünden ilham aldı :

Melezler

Bu karışıklık bazen yarı nesne ve yarı veri yapısı olan talihsiz hibrit veri yapılarına yol açar. Önemli şeyler yapan işlevleri vardır ve ayrıca tüm değişkenler ve amaçlar için özel değişkenleri herkese açık hale getiren ve bu değişkenleri prosedürel bir programın kullanacağı şekilde kullanmaya teşvik eden genel değişkenler veya kamu erişimcileri ve mutasyonları vardır. veri yapısı.

Bu melezler yeni işlevler eklemeyi zorlaştırır, aynı zamanda yeni veri yapıları eklemeyi zorlaştırır. Onlar her iki dünyanın da en kötüsü. Onları oluşturmaktan kaçının. Yazarlar, işlevlerden veya türlerden korunmaya ihtiyaç duyup duymadıklarından emin değil veya daha da kötüsü olan karışık bir tasarımın göstergesidir.

Son zamanlarda ( Ziyaretçi Desen uygulamak için olur) benim işçi nesnelerin birine kod bakıyordu ve bunu gördüm:

@Override
public void visit(MarketTrade trade) {
    this.data.handleTrade(trade);
    updateRun(trade);
}

private void updateRun(MarketTrade newTrade) {
    if(this.data.getLastAggressor() != newTrade.getAggressor()) {
        this.data.setRunLength(0);
        this.data.setLastAggressor(newTrade.getAggressor());
    }
    this.data.setRunLength(this.data.getRunLength() + newTrade.getLots());
}

Hemen kendime dedim ki "kıskançlık! Bu mantık Datasınıfta olmalı - özellikle handleTradeyöntemde. handleTradeVe hep birlikte updateRunolmalı ". Ama sonra "veri sınıfı sadece bir veri yapısı, bunu yapmaya başlarsam, o zaman bir Hibrit Nesne olacak!" Diye düşündüm .public

Daha iyi olan nedir ve neden? Hangisini yapacağınıza nasıl karar veriyorsunuz?


2
'Verilerin' neden bir veri yapısı olması gerekiyor? Açık bir davranışı var. Bu nedenle, tüm alıcıları ve ayarlayıcıları kapatın, böylece hiçbir nesne dahili durumu değiştiremez.
Cormac Mulhall

Yanıtlar:


9

Alıntıladığınız metnin iyi bir tavsiyesi vardır, ancak "veri yapılarını" "kayıtlar" ile değiştireceğim. Kayıtlar sadece aptal veri toplamalarıdır. Değişebilir olmalarına rağmen (ve böylece fonksiyonel bir programlama zihniyetinde durum bilgisi olan), herhangi bir iç durumları yoktur, korunması gereken değişmezler yoktur. Bir kayda kullanımını kolaylaştırmak için işlemler eklemek tamamen geçerlidir.

Örneğin, bir 3B vektörün aptal bir kayıt olduğunu iddia edebiliriz. Ancak bu, addvektör eklemeyi daha kolay hale getiren gibi bir yöntem eklememizi engellememelidir . Davranış eklemek, (çok aptal olmayan) bir kaydı melez haline getirmez.

Bu satır, bir nesnenin genel arabirimi kapsüllemeyi kırmamıza izin verdiğinde kesilir: Doğrudan erişebileceğimiz, böylece nesneyi geçersiz bir duruma getirebileceğimiz bazı iç öğeler vardır. Bana öyle Datageliyor ki , durumu vardır ve geçersiz bir duruma getirilebilir:

  • Bir işlem yaptıktan sonra son saldırgan güncellenmeyebilir.
  • Son saldırgan, yeni işlem yapılmasa bile güncellenebilir.
  • Saldırgan güncellendiğinde bile çalışma uzunluğu eski değerini koruyabilir.
  • vb.

Verileriniz için herhangi bir durum geçerliyse, kodunuzla ilgili her şey yolundadır ve devam edebilirsiniz. Aksi takdirde: DataSınıf kendi veri tutarlılığından sorumludur. Bir ticareti yönetmek her zaman saldırganın güncellenmesini içeriyorsa, bu davranışın Datasınıfın bir parçası olması gerekir . Saldırganı değiştirmek çalışma uzunluğunu sıfıra ayarlamayı içeriyorsa, bu davranışın Datasınıfın bir parçası olması gerekir . Dataasla aptalca bir rekor değildi. Genel ayarlayıcılar ekleyerek zaten bir melez yaptınız.

Bu katı sorumlulukları rahatlatmayı düşünebileceğiniz bir senaryo vardır : DataProjenize özelse ve bu nedenle herhangi bir genel arayüzün parçası değilse, yine de sınıfın doğru kullanımını sağlayabilirsiniz. Ancak bu Data, kodları merkezi bir yerde toplamaktansa kod boyunca tutarlılığı koruma sorumluluğunu ortaya koymaktadır.

Son zamanlarda kapsüllemeyle ilgili bir cevap yazdım , bu da kapsüllemenin ne olduğu ve nasıl sağlayabileceğiniz konusunda daha derinlere iniyor.


5

Aslında handleTrade()ve updateRun()her zaman birlikte ortaya (ve ikinci yöntem, ziyaretçi aslında ve veri nesnesinin çeşitli başka yöntemler aramaları) kokuyor zamansal bağlantı . Bu, yöntemleri belirli bir sırayla çağırmanız gerektiği anlamına gelir ve yöntemlerin sıra dışı çağrılmasının en kötü şeyleri bozacağını veya en iyi ihtimalle anlamlı bir sonuç sağlayamayacağını tahmin ediyorum. İyi değil.

Genellikle bu bağımlılığın yeniden düzenlenmesi için doğru yol, her yöntemin bir sonraki yönteme beslenebilen veya doğrudan üzerinde işlem yapılabilen bir sonuç döndürmesidir.

Eski kod:

MyObject x = ...;
x.actionOne();
x.actionTwo();
String result = x.actionThree();

Yeni kod:

MyObject x = ...;
OneResult r1 = x.actionOne();
TwoResult r2 = r1.actionTwo();
String result = r2.actionThree();

Bunun birkaç avantajı vardır:

  • Ayrı endişeleri ayrı nesnelere ( SRP ) taşır .
  • Geçici eşleşmeyi kaldırır: yöntemleri arızalı olarak çağırmak imkansızdır ve yöntem imzaları bunları nasıl arayacağınızla ilgili örtük belgeler sağlar. Hiç belgelere baktınız, istediğiniz nesneyi gördünüz ve geriye çalıştınız mı? Z nesnesini istiyorum. Ama bir Z elde etmek için Y'ye ihtiyacım var. Y elde etmek için X'e ihtiyacım var. Aha! Bir X almak için gerekli olan bir W'm var. Hepsini birlikte zincirleyin ve W'niz artık bir Z almak için kullanılabilir.
  • Bunun gibi nesneleri bölmenin onları değişmez hale getirme olasılığı daha yüksektir, bu da bu sorunun kapsamı dışında çok sayıda avantaja sahiptir. Hızlı paket, değişmez nesnelerin daha güvenli bir koda yol açma eğilimindedir.

Bu iki yöntem çağrısı arasında geçici bir eşleşme yoktur. Sıralamalarını değiştirdiğinizde davranış değişmez.
durron597

1
Başlangıçta, soruyu okurken sıralı / zamansal eşleşmeyi de düşündüm, ancak updateRunyöntemin özel olduğunu fark ettim . Sıralı kuplajdan kaçınmak iyi bir tavsiye, ancak uygulama ayrıntıları için değil, yalnızca API tasarımı / genel arayüzler için geçerlidir. Asıl soru updateRun, ziyaretçi veya veri sınıfında olması gerektiği gibi görünüyor ve bu cevabın bu sorunu nasıl ele aldığını göremiyorum.
amon

Görünürlüğü updateRunönemsizdir, önemli olan uygulaması this.datasoruda bulunmayan ve ziyaretçi nesnesi tarafından manipüle edilen nesnedir.

Bir şey varsa, bu ziyaretçinin sadece bir grup pasör çağırması ve aslında hiçbir şey işlememesi, zamansal kuplajın olmamasının bir nedenidir. Seterlerin hangi sırayla çağrıldıkları önemli değil.

0

Benim bakış açımdan bir sınıf "durum (üye değişkenler) değerleri ve davranış uygulamaları (üye işlevleri, yöntemler)" içermelidir.

"Talihsiz melez veri yapıları", sınıf durumu üye değişkenlerini (veya alıcılarını / ayarlayıcılarını) herkese açık olmamak üzere herkese açık hale getirirseniz ortaya çıkar.

Bu yüzden veri veri nesneleri ve çalışan nesneler için ayrı sınıflara ihtiyacım yok.

Durum-üye-değişkenleri herkese açık kalmamalı (veritabanı katmanınız herkese açık olmayan üye değişkenleri işleyebilmelidir)

Özellik kıskançlığı, başka bir sınıfın yöntemlerini aşırı kullanan bir sınıftır. Bkz. Code_smell . Yöntemleri ve durumu olan bir sınıfa sahip olmak bunu ortadan kaldıracaktır.

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.