it-swarm.dev

"Sade eski veri" sınıflarını kullanmak için herhangi bir neden var mı?

Eski kodda zaman zaman veri için sarmalayıcılardan başka bir şey olmayan sınıflar görüyorum. gibi bir şey:

class Bottle {
   int height;
   int diameter;
   Cap capType;

   getters/setters, maybe a constructor
}

Benim anlayışım OO sınıflar veri yapıları ve bu veri üzerinde çalışma yöntemleri. Bu tür nesneleri engelliyor gibi görünüyor. Bana göre onlar hiçbir şey fazla structs ve tür OO amacı yenilgi.Ben bir kod kokusu olsa da, mutlaka kötü olduğunu sanmıyorum.

Bu tür nesnelerin gerekli olacağı bir durum var mı? Bu sık kullanılırsa, tasarımı şüphelenir mi?

44
Michael K

Kesinlikle kötü ve zihnimde bir kod kokusu değil. Veri kapsayıcılar geçerli bir OO vatandaştır. Bazen ilgili bilgileri bir arada kapsüllemek istersiniz.

public void DoStuffWithBottle(Bottle b)
{
    // do something that doesn't modify Bottle, so the method doesn't belong
    // on that class
}

göre

public void DoStuffWithBottle(int bottleHeight, int bottleDiameter, Cap capType)
{
}

Bir sınıf kullanmak, DoStuffWithBottle'ın her çağıranını değiştirmeden Bottle'a ek bir parametre eklemenizi sağlar. Şişeyi alt sınıflara ayırabilir ve gerekirse kodunuzun okunabilirliğini ve organizasyonunu daha da artırabilirsiniz.

Örneğin, bir veritabanı sorgusu sonucunda döndürülebilecek düz veri nesneleri de vardır. Bu durumda onlar için terimin "Veri Aktarım Nesnesi" olduğuna inanıyorum.

Bazı dillerde başka hususlar da vardır. Örneğin, C # sınıflarında ve yapıları farklı davranır, çünkü yapılar bir değer türüdür ve sınıflar referans türleridir.

68
Adam Lear

Veri sınıfları bazı durumlarda geçerlidir. DTO'lar Anna Lear tarafından bahsedilen iyi bir örnektir. Genel olarak, onları henüz yöntemleri filizlenmemiş bir sınıfın tohumu olarak kabul etmelisiniz. Ve eski kodda birçoğuyla karşılaşıyorsanız, onları güçlü bir kod kokusu olarak kabul edin. Genellikle OO programlamaya geçiş yapmayan ve prosedürel programlamanın bir işareti olan eski C/C++ programcıları tarafından kullanılırlar.Alıcılara ve ayarlayıcılara her zaman (veya daha da kötüsü, özel olmayan üyelerin doğrudan erişimi) bunu bilmeden başınızı belaya sokabilir .. Bottle 'dan bilgiye ihtiyaç duyan harici bir yöntem örneği düşünün.

Burada Bottle bir veri sınıfıdır):

void selectShippingContainer(Bottle bottle) {
    if (bottle.getDiameter() > MAX_DIMENSION || bottle.getHeight() > MAX_DIMENSION ||
            bottle.getCapType() == Cap.FANCY_CAP ) {
        shippingContainer = WOODEN_CRATE;
    } else {
        shippingContainer = CARDBOARD_BOX;
    }
}

Burada Bottle bazı davranışlar verdik):

void selectShippingContainer(Bottle bottle) {
    if (bottle.isBiggerThan(MAX_DIMENSION) || bottle.isFragile()) {
        shippingContainer = WOODEN_CRATE;
    } else {
        shippingContainer = CARDBOARD_BOX;
    }
}

İlk yöntem Tell-Don't-Ask prensibini ihlal eder ve Bottle aptal tutarak şişeler hakkında bir fragle (Cap) yapan, Bottle sınıfının dışındaki mantık. Alıcının alışkanlıklarına güvenirken bu tür bir 'sızıntıyı' önlemek için ayak parmaklarınızın üzerinde olmalısınız.

İkinci yöntem Bottle 'e sadece işini yapması gerekeni sorar ve kırılgan ya da belirli bir boyuttan daha büyük olup olmadığına karar vermek için Bottle' i bırakır. Sonuç, yöntem ve Şişenin uygulanması arasında daha gevşek bir bağlantıdır. Hoş bir yan etki, yöntemin daha temiz ve daha etkileyici olmasıdır.

Sınıfta bu alanlarla birlikte bulunması gereken bir mantık yazmadan, bir nesnenin bu birçok alanını nadiren kullanırsınız. Bu mantığın ne olduğunu bulun ve ait olduğu yere taşıyın.

25
Mike E

Eğer ihtiyacınız olan şey buysa, bu iyi, ama lütfen, lütfen, lütfen bunu yapın

public class Bottle {
    public int height;
    public int diameter;
    public Cap capType;

    public Bottle(int height, int diameter, Cap capType) {
        this.height = height;
        this.diameter = diameter;
        this.capType = capType;
    }
}

gibi bir şey yerine

public class Bottle {
    private int height;
    private int diameter;
    private Cap capType;

    public Bottle(int height, int diameter, Cap capType) {
        this.height = height;
        this.diameter = diameter;
        this.capType = capType;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getDiameter() {
        return diameter;
    }

    public void setDiameter(int diameter) {
        this.diameter = diameter;
    }

    public Cap getCapType() {
        return capType;
    }

    public void setCapType(Cap capType) {
        this.capType = capType;
    }
}

Lütfen.

7
compman

@Anna'nın dediği gibi, kesinlikle kötü değil. Elbette sınıflara işlemler (yöntemler) koyabilirsiniz, ancak yalnızca istediğiniz için. Sen var için.

Sınıfların soyutlama olduğu fikrinin yanı sıra, sınıflara işlem yapmak zorunda olduğunuz fikri hakkında küçük bir sıkıntıya izin verin. Uygulamada, bu programcıları

  1. İhtiyaç duyduklarından daha fazla sınıf yapın (yedekli veri yapısı). Bir veri yapısı minimum düzeyde gerekenden daha fazla bileşen içerdiğinde, normalleştirilmez ve bu nedenle tutarsız durumlar içerir. Başka bir deyişle, değiştirildiğinde, tutarlı kalabilmek için birden fazla yerde değiştirilmesi gerekir. Koordine edilen tüm değişikliklerin yapılmaması tutarsızlığı sağlar ve bir hatadır.

  2. Sorun 1'i bildirim yöntemlerini girerek çözün, böylece A kısmı değiştirilirse, gerekli değişiklikleri B ve C parçalarına yaymaya çalışır. set erişimci yöntemleri. Bu uygulama önerildiğinden, sorun 1'i özür diler ve sorun 1 ve daha fazla çözüm 2'ye neden olur. Bu, yalnızca bildirimlerin eksik uygulanması nedeniyle değil, kaçak bildirimlerin performans düşürücü sorunuyla sonuçlanır. Bunlar sonsuz hesaplamalar değil, sadece çok uzun olanlardır.

Bu kavramlar iyi şeyler olarak öğretilir, genellikle bu sorunlarla dolu milyon satır canavar uygulamaları içinde çalışmak zorunda olmayan öğretmenler tarafından.

İşte yapmaya çalıştığım şey:

  1. Verileri olabildiğince normalleştirin, böylece verilerde bir değişiklik yapıldığında, tutarsız bir duruma girme olasılığını en aza indirmek için mümkün olduğunca az kod noktasında yapılır.

  2. Veriler normalleştirilmemeli ve artıklık kaçınılmaz olduğunda, tutarlı kalmasını sağlamak için bildirimleri kullanmayın. Aksine, geçici tutarsızlığı tolere edin. Yalnızca bunu yapan bir işlemle verilerdeki periyodik taramalardaki tutarsızlığı giderin. Bu, bildirimlerin eğilimli olduğu performans ve doğruluk sorunlarından kaçınırken tutarlılığı koruma sorumluluğunu merkezileştirir. Bu, çok daha küçük, hatasız ve verimli kodla sonuçlanır.

6
Mike Dunlavey

Oyun tasarımı ile, fonksiyon çağrılarının 1000'leri ve olay dinleyicileri yükü, bazen sadece veri depolayan sınıflara sahip olmayı ve mantığı gerçekleştirmek için yalnızca tüm veri sınıflarında döngü yapan diğer sınıflara sahip olmaya değer olabilir.

3
AttackingHobo

Anna Lear ile aynı fikirde,

Kesinlikle kötü ve aklımda bir kod kokusu değil. Veri kapsayıcılar geçerli bir OO vatandaş. Bazen ilgili bilgileri bir arada kapsüllemek istersiniz ...

Bazen insanlar 1999 Java Kodlama Kurallarını okumayı unuturlar ki bu da bu tür bir programlamanın mükemmel olduğunu gösterir. Aslında bundan kaçınırsanız, kodunuz kokar!)/ayarlayıcılar)

From Java Kod Kuralları 1999: Uygun genel örnek değişkenlere bir örnek, sınıfın temelde davranışsız bir veri yapısı olduğu durumdur. , sınıf yerine bir yapı kullanırsanız (if Java desteklenen yapı), sınıfın örnek değişkenlerini herkese açık hale getirmeniz uygundur. - http://www.Oracle.com/technetwork/Java/javase/documentation/codeconventions-137265.html#177

Doğru kullanıldığında, PJ'ler (düz eski veri yapıları) POJO'lardan genellikle EJB'lerden daha iyi olduğu gibi POJO'lardan daha iyidir.
http://en.wikipedia.org/wiki/Plain_Old_Data_Structures

3
Richard Faber

Bu tür sınıflar, bazı nedenlerden dolayı orta büyüklükte/büyük uygulamalarla uğraşırken oldukça kullanışlıdır:

  • bazı test senaryoları oluşturmak ve verilerin tutarlı olmasını sağlamak oldukça kolaydır.
  • bu bilgiyi içeren her türlü davranışı içerir, böylece veri hatası izleme süresi azalır
  • Bunları kullanmak yöntem argümanlarını hafif tutmalıdır.
  • ORM'leri kullanırken, bu sınıflar esneklik ve tutarlılık sağlar. Sınıfta zaten bulunan basit bilgilere dayanarak hesaplanan karmaşık bir öznitelik eklemek, tek bir basit yöntem yazmaya başlar. Bu, veritabanınızı kontrol etmek ve tüm veritabanlarının yeni bir değişiklikle düzeltilmesini sağlamak için oldukça çevik ve üretkendir.

Özetle, deneyimlerime göre genellikle can sıkıcı olmaktan daha faydalıdırlar.

3
guiman

Java'da bile yapıların yeri vardır. Bunları yalnızca aşağıdaki iki şey doğruysa kullanmalısınız:

  • Yalnızca davranışı olmayan verileri toplamanız gerekir; ör. parametre olarak geçmek
  • Birleştirilmiş verilerin ne tür değerlere sahip olduğu bir parça önemli değil

Bu durumda, alanları herkese açık hale getirmeli ve alıcıları/ayarlayıcıları atlamalısınız. Harfler ve ayarlayıcılar yine de tıknazdır ve Java yararlı bir dil gibi özelliklere sahip olmadığı için saçmadır.

Ancak, bunlardan biri geçerli değilse, gerçek bir sınıfla uğraşıyorsunuz demektir. Bu, tüm alanların özel olması gerektiği anlamına gelir. (Kesinlikle daha erişilebilir bir kapsamda bir alana ihtiyacınız varsa, bir alıcı/ayarlayıcı kullanın.)

Sözde yapınızın davranışının olup olmadığını kontrol etmek için alanların ne zaman kullanıldığına bakın. Eğer ihlal ediyor gibi görünüyorsa söyle, sorma , o zaman bu davranışı sınıfına taşımalısın.

Verilerinizden bazıları değişmezse, tüm bu alanları sonlandırmanız gerekir. Sınıfınızı yapmayı düşünebilirsiniz değişmez . Verilerinizi doğrulamanız gerekiyorsa ayarlayıcılarda ve yapıcılarda doğrulama sağlayın. (Yararlı bir püf noktası, özel bir ayarlayıcı tanımlamak ve sınıfınızdaki alanınızı yalnızca bu ayarlayıcıyı kullanarak değiştirmektir.)

Şişe örneğiniz büyük olasılıkla her iki testte de başarısız olacaktır. Şuna benzeyen bir kodunuz olabilir:

public double calculateVolumeAsCylinder(Bottle bottle) {
    return bottle.height * (bottle.diameter / 2.0) * Math.PI);
}

Bunun yerine olmalı

double volume = bottle.calculateVolumeAsCylinder();

Yüksekliği ve çapı değiştirirseniz, aynı şişe olur mu? Muhtemelen değil. Bunlar nihai olmalı. Çap için negatif bir değer uygun mu? Şişeniz genişlikten daha uzun olmalı mı? Kapak boş olabilir mi? Hayır? Bunu nasıl doğrulıyorsunuz? Danışanın aptal veya kötü olduğunu varsayın. ( Farkı söylemek imkansız. ) Bu değerleri kontrol etmeniz gerekiyor.

Yeni Bottle sınıfınız şöyle görünebilir:

public class Bottle {

    private final int height, diameter;

    private Cap capType;

    public Bottle(final int height, final int diameter, final Cap capType) {
        if (diameter < 1) throw new IllegalArgumentException("diameter must be positive");
        if (height < diameter) throw new IllegalArgumentException("bottle must be taller than its diameter");

        setCapType(capType);
        this.height = height;
        this.diameter = diameter;
    }

    public double getVolumeAsCylinder() {
        return height * (diameter / 2.0) * Math.PI;
    }

    public void setCapType(final Cap capType) {
        if (capType == null) throw new NullPointerException("capType cannot be null");
        this.capType = capType;
    }

    // potentially more methods...

}
3
Eva

IMHO, yoğun nesne yönelimli sistemlerde genellikle yeterli sınıf yoktur. Bunu dikkatli bir şekilde nitelendirmem gerekiyor.

Tabii ki veri alanları geniş kapsam ve görünürlüğe sahipse, kod tabanınızda bu tür verileri kurcalayan yüzlerce veya binlerce yer varsa bu istenmeyen bir durumdur. Bu, değişmezleri korumakta sorun ve zorluklar istiyor. Ancak bu aynı zamanda tüm kod tabanındaki her sınıfın bilgi gizlemeden faydalandığı anlamına gelmez.

Ancak bu tür veri alanlarının kapsamının çok dar olacağı birçok durum vardır. Çok basit bir örnek, bir veri yapısının özel Node sınıfıdır. Eğer Node sadece ham verilerden oluşuyorsa, devam eden nesne etkileşimlerinin sayısını azaltarak kodu büyük ölçüde basitleştirebilir. Alternatif sürüm, örneğin Tree->Node ve Node->Tree'den sadece Tree->Node Data yerine çift yönlü kuplaj gerektirebileceğinden, bir ayırma mekanizması görevi görür.

Daha karmaşık bir örnek, oyun motorlarında sıklıkla kullanılan varlık bileşen sistemleridir. Bu durumlarda, bileşenler genellikle yalnızca ham veri ve gösterdiğiniz gibi sınıflardır. Bununla birlikte, kapsam/görünürlükleri sınırlı olma eğilimindedir, çünkü tipik olarak söz konusu bileşene erişebilen yalnızca bir veya iki sistem vardır. Sonuç olarak, bu sistemlerde değişmezleri korumayı oldukça kolay bulmaya eğilimlisiniz ve dahası, bu tür sistemler çok az object->object etkileşime sahiptir, bu da kuş göz seviyesinde neler olup bittiğini kavramanızı kolaylaştırır.

Bu gibi durumlarda, etkileşimlere kadar buna benzer bir şeyle sonuçlayabilirsiniz (bu şema, birleşmeyi değil etkileşimleri gösterir, çünkü bir bağlantı şeması aşağıdaki ikinci görüntü için soyut arayüzler içerebilir):

enter image description here

... aksine:

enter image description here

... ve eski sistem türü, bağımlılıkların gerçekte verilere doğru akmasına rağmen, doğruluk açısından sürdürülmesi çok daha kolay ve mantıklıdır. Öncelikle çok daha az bağlantı elde edersiniz çünkü birçok şey birbirleriyle etkileşen nesneler yerine çok karmaşık bir etkileşim grafiği oluşturan ham verilere dönüştürülebilir.

0
user204677