Raylar: Demeter Karışıklık Kanunu


13

Rails AntiPatterns adlı bir kitap okuyorum ve Demeter Yasasını çiğnemekten kaçınmak için delegasyondan bahsediyorlar. İşte onların en iyi örneği:

Denetleyicide böyle bir şey çağırmanın kötü olduğuna inanıyorlar (ve katılıyorum)

@street = @invoice.customer.address.street

Önerilen çözümleri aşağıdakileri yapmaktır:

class Customer

    has_one :address
    belongs_to :invoice

    def street
        address.street
    end
end

class Invoice

    has_one :customer

    def customer_street
        customer.street
    end
end

@street = @invoice.customer_street

Sadece bir nokta kullandığınız için burada Demeter Yasasını çiğnemediğinizi belirtiyorlar. Bunun yanlış olduğunu düşünüyorum, çünkü hala faturayı sokmak için adres üzerinden gitmek için müşteriye gidiyorsunuz. Öncelikle bu fikri okuduğum bir blog gönderisinden aldım:

http://www.dan-manges.com/blog/37

Blog gönderisinde asıl örnek

class Wallet
  attr_accessor :cash
end
class Customer
  has_one :wallet

  # attribute delegation
  def cash
    @wallet.cash
  end
end

class Paperboy
  def collect_money(customer, due_amount)
    if customer.cash < due_ammount
      raise InsufficientFundsError
    else
      customer.cash -= due_amount
      @collected_amount += due_amount
    end
  end
end

Blog yazısı, customer.cashbunun yerine sadece bir nokta olmasına rağmen customer.wallet.cash, bu kodun Demeter Yasasını hala ihlal ettiğini belirtiyor .

Şimdi Paperboy collect_money yönteminde iki noktamız yok, sadece "customer.cash" de bir tane var. Bu delegasyon sorunumuzu çözdü mü? Bir şey değil. Davranışlara bakarsak, bir gazeteci nakit almak için hala doğrudan müşterinin cüzdanına ulaşıyor.

DÜZENLE

Bunun hala bir ihlal olduğunu tamamen anlıyorum ve kabul ediyorum ve benim Walletiçin ödemeyi işleyen ve Customersınıf içinde bu yöntemi çağırmam gerektiğini söyleyen bir para çekme yöntemi oluşturmam gerekiyor . Bu sürece göre, ilk örneğim Demeter Yasasını hala ihlal ediyor, çünkü Invoicehala Customersokağa ulaşmak için doğrudan ulaşıyor .

Birisi karışıklığı gidermeme yardımcı olabilir. Son 2 gündür bu konunun batmasına izin vermeye çalışıyorum, ama yine de kafa karıştırıcı.



Blogdaki 2. örneğin (gazeteci) Demeter Yasasını ihlal ettiğini düşünmüyorum. Kötü bir tasarım olabilir (müşterinin nakit ödeme yapacağını varsayıyorsunuz), ancak bu bir Demeter ihlali yasası DEĞİLDİR. Tüm tasarım hatalarına bu Yasanın çiğnenmesi neden olmaz. Yazarın IMO'su karıştı.
Andres F.Eki

Yanıtlar:


24

İlk örnek yok değil Demeter'in hukuku ihlal. Evet, kodun durduğu gibi, bir varsayımla @invoice.customer_streetaynı değeri elde ettiğinin söylenmesi gerçekleşir @invoice.customer.address.street, ancak geçişin her adımında, döndürülen değere, sorulan nesne tarafından karar verilir - bu, "kağıtboy, müşterinin cüzdanı "," gazeteci müşteriden nakit ister ve müşteri nakitlerini cüzdanından alır " dır .

Derken @invoice.customer.address.street, müşteri ve adres iç yapıları hakkında bilgi varsayıyoruz - Bu kötü bir şey. Derken @invoice.customer_street, soruyorsun invoice, "hey, ben müşterinin sokak istiyorum, sen bunu elde nasıl karar ". Müşteri daha sonra adresine "Hey, caddenizi istiyorum, nasıl alacağınıza siz karar verin .

Demeter itme olduğunu değil 'Hiç bilemeyiz değerleri size; bunun yerine ise uzakta "senden grafikte nesnelerden' kendini değerlerini elde etmek için çok nesne grafik boyunca hareket etmesi değildir'.

Bu ince bir ayrım gibi görünebilir katılıyorum, ama bunu düşünün: Demeter uyumlu kodda, bir addressdeğişikliklerin iç temsili ne zaman kod değiştirmek gerekir ? Demeter uyumlu olmayan kodda ne olacak?


Bu tam olarak aradığım açıklama! Teşekkür ederim.
user2158382

Çok iyi bir açıklama. Bir sorum var: 1) Fatura nesnesi mutlaka bir müşteri nesnesini fatura istemciye iade etmek istiyorsa, bu, dahili olarak sahip olduğu aynı müşteri nesnesi olduğu anlamına gelmez. Bu, istemciye, içinde birden fazla değer içeren güzel bir paketlenmiş veri kümesi döndürmek amacıyla, oluşturulan bir nesne olabilir. Sunduğunuz mantığı kullanarak, faturanın birden fazla veriyi temsil eden bir alana sahip olamayacağını söylüyorsunuz. Yoksa bir şey mi kaçırıyorum.
zumalifeguard

2

İlk örnek ve ikincisi aslında aynı değil. İlk olarak "tek nokta" nın genel kuralları hakkında konuşurken, ikincisi OO tasarımındaki diğer şeyler hakkında, özellikle " Anlat, Sorma "

Delegasyon, Demeter Yasası ihlallerinden kaçınmak için etkili bir tekniktir, ancak nitelikler için değil, sadece davranış için. - İkinci örnekten Dan'ın blogu

Yine, " sadece davranış için, nitelikler için değil "

Nitelikleri sorarsanız, sormanız gerekir . "Hey, adamım, cebinizde ne kadar paran var? Göster bana, bunu ödeyip ödeyemeyeceğinizi değerlendireceğim." Bu yanlış, hiçbir alışveriş memuru böyle davranmayacak. Bunun yerine, "Lütfen öde" derler

customer.pay(due_amount)

Ödemesi gerekip gerekmediğini ve ödeyip ödeyemeyeceğini değerlendirmek müşterinin kendi görevi olacaktır. Ve katip görevi müşteriye ödeme yapmasını söyledikten sonra bitirilir.

Peki, ikinci örnek birincinin yanlış olduğunu kanıtlıyor mu?

Bence. Hayır , şu sürece:

1. Bunu kendi kendini kısıtlama ile yaparsın.

@invoiceTemsilci tarafından müşterinin tüm özelliklerine erişebilmenize rağmen , normal durumlarda buna nadiren ihtiyacınız vardır.

Rails uygulamasında fatura gösteren bir sayfayı düşünün. Üstte müşterinin ayrıntılarını gösteren bir bölüm olacaktır. Fatura şablonunda, böyle kodlar mısınız?

#customer-info
  = @invoice.customer_name
  = @invoice.customer_address
  ....

Bu yanlış ve verimsiz. Daha iyi bir yaklaşım

#customer-info
  = render partial: 'invoice_header_customer', 
           locals: {customer: @invoice.customer}

Sonra müşteriye ait tüm özellikleri işlemek için kısmi müşteri bırakın.

Yani genellikle buna ihtiyacınız yok. Ancak, tüm son faturaları gösteren bir liste sayfanız olabilir, her birinde limüşterinin adını gösteren bir brifing alanı vardır . Bu durumda, göstermek için müşterinin özelliğine ihtiyacınız vardır ve şablonu şu şekilde kodlamak tamamen yasaldır

= @invoice.customer_name

2. Bu yöntem çağrısına bağlı olarak başka bir işlem yapılmaz.

Yukarıdaki liste sayfası durumunda, fatura müşterinin ad niteliğini sordu, ancak asıl amacı " bana adınızı göstermektir ", bu nedenle temelde yine de bir davranış, ancak nitelik değil . Adınız "Mike" ise, bu özelliğe dayalı başka bir değerlendirme ve işlem yoktur. Seni seveceğim ve size 30 gün daha fazla kredi vereceğim. Hayır, faturada sadece "bana ismini göster" deyin, artık yok. Bu, örnek 2'deki "Söyle Sorma" kuralına göre tamamen kabul edilebilir.


0

İkinci makalede daha fazlasını okuyun ve bence fikir daha net olacak. Fikir, sadece müşterinin davanın nerede saklandığını ödeme ve tamamen gizleme yeteneği sunmasını sağlar . Bir alan mı, bir cüzdanın üyesi mi, yoksa başka bir şey mi? Arayan kişi bilmiyor, bilmesine gerek yok ve uygulama detayı değişirse değişmiyor.

class Wallet
  attr_accessor :cash
  def withdraw(amount)
     raise InsufficientFundsError if amount > cash
     cash -= amount
     amount
  end
end
class Customer
  has_one :wallet
  # behavior delegation
  def pay(amount)
    @wallet.withdraw(amount)
  end
end
class Paperboy
  def collect_money(customer, due_amount)
    @collected_amount += customer.pay(due_amount)
  end
end

Bu yüzden ikinci referansınızın daha faydalı bir öneri verdiğini düşünüyorum.

"Tek nokta" tek fikri, kısmen derin bir ayrıntıyı gizlediği, ancak yine de ayrı bileşenler arasındaki bağlantıyı arttırdığı için kısmi bir başarıdır.


Üzgünüm belki net değildim, ama ikinci örneği mükemmel anlıyorum ve yayınladığınız soyutlamayı yapmanız gerektiğini anlıyorum, ama anlamadığım şey benim ilk örneğim. Blog gönderisine göre, ilk
örneğim

0

Dan, bu makaledeki örneğini aldı: Paperboy, The Wallet ve Demeter Yasası

Demeter Yasası Bir nesne yöntemi, yalnızca aşağıdaki nesne türlerinin yöntemlerini çağırmalıdır:

  1. kendisi
  2. parametreleri
  3. oluşturduğu / oluşturduğu herhangi bir nesne
  4. doğrudan bileşen nesneleri

Demeter Yasası Ne Zaman ve Nasıl Uygulanır?

Yani şimdi kanunu ve faydalarını iyi anlıyorsunuz, ancak mevcut koddaki, onu uygulayabileceğimiz yerlerin nasıl tanımlanacağını henüz tartışmadık (ve aynı derecede önemli, nerede uygulanmayacağı ...)

  1. Zincirli 'get' İfadeleri - Demeter Yasasını uygulamak için ilk, en belirgin yer, tekrarlanan get() ifadelere sahip kod yerleridir ,

    value = object.getX().getY().getTheValue();

    sanki bu örnek için kanonik kişimiz polis tarafından ele geçirildiğinde, şunu görebiliriz:

    license = person.getWallet().getDriversLicense();

  2. çok sayıda 'geçici' nesne - Kod benziyorsa yukarıdaki lisans örneği daha iyi olmaz,

    Wallet tempWallet = person.getWallet(); license = tempWallet.getDriversLicense();

    eşdeğerdir, ancak tespit edilmesi daha zordur.

  3. Birçok Sınıfı İçe Aktarma - Üzerinde çalıştığım Java projesinde, yalnızca gerçekte kullandığımız sınıfları içe aktarma kuralımız var; asla böyle bir şey görmezsin

    import java.awt.*;

    kaynak kodumuzda. Bu kural uygulandığında, aynı paketten gelen bir düzine kadar ithalat ifadesini görmek nadir değildir. Kodunuzda bu oluyorsa, gizlenmiş ihlal örneklerine bakmak için iyi bir yer olabilir. İçe aktarmanız gerekiyorsa, ona bağlanırsınız. Değişirse, yapmanız gerekebilir. Sınıfları açıkça içe aktararak, sınıflarınızın gerçekte nasıl birleştiğini görmeye başlayacaksınız.

Örneğinizin Ruby dilinde olduğunu anlıyorum, ancak bu tüm OOP dilleri için geçerli olmalıdı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.