it-swarm.asia

Başka bir işlevi çağıran işlevler, kötü tasarım seçimi?

Bir binayı temsil eden bir sınıf kurulumum var. Bu binanın sınırları olan bir kat planı vardır.

Ben kurulum yolu şöyle:

public struct Bounds {} // AABB bounding box stuff

//Floor contains bounds and mesh data to update textures etc
//internal since only building should have direct access to it no one else
internal class Floor {  
    private Bounds bounds; // private only floor has access to
}

//a building that has a floor (among other stats)
public class Building{ // the object that has a floor
    Floor floor;
}

Bu nesnelerin farklı şeyler yaptıkları için var olmak için kendi benzersiz nedenleri vardır. Ancak binaya yerel olarak bir puan almak istediğim bir durum var.

Bu durumda esasen şunu yapıyorum:

Building.GetLocalPoint(worldPoint);

Bu daha sonra şunları içerir:

public Vector3 GetLocalPoint(Vector3 worldPoint){    
    return floor.GetLocalPoint(worldPoint);
}

Hangi Floor nesnemde bu işleve yol açar:

internal Vector3 GetLocalPoint(Vector3 worldPoint){
    return bounds.GetLocalPoint(worldPoint);
}

Ve elbette bounds nesnesi aslında gerekli olan matematiği yapar.

Gördüğünüz gibi, bu fonksiyonlar aşağıdan başka bir fonksiyona geçtikleri için oldukça fazladır. Bu bana akıllı gelmiyor - kötü kod gibi beni popo bazı ısırmak için kod karmaşa ile satır aşağı gibi kokuyor.

Alternatif olarak aşağıdaki gibi benim kod yazmak ama ben yapmak istemiyorum halka daha fazla maruz kalmak zorunda:

building.floor.bounds.GetLocalPoint(worldPoint);

Bu aynı zamanda birçok iç içe nesneye gittiğinizde biraz aptallaşmaya başlar ve verilen işlevi almak için büyük tavşan deliklerine yol açar ve nerede olduğunu unutursunuz - bu da kötü kod tasarımı gibi kokar.

Tüm bunları tasarlamanın doğru yolu nedir?

53
WDUK

Asla unutma Demeter Yasası :

Demeter Yasası (LoD) veya en az bilgi ilkesiyazılım geliştirme, özellikle nesne yönelimli programlar için bir tasarım kılavuzudur. Genel haliyle, LoD belirli bir gevşek bağlantı örneğidir. Kılavuz 1987'nin sonuna doğru Northeastern Üniversitesi'nden Ian Holland tarafından önerildi ve aşağıdaki yolların her birinde kısa ve öz bir şekilde özetlenebilir: [1]

  • Her birim diğer birimler hakkında sadece sınırlı bilgiye sahip olmalıdır: sadece mevcut birim ile ilgili "yakından" birimler.
  • Her birim sadece arkadaşlarıyla konuşmalıdır; yabancılarla konuşma.
  • Sadece yakın arkadaşlarınızla konuşun.

Temel düşünce, verilen bir nesnenin, "bilgi gizleme" ilkesine uygun olarak, başka bir şeyin (alt bileşenleri dahil [)yapısı hakkında mümkün olduğu kadar az varsayması gerektiğidir.
Bir modülün sadece meşru amacı için gerekli bilgi ve kaynaklara sahip olduğunu belirten, en az imtiyaz ilkesinin sonucu olarak görülebilir.


building.floor.bounds.GetLocalPoint(worldPoint);

Bu kod LOD'u ihlal ediyor. Mevcut tüketicinizin bir şekilde bilmesi gerekir:

  • Binanın floor
  • Zeminde bounds
  • Sınırların bir GetLocalPoint yöntemi olduğunu

Ancak gerçekte, tüketiciniz binanın içindeki hiçbir şeyi değil, sadece building ile çalışmalıdır (alt bileşenleri doğrudan kullanmamalıdır).

Bu temel sınıfların herhangi biryapısı yapısal olarak değişirse, aslında değiştirdiğiniz sınıftan birkaç seviye yukarı çıkmış olsa bile, aniden bu tüketiciyi değiştirmeniz istenir.
Bu, sahip olduğunuz katmanların ayrılmasını ihlal etmeye başlar, çünkü bir değişiklik birden fazla katmanı etkiler (sadece doğrudan komşularından daha fazlası).

public Vector3 GetLocalPoint(Vector3 worldPoint){    
    return floor.GetLocalPoint(worldPoint);
}

Diyelim ki biri zeminsiz, ikinci tip bir bina tanıttınız. Gerçek dünyadan bir örnek düşünemiyorum, ancak genelleştirilmiş bir kullanım durumu göstermeye çalışıyorum, bu yüzden EtherealBuilding'in böyle bir durum olduğunu varsayalım.

Çünkü building.GetLocalPoint yöntemiyle, binanızın tüketicisi farkında olmadan çalışmalarını değiştirebilirsiniz, örn .:

public class EtherealBuilding : Building {
    public Vector3 GetLocalPoint(Vector3 worldPoint){    
        return universe.CenterPoint; // Just a random example
    }
}

Bunu anlamayı zorlaştıran şey, zemini olmayan bir bina için net bir kullanım durumu olmamasıdır. Alan adınızı bilmiyorum ve bunun gerçekleşip gerçekleşmeyeceği konusunda nasıl bir karar veremiyorum.

Ancak geliştirme yönergeleri, belirli bağlamsal uygulamalardan vazgeçen genelleştirilmiş yaklaşımlardır. Bağlamı değiştirirsek, örnek daha net olur:

// Violating LOD

bool isAlive = player.heart.IsBeating();

// But what if the player is a robot?

public class HumanPlayer : Player {
    public bool IsAlive() {
        return this.heart.IsBeating();
    }
}

public class RobotPlayer : Player {
    public bool IsAlive() {
        return this.IsSwitchedOn();
    }
}

// This code works for both human and robot players, and thus wouldn't need to be changed when new (sub)types of players are developed.

bool isAlive = player.IsAlive();

Bu, Player sınıfı (veya türetilmiş sınıflarından herhangi biri) üzerindeki yöntemin, şu anki uygulaması önemsizolsa bile bir amacı olduğunu kanıtlamaktadır.


Sidenote
Örnek uğruna, mirasa nasıl yaklaşılacağı gibi birkaç teğetsel tartışma yürüttüm. Bunlar cevabın odağı değil.

110
Flater

Zaman zaman burada ve orada bu tür yöntemleriniz varsa, bu tutarlı bir tasarımın sadece bir yan etkisi (veya ödeyecek olursanız, ödenmesi gereken fiyat) olabilir.

Onlardan çok varsa, bu tasarımın kendisinin sorunlu olduğunun bir işareti olduğunu düşünürdüm.

Örneğinizde, belki de olmamalıdır binanın dışından "binaya yerel olarak bir nokta almanın" bir yolu ve bunun yerine binanın yöntemleri daha yüksek bir soyutlama seviyesinde olmalı ve bununla çalışmalıdır. yalnızca dahili olarak gösterir.

21

Ünlü "Demeter Yasası" ne tür bir kod yazacağını belirleyen bir yasadır, ancak yararlı bir şeyi açıklamaz. Flater'ın cevabı gayet iyi, çünkü örnekler veriyor, ama ben buna "Demeter yasasını ihlal etme/uyma" demem. "Demeter Yasası" bulunduğunuz yerde uygulanırsa, lütfen yerel Demeter Polis Karakolunuzla temasa geçin, sorunları sizinle birlikte çözmekten mutluluk duyacaklardır.

Unutmayın, her zaman yazdığınız kodun ustasısınız ve bu nedenle, "yetki devri işlevleri" oluşturmak ve bunları yazmamak arasında, bu sizin kendi kararınıza bağlıdır. Keskin bir çizgi yoktur, bu yüzden keskin bir kural tanımlanamaz. Aksine, Flater'ın yaptığı gibi, bu tür işlevlerin yaratılmasının işe yaramaz olduğu ve bu tür işlevlerin yaratılmasının faydalı olduğu durumları bulabiliriz. ( Spoiler: Önceki durumda, düzeltme işlevi satır içine almaktır. İkinci durumda, düzeltme işlevi oluşturmaktır)

Temsilci seçme işlevinin tanımlanmasının yararsız olduğu örnekler arasında tek sebebin ne zaman olacağı:

  • Bir üye tarafından döndürülen bir nesnenin bir üyesine erişmek için, üye kapsüllenmesi gereken bir uygulama ayrıntısı olmadığında.
  • Arabirim üyeniz .NET'in yarı uygulama
  • Demeter uyumlu olmak

Yetkilendirme işlevi oluşturmanın yararlı olduğu örnekler şunlardır:

  • Tekrar tekrar tekrarlanan bir çağrı zincirini faktoring
  • Dil sizi zorladığında, ör. başka bir üyeye yetki vererek veya yalnızca başka bir işlevi çağırarak bir arabirim üyesi uygulamak
  • Aradığınız işlev, aynı düzeydeki diğer çağrılarla aynı kavramsal düzeyde değilse (örneğin, eklenti içgözlemiyle aynı düzeyde bir LoadAssembly çağrısı)
1
Laurent LA RIZZA

Bir an için Bina uygulamasını bildiğinizi unutun. Başka biri yazmış. Belki sadece size derlenmiş kod veren bir tedarikçi. Ya da önümüzdeki hafta yazmaya başlayan bazı yükleniciler.

Tüm bildiğiniz Bina arayüzü ve bu arayüze yaptığınız çağrılar. Hepsi oldukça makul görünüyor, bu yüzden iyi.

Şimdi farklı bir palto giyiyorsunuz ve aniden Bina'nın uygulayıcısı oluyorsunuz. Floor uygulamasını bilmiyorsunuz, sadece arayüzü biliyorsunuz. Bina sınıfınızı uygulamak için Floor arabirimini kullanırsınız. Floor arayüzünü ve Building sınıfınızı uygulamak için bu arayüze yaptığınız çağrıları biliyorsunuz ve hepsi oldukça makul görünüyor, bu yüzden tekrar iyisiniz.

Sonuçta, sorun değil. Herşey yolunda.

1
gnasher729

building.floor.bounds.GetLocalPoint (WorldPoint);

kötüdür.

Nesnelerin sadece yakın komşuları ile ilgilenmesi gerekir, çünkü sisteminizin başka türlü değiştirilmesi çok zor olacaktır.

0
kiwicomb123

Sadece işlevleri çağırmak sorun değil. Bu tekniği kullanan birçok tasarım deseni vardır, örneğin adaptör ve cephe, ancak bazıları dekoratör, proxy ve daha fazlası gibi desenleri genişletir.

Her şey soyutlama seviyeleri ile ilgili. Farklı soyutlama düzeylerindeki kavramları karıştırmamalısınız. Bunu yapmak için bazen iç nesneleri çağırmanız gerekir, böylece müşteriniz bunu kendisi yapmaya zorlanmaz.

Örneğin (Araba örneği daha basit olacaktır):

Sürücü, Araba ve Tekerlek nesneleriniz var. Gerçek dünyada, araba sürmek için sürücünüz doğrudan tekerleklerle bir şeyler yapıyor mu yoksa sadece bir bütün olarak araba ile etkileşime giriyor mu?

Bir şeyin yolunda olmadığını bilmek nasıl:

  • Kapsülleme bozuk, iç nesneler genel API'de kullanılabilir. (örn. araba.Wheel.Move () gibi kodlar).
  • SRP ilkesi bozuldu, nesneler çok farklı şeyler yapıyor (örneğin, e-posta iletisi metni hazırlama ve aslında aynı nesneye gönderme).
  • Belirli bir sınıfı test etmek zordur (örneğin birçok bağımlılık vardır).
  • Aynı sınıfta ele aldığınız şeyleri ele alan farklı alan adı uzmanları (veya şirket bölümleri) vardır (örneğin, satış ve paket teslimatı).

Demeter Yasasını çiğnemede olası sorunlar:

  • Sert birim testi.
  • Diğer nesnelerin iç yapısına bağımlılık.
  • Nesneler arasında yüksek bağlantı.
  • Dahili verilerin ortaya çıkarılması.
0
0lukasz0