Özel verilere erişmek için özel yöntem ne zaman genel yolu izlemelidir?


11

Özel verilere erişmek için özel yöntem ne zaman genel yolu izlemelidir? Örneğin, bu değişmez 'çarpan' sınıfına sahipsem (biraz yapışık, biliyorum):

class Multiplier {
public:
    Multiplier(int a, int b) : a(a), b(b) { }
    int getA() const { return a; }
    int getB() const { return b; }
    int getProduct() const { /* ??? */ }
private:
    int a, b;
};

Uygulamanın iki yolu var getProduct:

    int getProduct() const { return a * b; }

veya

    int getProduct() const { return getA() * getB(); }

Burada niyet değerini kullanmaktır Çünkü a, yani almak a kullanarak getA()uygulamak için getProduct()bana süpürge gibi görünüyor. aDeğiştirmek zorunda kalmadıkça kullanmaktan kaçınmayı tercih ederim . Benim endişem, kodun bu şekilde yazıldığını sık sık görmüyorum, tecrübelerime göre a * bdaha yaygın bir uygulama olurdu getA() * getB().

Özel yöntemler, bir şeye doğrudan erişebildikleri zaman kamusal yolu kullanmalı mıdır?

Yanıtlar:


7

Bu gerçek anlamını bağlıdır a, bve getProduct.

Alıcıların amacı, nesnenin arayüzünü aynı tutarken gerçek uygulamayı değiştirebilmektir. Bir gün, Örneğin, getAolur return a + 1;, değişim bir gaz giderici lokalize olur.

Gerçek senaryo vakaları bazen bir alıcı ile ilişkili bir kurucu aracılığıyla atanan sabit bir destek alanından daha karmaşıktır. Örneğin, alanın değeri hesaplanabilir veya kodun orijinal sürümündeki bir veritabanından yüklenebilir. Bir sonraki versiyonda, performansı optimize etmek için önbellekleme eklenebilir. Eğer getProductbilgisayarlı sürümünü kullanmaya devam eder, bu önbelleğe yarar olmaz (veya sürdürme iki defa aynı değişikliği yapacağız).

Kullanmak ve doğrudan getProductkullanmak mantıklıysa , onları kullanın. Aksi takdirde, daha sonra bakım sorunlarını önlemek için alıcıları kullanın.ab

Birinin alıcıları kullanacağı örnek:

class Product {
public:
    Product(ProductId id) : {
        price = Money.fromCents(
            data.findProductById(id).price,
            environment.currentCurrency
        )
    }

    Money getPrice() {
        return price;
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate); // ← Using a getter instead of a field.
    }
private:
    Money price;
}

Şu an için, alıcı herhangi bir iş mantığı içermese de, nesneyi başlatırken veritabanı çalışması yapmaktan kaçınmak için yapıcıdaki mantığın alıcıya taşınacağı hariç tutulmamaktadır:

class Product {
public:
    Product(ProductId id) : id(id) { }

    Money getPrice() {
        return Money.fromCents(
            data.findProductById(id).price,
            environment.currentCurrency
        )
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate);
    }
private:
    const ProductId id;
}

Daha sonra, önbellekleme eklenebilir (C #, bir kullanacak Lazy<T>, kodu kısa ve kolay hale getirme; C ++ 'da eşdeğer olup olmadığını bilmiyorum):

class Product {
public:
    Product(ProductId id) : id(id) { }

    Money getPrice() {
        if (priceCache == NULL) {
            priceCache = Money.fromCents(
                data.findProductById(id).price,
                environment.currentCurrency
            )

        return priceCache;
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate);
    }
private:
    const ProductId id;
    Money priceCache;
}

Her iki değişiklik de alıcıya ve destek alanına odaklandı, kalan kod etkilenmedi. Bunun yerine, bir alıcı yerine bir alan kullansaydım getPriceWithRebate, oradaki değişiklikleri de yansıtmalıydım.

Kişinin muhtemelen özel alanları kullanacağı örnek:

class Product {
public:
    Product(ProductId id) : id(id) { }
    ProductId getId() const { return id; }
    Money getPrice() {
        return Money.fromCents(
            data.findProductById(id).price, // ← Accessing `id` directly.
            environment.currentCurrency
        )
    }
private:
    const ProductId id;
}

Alıcı basittir: readonlygelecekte değişmesi beklenmeyen bir sabit (C # 'a benzer ) alanın doğrudan bir temsilidir : Şansı vardır, ID alıcısı asla hesaplanan bir değer olmaz. Bu yüzden basit tutun ve alana doğrudan erişin.

Başka bir avantajı, getIddışarıda kullanılmadığı anlaşılırsa gelecekte kaldırılabilmesidir (önceki kodda olduğu gibi).


Size bir +1 veremem çünkü özel alanları kullanma örneğiniz tek bir IMHO değil, çünkü şunu beyan ettiniz const: Derleyicinin bir getIdçağrıyı yine de arayacağı anlamına geliyor ve her iki yönde de değişiklik yapmanıza izin veriyor. (Aksi takdirde ben tam nedenlerle katılıyorum için . Kullanım getters) Ve özellik dizimi sağlıyoruz dilde, mülk ziyade doğrudan destek alanını kullanmamayı da azı nedenleri var.
Mark Hurd

1

Genellikle, değişkenleri doğrudan kullanırsınız. Bir sınıfın uygulamasını değiştirirken tüm üyeleri değiştirmeyi beklersiniz. Değişkenleri doğrudan kullanmamak, onlara bağlı olan kodu doğru bir şekilde izole etmeyi zorlaştırır ve üyeyi okumayı zorlaştırır.

Bu, eğer alıcılar gerçek mantığı uygularsa elbette farklıdır, bu durumda mantıklarını kullanmanız gerekip gerekmediğine bağlıdır.


1

DRY'ye uymaktan başka bir sebep olmasa bile, kamu yöntemlerini kullanmanın tercih edileceğini söyleyebilirim .

Sizin durumunuzda, erişimcileriniz için basit yedekleme alanlarınız olduğunu biliyorum, ancak bu değişkeni ilk kez kullanmadan önce çalıştırmanız gereken belirli mantık, örneğin tembel yükleme kodu olabilir. Böylece, alanlarınıza doğrudan başvurmak yerine erişimcilerinizi aramak istersiniz. Bu durumda buna sahip olmasanız bile, tek bir toplantıya sadık kalmak mantıklıdır. Bu şekilde, mantığınızda herhangi bir değişiklik yaparsanız, onu yalnızca tek bir yerde değiştirmeniz gerekir.


0

Bir sınıf için bu küçük, basitlik kazanır. Sadece * b kullanırdım.

Çok daha karmaşık bir şey için, açıkça "minimal" arayüzü tam ortak API tüm diğer işlevlerden açıkça ayırmak istedim getA () * getB () kullanmayı düşünün. Mükemmel bir örnek C ++ 'da std :: string olacaktır. 103 üye işlevi vardır, ancak bunların sadece 32'sinin özel üyelere erişmesi gerekir . Karmaşık bir sınıfınız varsa, tüm "çekirdek olmayan" işlevleri sürekli olarak "temel API" den geçmeye zorlamak, uygulamayı sınamak, hata ayıklamak ve yeniden düzenleme yapmak için çok daha kolay hale getirebilir.


1
Karmaşık bir sınıfınız varsa, bunu bant yardımına değil düzeltmeye zorlamalısınız.
DeadMG

Kabul. Muhtemelen sadece 20-30 işlevli bir örnek seçmeliydim.
Ixrec

1
"103 işlev" biraz kırmızı ringa balığıdır. Aşırı yüklenmiş yöntemler, arayüz karmaşıklığı açısından bir kez sayılmalıdır.
Avner Shahar-Kashtan

Tamamen katılmıyorum. Farklı aşırı yükler farklı semantiklere ve farklı arayüzlere sahip olabilir.
DeadMG

Bu "küçük" örnek için bile getA() * getB()orta ve uzun vadede daha iyidir.
Mark Hurd
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.