Kapsülleme ve yöntem karşılaştırması


17

"Tek erişim noktası" yöntemlerine karşı yöntem zincirleme klasik OOP sorunu var:

main.getA().getB().getC().transmogrify(x, y)

vs

main.getA().transmogrifyMyC(x, y)

Birincisi, her sınıfın sadece daha küçük bir operasyon kümesinden sorumlu olması ve her şeyi çok daha modüler hale getirme avantajına sahip gibi görünüyor - C'ye bir yöntem eklemek A, B veya C'de onu ortaya çıkarmak için herhangi bir çaba gerektirmez.

Olumsuz, elbette, ikinci kodun çözdüğü zayıf kapsülleme . Şimdi A, içinden geçen her yöntemin kontrolüne sahiptir ve eğer isterse kendi alanlarına devredebilir.

Tek bir çözüm olmadığını ve elbette bağlama bağlı olduğunun farkındayım, ancak iki stil arasındaki diğer önemli farklılıklar ve hangi koşullar altında ikisini de tercih etmeliyim - çünkü şu anda, denediğimde bazı kod tasarlamak için, sadece bir şekilde karar vermek için argümanlar kullanmıyorum gibi hissediyorum.

Yanıtlar:


25

Demeter Yasası'nın bu konuda önemli bir rehber olduğunu düşünüyorum (her zamanki gibi vaka bazında ölçülmesi gereken avantajları ve dezavantajları ile).

Demeter Yasasını takip etmenin avantajı, ortaya çıkan yazılımın daha sürdürülebilir ve uyarlanabilir olma eğilimindedir. Nesneler, diğer nesnelerin iç yapısına daha az bağımlı olduğundan, nesne kapları, arayanlarını yeniden işlemeden değiştirilebilir.

Demeter Yasası'nın bir dezavantajı, yöntem çağrılarını bileşenlere yaymak için bazen çok sayıda küçük "sarıcı" yöntem yazmayı gerektirmesidir. Ayrıca, bir sınıfın arabirimi, içerilen sınıflar için yöntemler barındırdığından hantal hale gelebilir, bu da uyumlu bir arabirime sahip olmayan bir sınıfla sonuçlanır. Ancak bu kötü OO tasarımının bir işareti olabilir.


Bu yasayı unuttum, hatırlattığın için teşekkürler. Ama burada sorduğum şey, esas olarak avantajların ve dezavantajların ne olduğu veya daha doğru bir şekilde bir stili diğerine nasıl kullanmaya karar vereceğim.
Meşe

@ Meşe, avantajları ve dezavantajları açıklayan alıntılar ekledim.
Péter Török

10

Genel olarak yöntem zincirini olabildiğince sınırlı tutmaya çalışıyorum (Demeter Yasası'na dayanarak )

Yaptığım tek istisna akıcı arayüzler / dahili DSL tarzı programlama için.

Martin Fowler, Etki Alanına Özgü Diller'de aynı ayrımı yapar, ancak aşağıdakileri içeren ihlal komut sorgusu ayırma nedenleriyle :

her yöntemin bir eylem gerçekleştiren bir komut olması ya da arayana veri döndüren ancak her ikisini birden vermeyen bir sorgu olması gerekir.

Fowler, 70. sayfadaki kitabında şöyle diyor:

Komut sorgusu ayırma, programlamada son derece değerli bir prensiptir ve ekipleri bunu kullanmaya teşvik ediyorum. Dahili DSL'lerde Yöntem Zinciri kullanmanın sonuçlarından biri, genellikle bu prensibi ihlal etmesidir - her yöntem durumu değiştirir ancak zincire devam etmek için bir nesne döndürür. Komut sorgusu ayrımını takip etmeyen insanları küçümseyen birçok desibel kullandım ve tekrar yapacağım. Ancak akıcı arayüzler farklı bir dizi kurala uyar, bu yüzden burada izin vermekten mutluluk duyuyorum.


3

Bence soru, uygun bir soyutlama kullanıp kullanmadığınızdır.

İlk durumda,

interface IHasGetA {
    IHasGetB getA();
}

interface IHasGetB {
    IHasGetC getB();
}

interface IHasGetC {
    ITransmogrifyable getC();
}

interface ITransmogrifyable {
    void transmogrify(x,y);
}

Main türündedir IHasGetA. Soru şudur: Bu soyutlama uygun mudur? Cevap önemsiz değil. Ve bu durumda biraz uzakta görünüyor, ama yine de teorik bir örnek. Ancak farklı bir örnek oluşturmak için:

main.getA(v).getB(w).getC(x).transmogrify(y, z);

Genellikle daha iyidir

main.superTransmogrify(v, w, x, y, z);

İkinci örnekte hem için thisve maintürlerine bağlıdır v, w, x, yve z. Ayrıca, her yöntem bildiriminde yarım düzine argüman varsa kod gerçekten daha iyi görünmüyor.

Bir servis bulucu aslında ilk yaklaşımı gerektirir. Oluşturduğu örneğe servis bulucu üzerinden erişmek istemezsiniz.

Dolayısıyla, bir nesne aracılığıyla "uzanmak" çok fazla bağımlılık yaratabilir, gerçek sınıfların özelliklerine dayanırsa, daha fazla bağımlılık yaratabilir.
Ancak bir soyutlama yaratmak, tamamen bir nesnenin sağlanmasıyla ilgili tamamen farklı bir şeydir.

Örneğin, şunlara sahip olabilirsiniz:

class Main implements IHasGetA, IHasGetA, IHasGetA, ITransmogrifyable {
    IHasGetB getA() { return this; }
    IHasGetC getB() { return this; }
    ITransmogrifyable getC() { return this; }
    void transmogrify(x,y) {
        return x + y;//yeah!
    }
}

mainBir örneği nerede Main. Sınıf bilmek onun yerine mainbağımlılığı azaltırsa , bağlantının aslında oldukça düşük olduğunu göreceksiniz. Çağıran kod aslında asıl nesne üzerindeki son yöntemi çağırdığını bile bilmiyor, ki bu aslında ayrılma derecesini gösteriyor. Bir uygulamanın içlerine derinlemesine bakmak yerine kısa ve dik bir soyutlama yoluna ulaşırsınız. IHasGetAMain


Parametre sayısındaki büyük artış hakkında çok ilginç bir nokta.
Meşe

2

Demeter Hukuku Péter Török işaret @ gibi, "kompakt" formu öneriyor.

Buna ek olarak, kodunuzda açıkça belirttiğiniz daha fazla yöntem, sınıfınızın daha fazla sınıfa bağlı olması ve bakım sorunlarının artması. Örneğin, kompakt form iki sınıfa, daha uzun form ise dört sınıfa bağlıdır. Uzun form sadece Demeter Yasasına aykırı değildir; ayrıca, başvurulan dört yöntemden birini her değiştirdiğinizde (kompakt biçimde ikisinin aksine) kodunuzu değiştirmenizi sağlar.


Öte yandan, bu kanunun körü körüne uygulanması, yöntem sayısının Apatlayacağı anlamına gelir ve Ayine de birçok yöntem devredebilir. Yine de, bağımlılıklara katılıyorum - istemci kodundan gerekli bağımlılık miktarını önemli ölçüde azaltır.
Meşe

1
@Oak: Körü körüne bir şey yapmak asla iyi değildir. Artıları ve eksileri bakmak ve kanıta dayalı kararlar almak gerekir. Buna Demeter Yasası da dahildir.
CesarGon

2

Bu problemle kendim güreştim. Farklı nesnelere derinlemesine "ulaşmanın" dezavantajı, yeniden düzenleme yaptığınızda çok fazla bağımlılık olduğu için çok fazla kodu değiştirmek zorunda kalacağınızdır. Ayrıca, kodunuz biraz şişirilmiş ve okunması zorlaşır.

Öte yandan, yöntemleri basitçe "geçiren" sınıflara sahip olmak aynı zamanda birden fazla yöntemi birden fazla yerde beyan etmek zorunda kalmanın yükü anlamına da gelir.

Bunu hafifleten ve bazı durumlarda uygun olan bir çözüm, uygun sınıflardan veri / nesne kopyalayarak türden bir cephe nesnesi oluşturan bir fabrika sınıfına sahip olmaktır. Bu şekilde cephe nesnenize karşı kodlama yapabilirsiniz ve yeniden düzenleme yaptığınızda fabrikanın mantığını değiştirirsiniz.


1

Sıklıkla bir programın mantığının zincirleme yöntemlerle groklaştırılmasını daha kolay buluyorum. Bana customer.getLastInvoice().itemCount()göre beynime daha iyi uyuyor customer.countLastInvoiceItems().

Bunun ekstra bağlantıya sahip olmanın bakım baş ağrısına değip değmeyeceği size kalmış. (Küçük sınıflardaki küçük işlevleri de seviyorum, bu yüzden zincirleme eğilimindeyim. Doğru olduğunu söylemiyorum - sadece yaptığım şey bu.)


bu IMO müşterisi olmalıdır.NrLastInvoices veya customer.LastInvoice.NrItems. Bu zincir çok uzun değil, bu yüzden kombinasyonların miktarı biraz büyükse muhtemelen düzleşmeye değmez
Homde
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.