“Söyleme, Sorma” nın nasıl iyi bir OO olarak kabul edildiğine ilişkin açıklama


49

Bu blog yayını Hacker News’de birkaç oy aldı. C ++ 'dan gelince, bu örneklerin çoğu benim öğretildiklerime aykırı görünüyor.

Örnek 2 gibi:

Kötü:

def check_for_overheating(system_monitor)
  if system_monitor.temperature > 100
    system_monitor.sound_alarms
  end
end

iyi karşı:

system_monitor.check_for_overheating

class SystemMonitor
  def check_for_overheating
    if temperature > 100
      sound_alarms
    end
  end
end

C ++ 'da tavsiye, kapsüllemeyi arttırdıkça, üye işlevler yerine ücretsiz işlevleri tercih etmeniz gerektiğidir. Bunların her ikisi de anlamsal olarak aynıdır, peki neden daha fazla duruma erişimi olan seçimi tercih etsin?

Örnek 4:

Kötü:

def street_name(user)
  if user.address
    user.address.street_name
  else
    'No street name on file'
  end
end

iyi karşı:

def street_name(user)
  user.address.street_name
end

class User
  def address
    @address || NullAddress.new
  end
end

class NullAddress
  def street_name
    'No street name on file'
  end
end

Neden Useralakasız bir hata dizesini biçimlendirmek sorumluluğu var ? Ya 'No street name on file'sokağı yoksa izinden başka bir şey yapmak istersem ? Ya caddeye aynı şey denirse?


Birisi beni “Söyleme, Soru Sorma” avantajları ve gerekçeleriyle aydınlatabilir mi? Hangisinin daha iyi olduğunu aramıyorum, bunun yerine yazarın bakış açısını anlamaya çalışıyorum.


Kod örnekleri Ruby olabilir ve Python değil, bilmiyorum.
Pubby

2
İlk örnek gibi bir şeyin bir SRP ihlali olmadığını merak ediyorum.
stijn

1
Bunu okuyabilirsiniz: pragprog.com/articles/tell-dont-ask
Mik378

Yakut. @ kısaca. Python bloklarını tamamen boşluklarla kapatıyor.
Erik,

3
“C ++ 'da tavsiye, kapsüllemeyi arttırdıkça, üye işlevler yerine ücretsiz işlevleri tercih etmeniz gerektiğidir.” Bunu sana kim söyledi bilmiyorum, ama doğru değil. Kapsüllemeyi arttırmak için serbest işlevler kullanılabilir, ancak kapsüllemeyi arttırmaları gerekmez.
Rob K

Yanıtlar:


81

Nesneye durumu sormak ve ardından nesnenin dışında alınan kararlara dayanarak bu nesneye yöntem çağırmak, nesnenin artık sızan bir soyutlama olduğu anlamına gelir; Davranışlarının bir kısmı nesnenin dışında bulunuyor ve iç devlet (belki de gereksiz yere) dış dünyaya maruz kalıyor.

Nesnelere ne yapmalarını istediğinizi söylemeye çalışmalısınız; Onlara durumları hakkında soru sorma, bir karar verme ve sonra onlara ne yapacaklarını söyleme.

Sorun, arayan kişi olarak, daha sonra nesnenin durumunu değiştiren sonuçlanan nesnenin durumuna dayanarak kararlar almamanız gerektiğidir. Uyguladığınız mantık muhtemelen sizinki değil nesnenin sorumluluğundadır. Nesnenin dışında kararlar almanız onun kapsüllemesini ihlal ediyor.

Tabii ki söyleyebiliriz ki bu çok açık. Asla böyle kod yazmam. Yine de, başvurulan bir nesneyi incelemekten ve sonuçlara göre farklı yöntemleri çağırmaktan kaçınmak çok kolaydır. Ancak, bunu yapmanın en iyi yolu bu olmayabilir. Nesneye ne istediğini söyle. Nasıl yapılacağını çözmesine izin verin. Prosedürel yerine bildirimsel olarak düşünün!

Sorumluluklarına göre sınıflar tasarlamaya başlarsanız, bu tuzaktan uzak durmak daha kolaydır; daha sonra, nesnenin durumu hakkında sizi bilgilendiren sorguların aksine, sınıfın uygulayabileceği komutları belirtmek için doğal olarak ilerleyebilirsiniz.

http://pragprog.com/articles/tell-dont-ask


4
Örnek metin açıkça iyi bir uygulama olan birçok şeye izin vermez .
DeadMG

13
@DeadMG yalnızca söylediklerinizi takip edenleri takip eder, sitedeki adında "pragmatik" ve ana kitaplarında açıkça belirtilen site yazarlarının temel düşüncelerini kör bir şekilde görmezden gelenlere yapar : " En iyi çözüm diye bir şey yoktur." ... "
gnat

2
Asla kitabı okuma. Ben de istemezdim. Ben sadece tamamen adil olan örnek metni okudum.
DeadMG

3
@DeadMG endişelenme. Şimdi istediğiniz bağlamda ( "a en iyi çözüm olarak böyle bir şey ...") içinde (bu konuda pragprog gelen ve diğer bir) bu örneği koyar temel noktayı biliyoruz, bu kitap okumak değil Tamam
tatarcık

1
Söylemeyin, Sorma'nın sizin için bağlam olmadan yazılmasının beklendiğinden hala emin değilim ama bu gerçekten iyi bir OOP tavsiyesi.
Erik,

16

Genel olarak, eser, kendiniz hakkında bir sebep olabilirseniz , üye devleti başkalarının sebeplerine maruz bırakmamanızı önerir .

Ancak, açıkça ifade edilmeyen şey , mantığın belirli bir sınıfın sorumluluğunun üzerinde olduğu durumlarda bu kanunun çok açık sınırlara düşmesidir . Örneğin, işi belirli bir değere sahip olan veya bazı değerler sağlayan, özellikle de genel olanları veya sınıfın genişletilmesi gereken davranışlar sağladığı her sınıf.

Örneğin, sistem temperaturebir sorgu olarak veriyorsa , o zaman yarın, müşteri check_for_underheatingdeğiştirmeden bunu yapabilir SystemMonitor. Bu kendini SystemMonitoruygulayan durum değil check_for_overheating. Bu nedenle, SystemMonitorsıcaklığı çok yüksekken işi bir alarm SystemMonitorverecek olan bir sınıf bunu takip eder, ancak işi başka bir kod parçasının sıcaklığı okumasını, yani TurboBoost veya bunun gibi bir şeyi kontrol edebilmesi için izin veren bir sınıftır. , yapmamalı.

Ayrıca, ikinci örneğin anlamsız olarak Null Object Anti-pattern kullandığını unutmayın.


19
“Boş nesne”, anti-kalıp dediğim şey değil, bu yüzden neden bunun ne olduğunu merak ediyorum.
Konrad Rudolph

4
Hiç kimsenin "Hiçbir şey yapmaz" olarak belirtilen hiçbir yöntemi olmadığından emin olun. Bu onları çok anlamsız kılıyor. Bu, Null Object'i uygulayan herhangi bir nesnenin en azından LSP'yi kırdığı ve kendisini aslında aslında gerçekleştirmediği işlemler olarak tanımladığı anlamına gelir. Kullanıcı geri bir değer bekliyor . Programlarının doğruluğu buna bağlı. Sadece olmadığı zamanlarda bir değer olduğunu iddia ederek daha fazla sorun getirin. Hiç sessizce başarısız yöntemlerde hata ayıklama denediniz mi? Bu imkansızdır ve hiç kimsenin bundan acı çekmesi gerekmemelidir.
DeadMG

4
Bunun tamamen problem alanına bağlı olduğunu iddia ediyorum.
Konrad Rudolph

5
@DeadMG Yukarıdaki örnek null Nesne desen kötü kullanımı olduğunu kabul, ama orada olan bir hak kullanmaktan için. Birkaç kez null kontrolünü veya sisteme nüfuz etmeyi 'null' yapmaktan kaçınmak için bazı arayüzlerin veya başkalarının 'no-op' uygulamasını kullandım.
Max,

6
"Müşteri check_for_underheatingdeğişmek zorunda kalmadan yapabilir" ile amacınızı gördüğümden emin değilim SystemMonitor. Müşterinin SystemMonitorbu noktada farkı nedir? Artık izleme mantığınızı birden fazla sınıf arasında dağıtmıyor musunuz? Ayrıca, alarm fonksiyonlarını kendine saklarken diğer sınıflara sensör bilgisi sağlayan bir monitör sınıfında da sorun görmüyorum. Takviye denetleyicisi, sıcaklık çok yükselirse alarm vermekten endişe duymadan güçlendirme kontrolü yapmalıdır.
TMN

9

Aşırı ısınma örneğinizle ilgili asıl sorun, aşırı ısınma olarak nitelendirilen kuralların farklı sistemler için kolayca değiştirilememesidir. Sistem A'nın elinizde gibi olduğunu düşünün (sıcaklık> 100 aşırı ısınır) ancak Sistem B daha hassastır (sıcaklık> 93 aşırı ısınır). Sistem türünü kontrol etmek için kontrol fonksiyonunuzu değiştiriyor ve ardından doğru değeri uyguluyor musunuz?

if (system is a System_A and system_monitor.temp >100)
  system_monitor.sound_alarms
else if (system is a System_B and system_monitor.temp > 93)
  system_monitor.sound_alarms
end

Veya her ısıtma sistemi kendi ısıtma kapasitesini belirliyor mu?

DÜZENLE:

system.check_for_overheating

class SystemA : System
  def check_for_overheating
    if temperature > 100
      sound_alarms
    end
  end
end

class SystemB : System
  def check_for_overheating
    if temperature > 93
      sound_alarms
    end
  end
end

Eski yöntem, daha fazla sistemle uğraşmaya başladığınızda denetim işlevinizi çirkin hale getirir. İkincisi, zaman geçtikçe kontrol işlevinin kararlı olmasını sağlar.


1
Neden her sistem monitöre kayıt yaptırmıyor? Kayıt sırasında aşırı ısınmanın ne zaman olacağını gösterebilirler.
Martin York

@LokiAstari - Yapabilirsin, fakat sonra neme veya atmosferik basınca duyarlı yeni bir sisteme girebilirsin. İlke, neyin değişebileceğini soyutlamaktır - bu durumda aşırı ısınmaya yatkınlıktır
Matthew Flynn

1
İşte bu yüzden tam bir anlatım modeline sahip olmalısınız. Sisteme mevcut koşulları anlatır ve normal çalışma koşullarının dışında olup olmadığını size bildirir. Böylece SystemMoniter'ı hiçbir zaman değiştirmeniz gerekmez. Bu senin için enkapsülasyon.
Martin York

@LokiAstari - Sanırım burada çapraz amaçlardan bahsediyoruz - Gerçekten farklı monitörler yerine farklı sistemler yaratmaya bakıyordum. Mesele şu ki, sistem bazı dış denetleyici işlevlerinin aksine, alarm verecek bir durumda olduğunda bilmelidir. SystemA'nın kriterleri olmalı, SystemB'nin kendine ait olması gerekir. Kontrolör sistemin normal olup olmadığını sorabilir (düzenli aralıklarla).
Matthew Flynn,

6

Öncelikle, örneklerin "kötü" ve "iyi" olarak nitelendirilmesinde istisna olmam gerektiğini düşünüyorum. Makale "Çok iyi değil" ve "Daha iyi" terimlerini kullanmaktadır, bence bu terimler bir sebepten seçilmiştir: şartlar ve koşullara bağlı olarak "çok iyi değil" yaklaşımı uygun olabilir, ya da gerçekten tek çözüm olabilir.

Bir seçim yapıldığında, yalnızca sınıf içindeki sınıfa dayanan herhangi bir işlevselliği dışarıda değil de sınıfta kullanmayı tercih etmelisiniz - bunun nedeni kapsülleme ve zaman içinde sınıfı geliştirmeyi kolaylaştırmasıdır. Ayrıca sınıf, yeteneklerini bir sürü ücretsiz işlevden daha iyi bir şekilde yapar.

Bazen söylemelisiniz, çünkü karar sınıfın dışındaki bir şeye dayanır veya sadece sınıftaki kullanıcıların çoğunun yapmasını istemediğiniz bir şey olduğu için. Bazen anlatmak istersiniz, çünkü davranış, sınıf için karşı sezgiseldir ve sınıfın çoğu kullanıcısını karıştırmak istemezsiniz.

Örneğin, sokak adresinden bir hata mesajı döndüren şikayetçi olursunuz, öyle değildir, yaptığı şey varsayılan bir değer sağlamaktır. Ancak bazen varsayılan bir değer uygun değildir. Eğer bu Eyalet veya Şehir ise, bir satıcıya veya bir anketöre bir kayıt verirken bir varsayılan isteyebilirsiniz, böylece bilinmeyenlerin tümü belirli bir kişiye gider. Öte yandan, zarf yazdırıyorsanız, teslim edilemeyen harflerin üzerine kağıt israf etmenizi engelleyen bir istisna veya koruma tercih edebilirsiniz.

Bu yüzden, "Çok iyi değil" in gitmenin yolu olduğu durumlar olabilir, fakat genellikle "Daha iyi", iyi, daha iyi.


3

Veri / Nesne Karşıtı Simetri

Diğerlerinin de belirttiği gibi, Tell-Dont-Ask, özellikle sizden sonra nesne durumunu değiştirdiğiniz durumlar içindir (örneğin, bu sayfada başka bir yerde yayınlanan Pragprog metnine bakınız). Bu her zaman böyle değildir, örneğin 'user' nesnesi, user.adress için istendikten sonra değiştirilmez. Bu nedenle, Tell-Dont-Ask'ı uygulamak için uygun bir durum olup olmadığı tartışmalıdır.

Tell-Dont-Ask, içinde mantıklı bir şekilde olması gereken bir sınıfın mantığını çıkarmamakla yükümlüdür. Ancak, nesnelerle ilgilenen tüm mantıklar mutlaka bu nesnelerin mantığı değildir. Bu, Tell-Dont-Ask'ın ötesinde daha derin bir seviyede ipucu veriyor ve bunun hakkında kısa bir açıklama eklemek istiyorum.

Mimari tasarım olarak, gerçekten sadece özellikler için kaplar, hatta belki de değişken olmayan nesnelere sahip olmak ve ardından bu nesnelerin koleksiyonları üzerinde çeşitli işlevler yürütmek, bunları komutları göndermek yerine değerlendirmek, filtrelemek veya dönüştürmek gibi çeşitli işlevler kullanmak isteyebilirsiniz. daha fazla Tell-Dont-Ask alanı.

Sorununuz için daha uygun olan karar, kararlı verilere (bildirimsel nesneler) sahip olup olmayacağınıza, ancak işlev tarafında değişme / eklemeye karar vermenize bağlıdır. Veya sabit ve sınırlı bir dizi bu fonksiyona sahip olmayı planlıyorsanız, ancak nesne düzeyinde daha fazla akı beklemeniz gerekiyorsa, örneğin yeni türler ekleyerek. İlk durumda, ikinci nesne yöntemlerinde serbest işlevleri tercih edersiniz.

Bob Martin, “Temiz Kod” adlı kitabında buna “Veri / Nesne Karşıtı Simetri” (s.95ff) diyor, diğer topluluklar buna “ ifade sorunu ” olarak başvurabilir .


3

Bu paradigma bazen 'söyle, sorma' , anlamı nesneye ne yapacağını, durumunu sorma; ve bazen 'Sorma, söyleme' olarak , nesneden sizin için bir şey yapmasını isteyin, durumunun ne olduğunu söylemeyin. Her iki yöntem de en iyi uygulama etrafında aynıdır - bir nesnenin bir eylemi gerçekleştirmesi gereken yöntem, arayan nesnenin değil, o nesnenin endişesidir. Arayüzler devletlerini ifşa etmekten kaçınmalıdır (ör. Erişimciler veya kamu mülkleri yoluyla) ve bunun yerine uygulamasının opak olduğu 'yapma' yöntemlerini göstermelisiniz. Diğerleri bunu pragmatik programcının bağlantıları ile ele aldılar.

Bu kural, genellikle 'çift arkadaş' veya 'çift ok' kodundan kaçınmayla ilgili kurallarla ilgilidir, genellikle foo->getBar()->doSomething()kötü durumdaki 'Yalnızca yakın arkadaşlarla konuşun', bunun yerine foo->doSomething();, çubuğun işlevselliği etrafında bir sarmalayıcı çağrısı kullanın. basit olarak uygulanmış return bar->doSomething();- eğer fooyönetimden sorumlu ise bar, o zaman yapsın!


1

"Söylemeyin, sormayın" hakkındaki diğer iyi cevaplara ek olarak, bazıları yardımcı olabilecek özel örneklerinize ilişkin yorumlarda:

C ++ 'da tavsiye, kapsüllemeyi arttırdıkça, üye işlevler yerine ücretsiz işlevleri tercih etmeniz gerektiğidir. Bunların her ikisi de anlamsal olarak aynıdır, peki neden daha fazla duruma erişimi olan seçimi tercih etsin?

Bu seçimin daha fazla devlete erişimi yok. Her ikisi de işlerini yapmak için aynı miktarda devlet kullanıyor, ancak 'kötü' örnek, sınıf devletinin işini yapmak için kamuya açılmasını gerektiriyor. Ayrıca, bu sınıfın 'kötü' örnekteki davranışı, serbest işleve yayılır, bu da bulmayı zorlaştırır ve yeniden yapılandırmayı zorlaştırır.

İlişkili olmayan bir hata dizesini biçimlendirmek Kullanıcı'nın sorumluluğunda mı? Sokağı yoksa 'Dosyada sokak adı yok' yazısının dışında bir şey yapmak istersem ne olur? Ya caddeye aynı şey denirse?

Hem 'sokak adını al' hem de 'hata mesajı vermek' yapmak neden 'sokak_adı' sorumluluğudur? En azından 'iyi' versiyonda, her parçanın bir sorumluluğu vardır. Yine de, bu harika bir örnek değil.


2
Bu doğru değil. Aşırı ısınmanın kontrolünün sıcaklıkla ilgili tek akıllı şey olduğunu varsayıyorsunuz. Sınıfın bir dizi sıcaklık izleyiciden biri olması amaçlanıyorsa ve bir sistem sonuçlarına bağlı olarak farklı işlemler yapmalıysa, örneğin? Bu davranış , tek bir vakanın önceden tanımlanmış davranışıyla sınırlı olduğunda, o zaman kesin. Aksi takdirde, açıkça uygulanamaz.
DeadMG

Tabii ya da termostat ve alarmlar farklı sınıflarda mevcutsa (olması gerektiği gibi).
Telastyn

1
@DeadMG: Genel tavsiye, siz onlara erişene kadar işleri özel / korumalı yapmaktır. Bu özel örnek meh olsa da, bu standart uygulamayı tartışmaz.
Guvante,

Uygulamanın 'meh' olmasıyla ilgili bir makalesinde bir örnek bunu tartışmaktadır. Bu uygulama büyük yararları nedeniyle standart ise, o zaman neden uygun bir örnek bulmakta sorun var?
Stijn de Witt

1

Bu cevaplar çok iyi, ancak işte vurgulamak için başka bir örnek: not: çoğaltmanın önlenmesinin genellikle bir yoludur. Örneğin, diyelim ki şöyle bir kod içeren SEVERAL yerleriniz var:

Product product = productMgr.get(productUuid)
if (product.userUuid != currentUser.uuid) {
    throw BlahException("This product doesn't belong to this user")
}

Bu, şöyle bir şeyin olsa iyi olur:

Product product = productMgr.get(productUuid, currentUser)

Çünkü bu çoğaltma, arabiriminizin çoğu müşterisinin burada ve orada aynı mantığı tekrarlamak yerine yeni yöntemi kullanacağı anlamına geliyor. Temsilcinize istediğiniz işi yapmak yerine, ihtiyaç duyduğunuz bilgiyi kendiniz sormak yerine verirsiniz.


0

Yüksek seviye nesne yazarken bunun daha doğru olduğuna inanıyorum, ancak daha derin seviyeye inerken daha az doğru, örneğin sınıf kütüphanesini tüm sınıf tüketicilerini tatmin etmek için her bir yöntemi yazmak imkansız.

Örneğin # 2, aşırı basitleştirildiğini düşünüyorum. Bunu gerçekten uygulayacak olsaydık, SystemMonitor düşük seviyeli donanım erişimi ve aynı sınıfa gömülü yüksek seviye soyutlama için mantık koduna sahip olacaktı. Ne yazık ki, bunu iki sınıfa ayırmaya çalışıyorsak, "Söylemeyin, Sorma" nın kendisini ihlal ederiz.

Örnek 4 - aşağı yukarı aynıdır - kullanıcı arayüzü mantığını veri katmanına dahil eder. Şimdi, kullanıcının adres olmaması durumunda neyi görmek istediğini düzelteceksek, nesneyi veri katmanında düzeltmek zorundayız ve bu aynı nesneyi kullanan ancak null adres için farklı metin kullanması gereken iki proje varsa?

Her şey için "Söyleme, Sorma" uygularsak, çok faydalı olacağı konusunda hemfikirim - gerçek hayatta sormak yerine (ve kendim yapmayı) söylemekten çok mutlu olurum! Bununla birlikte, gerçek hayatta olduğu gibi, çözümün uygulanabilirliği de üst düzey sınıflarla sınırlı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.