Alıcılardan ve ayarlayıcılardan nasıl kaçınırsın?


85

Sınıfları oo tarzında tasarlamakta zorlanıyorum. Nesnelerin verilerini değil davranışlarını gösterdiğini okudum; bu nedenle, verileri değiştirmek için alıcı / ayarlayıcı kullanmak yerine, verilen bir sınıfın metotları "fiiller" veya nesne üzerinde çalışan eylemler olmalıdır. Örneğin, bir 'Hesap' nesnesinde, biz yöntemleri olurdu Withdraw()ve Deposit()yerine setAmount()vs. bakınız: alıcı ve ayarlayıcı yöntemleri kötüdür Neden .

Örneğin, müşteri hakkında çok fazla bilgi tutan bir Müşteri sınıfı verildiğinde, örneğin Ad, DOB, Tel, Adres, vb., Tüm bu özellikleri almak ve ayarlamak için alıcı / ayarlayıcılardan nasıl kaçınır? Tüm bu verileri doldurmak için hangi 'Davranış' tipi yöntem yazılabilir?


3
olası düz
tatarcık


8
Eğer Java Fasulyesi spesifikasyonuyla uğraşmak zorunda kalırsanız , alıcı ve ayarlayıcılara sahip olacağınıza dikkatinizi çekerim. Pek çok şey Java Fasulyesi (JSPS'de İfade Dili) kullanır ve bundan kaçınmaya çalışmak muhtemelen zor olacaktır.

4
... ve, MichaelT’nin tam tersi bir perspektiften: JavaBeans özelliğini kullanmıyorsanız (ve nesnenizi gerektiğinde bir bağlamda kullanmıyorsanız şahsen kaçınmanız gerektiğini düşünüyorum), Özellikle ilgili ayarlayıcıya sahip olmayan özellikler için alıcılarda "get" siğilleri. Aradım bir yöntem düşünmek name()üzerine Customerdenilen bir yöntemle daha gibi berrak veya nettir getName().
Daniel Pryden

2
@Daniel Pryden: adı () hem ayarlamak veya almak için anlamına gelebilir ...?
IntelliData

Yanıtlar:


55

Epeyce yanıtlar ve yorumlar da belirtildiği üzere, DTOs olan özellikle (web hizmeti üzerinden göndermek için JSON seri hale örn) sınırları ötesinde veri aktarımı olmak üzere bazı durumlarda uygun ve kullanışlı. Bu cevabın geri kalanında, bunu az ya da çok görmezden geleceğim ve etki alanı sınıfları ve alıcıları ve belirleyicileri asgariye indirmek (ortadan kaldırmak değilse) nasıl en aza indirileceklerini ve büyük bir projede nasıl faydalı olabileceklerini konuşacağım. Ayrıca alıcıların veya ayarlayıcıların neden kaldırıldığını ya da ne zaman yapılacağından da bahsetmiyorum , çünkü bunlar kendi sorularıdır.

Örnek olarak, projenizin Satranç ya da Savaş Gemisi gibi bir masa oyunu olduğunu hayal edin. Bunu bir sunum katmanında (konsol uygulaması, web servisi, GUI vb.) Temsil etmenin çeşitli yolları olabilir, ancak aynı zamanda bir çekirdek etki alanınız da var. Sahip olabileceğiniz bir sınıf Coordinate, tahtadaki pozisyonu temsil etmektir. Bunu yazmanın "kötülük" yolu olacaktır:

public class Coordinate
{
    public int X {get; set;}
    public int Y {get; set;}
}

(Java için değil, C # dilinde kod örnekleri yazacağım, kısayım ve daha iyi tanıdığım için. Umarım bu bir problem değildir. Kavramlar aynıdır ve çeviri basit olmalıdır.)

Ayarlayıcıları Çıkarma: İmkansızlık

Halka açık alıcılar ve taraftarlar hem potansiyel olarak sorunluyken, hem taraftarlar ikisinin çok daha "kötüsü". Ayrıca, genellikle ortadan kaldırılması daha kolaydır. Süreç, kurucu içindeki değeri basit bir şekilde belirler. Daha önce nesneyi mutasyona uğratan herhangi bir yöntem bunun yerine yeni bir sonuç vermelidir. Yani:

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}

    public Coordinate(int x, int y)
    {
        X = x;
        Y = y;
    }
}

Bunun, X ve Y mutasyonları sınıfındaki diğer yöntemlere karşı koruma sağlamadığına dikkat edin. Daha kesin olarak değişken olmak için, readonly( finalJava'da) kullanabilirsiniz. Ancak, her iki durumda da - mülkünüzü gerçekten değişmez kılmak ya da sadece ayarlayıcılar aracılığıyla doğrudan halkın mutasyonunu önlemek olsun - herkese açık ayarlayıcılarınızı kaldırma hilesidir. Çoğu durumda, bu sadece işe yarıyor.

Alıcıları Çıkarma, Bölüm 1: Davranış için Tasarım

Yukarıdakiler her şey yolunda ve iyi anlaşmalar için iyidir, ancak alıcılar açısından aslında başlamadan önce kendimizi ayağından vurduk. Bizim sürecimiz bir koordinatın ne olduğunu - temsil ettiği veriyi - düşünmek ve bunun etrafında bir sınıf yaratmaktı. Bunun yerine, bir koordinattan hangi davranışa ihtiyacımız olduğunu başlatmalıydık . Bu süreç, TDD'nin desteğiyle, onlara ancak ihtiyaç duyduğumuzda böyle sınıfları çıkardığımız için, bu nedenle istenen davranışla başlıyoruz ve oradan çalışıyoruz.

Diyelim ki, kendinizi ihtiyaç duyduğunuz ilk yerde Coordinateçarpışma tespiti içindi: iki parçanın tahtada aynı alanı işgal edip etmediğini kontrol etmek istediniz. İşte "kötülük" yolu (yapıcılar kısalık için göz ardı edildi):

public class Piece
{
    public Coordinate Position {get; private set;}
}

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}
}

    //...And then, inside some class
    public bool DoPiecesCollide(Piece one, Piece two)
    {
        return one.X == two.X && one.Y == two.Y;
    }

Ve işte güzel yol:

public class Piece
{
    private Coordinate _position;
    public bool CollidesWith(Piece other)
    {
        return _position.Equals(other._position);
    }
}

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;
    public bool Equals(Coordinate other)
    {
        return _x == other._x && _y == other._y;
    }
}

( IEquatableuygulama basitlik için kısaltılmıştır). Verileri modellemek yerine davranışları tasarlayarak, alıcılarımızı kaldırmayı başardık.

Bunun, örneğinizle de alakalı olduğunu unutmayın. Bir ORM kullanıyor olabilirsiniz veya müşteri bilgilerini bir web sitesinde veya başka bir şeyde görüntüleyebilirsiniz, bu durumda bir tür CustomerDTO muhtemelen mantıklı olacaktır. Ancak, sisteminiz müşterileri içerdiği ve veri modelinde temsil edilmeleri, otomatik olarak Customeretki alanınızda bir sınıf olması gerektiği anlamına gelmez . Belki davranış için tasarım yaptığınız zaman, biri ortaya çıkar, ancak alıcılardan kaçınmak istiyorsanız, önleyici olarak bir tane yaratmayın.

Alıcıları Çıkarma, Bölüm 2: Dış Davranış

Yani yukarıda iyi bir başlangıç, ama er ya da geç muhtemelen bir şekilde sınıfının durumuna bağlıdır bir sınıfın, ilişkili davranışa sahip bir durumun içine çalışır, ancak hangi ait değil üzerine sınıfa. Bu tür davranışlar, genellikle uygulamanızın hizmet katmanında yaşayan şeydir .

Bizim çıkarak Coordinateörnek, sonunda kullanıcıya oyununuzu temsil etmek isteyeceksiniz ve bu ekrana çizim anlamına gelebilir. Örneğin, Vector2ekrandaki bir noktayı temsil etmek için kullanılan bir UI projeniz olabilir . Ancak, Coordinatesınıfın her türlü sunum kaygısını ana alanınıza getirecek olan bir koordinattan ekrandaki bir noktaya dönüştürülmesinin sorumluluğunu alması uygunsuz olacaktır. Ne yazık ki bu tür bir durum OO tasarımında doğaldır.

Çok yaygın olarak seçilen ilk seçenek sadece lanet alıcıları ortaya çıkarmak ve cehenneme gitmektir. Bu basitlik avantajına sahiptir. Ancak alıcılardan kaçınmaktan bahsettiğimiz için, tartışmanın uğruna, bunu reddediyoruz ve başka hangi seçeneklerin olduğunu görelim.

İkinci seçenek , .ToDTO()sınıfınıza bir tür yöntem eklemektir . Buna - veya benzer şekilde - yine de, örneğin, oyunu kurtarmak istediğinizde, eyaletinizin neredeyse tamamını yakalamak için ihtiyaç duyduğunuzda gerekli olabilir. Ancak bunu hizmetleriniz için yapmakla ve yalnızca alıcıya doğrudan erişmek arasındaki fark az çok estetiktir. Hala ona göre "kötülük" var.

Zoran Horvat'ın birkaç Çoğul Görüş videosunda savunduğunu gördüğüm üçüncü bir seçenek , ziyaretçi modelinin değiştirilmiş bir versiyonunu kullanmak. Bu, alışılmadık bir kullanım ve örüntü çeşididir ve insanların kilometresinin, gerçek bir kazanç elde etmek için karmaşıklık ekleyip eklemediği veya durum için iyi bir uzlaşma olup olmadığına göre büyük ölçüde değişeceğini düşünüyorum. Buradaki fikir temel olarak standart ziyaretçi kalıbını kullanmaktır, ancak Visityöntemlerin ziyaret ettikleri sınıfın yerine ihtiyaç duydukları durumu parametre olarak almasını sağlayın. Örnekler burada bulunabilir .

Sorunumuz için, bu modeli kullanan bir çözüm şöyle olacaktır:

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;

    public T Transform<T>(IPositionTransformer<T> transformer)
    {
        return transformer.Transform(_x,_y);
    }
}

public interface IPositionTransformer<T>
{
    T Transform(int x, int y);
}

//This one lives in the presentation layer
public class CoordinateToVectorTransformer : IPositionTransformer<Vector2>
{
    private readonly float _tileWidth;
    private readonly float _tileHeight;
    private readonly Vector2 _topLeft;

    Vector2 Transform(int x, int y)
    {
        return _topLeft + new Vector2(_tileWidth*x + _tileHeight*y);
    }
}

Muhtemelen söyleyebileceğiniz gibi, _xve _yartık gerçekten kapsüllenmemiş. IPositionTransformer<Tuple<int,int>>Onları doğrudan döndüren bir tane yaratarak onları çıkarabiliriz . Tadı bağlı olarak, bunun tüm egzersizi anlamsız hale getirdiğini hissedebilirsiniz.

Ancak, kamu alıcıları ile işleri yanlış bir şekilde yapmak çok kolaydır, sadece verileri doğrudan çekmek ve Tell, Ask Sorma ihlaliyle kullanmak çok kolaydır . Bu kalıbı kullanırken, aslında doğru şekilde yapmak daha kolaydır : davranış oluşturmak istediğinizde, onunla ilişkilendirilmiş bir tür oluşturarak otomatik olarak başlarsınız. TDA ihlalleri çok açık bir şekilde koklayacak ve muhtemelen daha basit ve daha iyi bir çözüm etrafında çalışmayı gerektirecektir. Uygulamada, bu noktalar, doğru yapmayı kolaylaştırıyor, OO, alıcıların cesaretlendirdiği “kötülük” yönteminden daha kolay.

Son olarak , başlangıçta açık olmasa bile, aslında durumu açığa vurmaktan kaçınmak için davranış olarak ihtiyacınız olanı yeterince ortaya çıkarmanın yolları olabilir . Örneğin, Coordinatetek genel üyesi olan Equals()(uygulamada tam bir IEquatableuygulamaya ihtiyaç duyacağı ) önceki sürümümüzü kullanarak , aşağıdaki sınıfı sunum katmanınıza yazabilirsiniz:

public class CoordinateToVectorTransformer
{
    private Dictionary<Coordinate,Vector2> _coordinatePositions;

    public CoordinateToVectorTransformer(int boardWidth, int boardHeight)
    {
        for(int x=0; x<boardWidth; x++)
        {
            for(int y=0; y<boardWidth; y++)
            {
                _coordinatePositions[new Coordinate(x,y)] = GetPosition(x,y);
            }
        }
    }

    private static Vector2 GetPosition(int x, int y)
    {
        //Some implementation goes here...
    }

    public Vector2 Transform(Coordinate coordinate)
    {
        return _coordinatePositions[coordinate];
    }
}

Belki şaşırtıcı bir şekilde, bir koordinattan hedefimize ulaşmak için gerçekten ihtiyaç duyduğumuz tüm davranışların eşitlik kontrolü olduğu ortaya çıktı! Elbette, bu çözüm bu soruna uyarlanmıştır ve kabul edilebilir bellek kullanımı / performansı hakkında varsayımlarda bulunur. Genel bir çözüm için bir taslaktan ziyade, bu özel problem alanına uyan bir örnek.

Ve yine, görüşler pratikte bunun gereksiz bir karmaşıklık olup olmadığına bağlı olarak değişecektir. Bazı durumlarda, bunun gibi bir çözüm bulunmayabilir veya yasaklayıcı bir şekilde tuhaf veya karmaşık olabilir, bu durumda yukarıdaki üçe geri dönebilirsiniz.


Güzel cevap verdi! Kabul etmek istiyorum, ancak önce bazı yorumlar: 1. ToDTO () 'nun harika olduğunu düşünüyorum, çünkü bcuz ur / set'e erişemiyor, bu da DTO’ya verilen alanları değiştirmeden varolan kodu kırmanıza izin veriyor. 2. Say Müşteri u bunları değiştirmek için sahne erişmek nasıl, onu bir varlık yapma haklı çıkarmak için yeterli davranışa sahip, örneğin adres / tel değişikliği vs.
IntelliData

@IntelliData 1. "Alanları değiştir" derken, sınıf tanımını değiştirmeyi mi yoksa verileri mutasyona mı geçirmeyi kastediyorsunuz? İkincisi, kamuoyu belirleyenleri kaldırarak ancak alıcıları terk ederek önlenebilir, bu nedenle dto yönü önemsizdir. İlki, halkın “kötülük” olmasının gerçek bir nedeni değildir. Örneğin, programmers.stackexchange.com/questions/157526/… adresine bakın .
Ben Aaronson

@IntelliData 2. Davranışı bilmeden cevap vermek zordur. Ama muhtemelen, cevap şu ki: yapmazsınız. Bir Customersınıfın telefon numarasını değiştirebilmeyi gerektiren hangi davranışları olabilir ? Belki de müşterinin telefon numarası değişiyor ve veritabanındaki bu değişikliği sürdürmem gerekiyor, ama bunların hiçbiri davranış sağlayan bir etki alanı nesnesinin sorumluluğunda değil. Bu veri erişimiyle ilgili bir endişedir ve muhtemelen bir DTO ve bir depo ile ele alınacaktır.
Ben Aaronson

@IntelliData CustomerEtki alanı nesnesinin verilerini nispeten taze tutmak (db ile eşzamanlı olarak), kendi sorumluluğunu da barındırmayan yaşam döngüsünü yönetme meselesidir ve muhtemelen bir depoda veya bir fabrikada veya bir IOC konteynerinde yaşamaya başlar. ne olursa olsun Customers.
Ben Aaronson

2
Ben gerçekten seviyorum Davranış için Tasarım konsepti. Bu, altta yatan "veri yapısını" bilgilendirir ve tüm yaygın anemik, kullanımı zor sınıflardan kaçınmaya yardımcı olur. artı bir.
radarbob

71

Ayarlayıcılardan kaçınmanın en basit yolu , nesneyi kaldırdığınızda değerleri yapıcı yöntemine newvermektir. Bu, aynı zamanda bir nesneyi değişmez yapmak istediğiniz zamanki olağan düzendir. Bu, gerçek dünyada her zaman her şeyin net olmadığını söyledi.

Yöntemlerin davranışla ilgili olması gerektiği doğrudur. Ancak, Müşteri gibi bazı nesneler öncelikle bilgi tutmak için vardır. Bunlar alıcılardan ve ayarlayıcılardan en fazla yararlanan nesne türleridir; Bu tür yöntemlere hiç ihtiyaç duyulmasaydı, onları tamamen ortadan kaldırırdık.

Alıcılar ve Setçiler Haklı Olduğunda Daha Fazla Okuma


3
Peki neden 'Getter / Setters' ile ilgili tüm yutturmaca kötü?
IntelliData

46
Sadece daha iyi bilmesi gereken yazılım geliştiricileri tarafından yapılan genel sunum. Bağladığınız makale için, yazar dikkatinizi çekmek için "alıcılar ve ayarlayıcılar kötüdür" ifadesini kullanıyor, ancak bu ifadenin kategorik olarak doğru olduğu anlamına gelmiyor.
Robert Harvey,

12
@IntelliData bazı insanlar size java'nın kötü olduğunu söyleyecektir.
null

18
@MetaFightsetEvil(null);

4
Kesinlikle eval Evil Incarnate. Ya da yakın bir kuzen.
piskopos

58

Davranıştan ziyade veriyi ortaya çıkaran bir nesneye sahip olmak tamamen iyidir. Biz sadece buna "veri nesnesi" diyoruz. Model, Veri Aktarım Nesnesi veya Değer Nesnesi gibi adlar altında bulunur. Nesnenin amacı veri tutmaksa, verilere erişmek için alıcılar ve ayarlayıcılar geçerlidir.

Öyleyse neden biri "alıcı ve belirleyici yöntemlerin kötülük olduğunu" diyor? Bunu çok göreceksiniz - birileri belirli bir bağlamda tamamen geçerli olan bir kılavuz alır ve daha zorlayıcı bir başlık elde etmek için içeriği kaldırır. Örneğin, " kalıtım yerine iyilik kompozisyonu " iyi bir ilkedir, ancak kısa sürede yeterince insan bağlamı kaldıracak ve " Niçin kötülük esastır? " (Hey, aynı yazar, ne tesadüf!) Veya " miras kötüdür ve olmalı yok edildi ".

Makalenin içeriğine bakarsanız, aslında bazı geçerli noktaları var, sadece bir tıklama başı başlık oluşturmak için bu noktayı genişletiyor. Örneğin, makale uygulama detaylarının ifşa edilmemesi gerektiğini belirtiyor. Bu, OO'da temel olan kapsülleme ve veri gizleme ilkeleridir. Bununla birlikte, bir alıcı yöntemi tanımı gereği uygulama detaylarını göstermez. Müşteri veri nesnesi söz konusu olduğunda, Ad , Adres vb. Özellikleri uygulama detayları değil, nesnenin bütün amacıdır ve ortak arayüzün bir parçası olmalıdır.

Kötü belirleyicileri kullanmadan, bir 'Çalışan' nesnesinde 'isim' ve 'maaş' gibi özellikleri nasıl ayarlamayı önerdiğini görmek için link verdiğiniz makalenin devamını okuyun . Ad Ekleme adı verilen yöntemlerle doldurulmuş bir 'Aktarıcı' içeren bir kalıp kullandığı , sırayla aynı adın alanlarını ayarlayan Maaş eklediği ... Sonuçta tam olarak ayarlayıcı kalıbı kullanarak, sadece farklı adlandırma kuralları.

Bu, aynı uygulamayı koruyarak onları yeniden adlandırarak tekillerin tuzaklarından kaçındığınızı düşünmek gibidir.


Oop'ta, sözde uzmanlar bunun böyle olmadığını söylüyor gibi görünüyor.
IntelliData

8
Sonra tekrar, bazı insanlar 'Asla OOP kullanma' demiştir
JacquesB

7
FTR, bence daha fazla “uzman” hala hiçbir şey yaratmamaktansa, her şey için anlamsızca alıcılar / belirleyiciler yaratmayı öğretiyor . IMO, ikincisi daha az yanlış tavsiye niteliğindedir.
19:15

4
@leftaroundabout: Tamam, ama 'her zaman' ile 'asla' arasında 'uygun olduğunda kullan' olan bir orta yol öneriyorum.
JacquesB

3
Bence sorun, birçok programcının her nesneyi (veya çok fazla nesneyi) bir DTO'ya dönüştürmesidir. Bazen gerekli olurlar, ancak verileri davranıştan ayırdıklarından mümkün olduğu kadar kaçının. (Bir dindar OOPer olduğunuzu varsayarsak)
user949300, 19:15

11

CustomerSınıfını bir veri nesnesinden dönüştürmek için kendimize veri alanları hakkında şu soruları sorabiliriz:

{Data field} 'ı nasıl kullanmak istiyoruz? {Data field} nerede kullanılır? {Data field} kullanımı sınıfa taşınabiliyor mu?

Örneğin:

  • Amacı nedir Customer.Name?

    Olası cevaplar, adı bir giriş web sayfasında görüntüleyin, müşteriye postalamada adı kullanın.

    Hangi yöntemlere yol açar:

    • Customer.FillInTemplate (...)
    • Customer.IsApplicableForMailing (...)
  • Amacı nedir Customer.DOB?

    Müşterinin yaşını doğrulamak. Müşterinin doğum gününde indirim. Postalar.

    • Customer.IsApplicableForProduct ()
    • Customer.GetPersonalDiscount ()
    • Customer.IsApplicableForMailing ()

Yorumlar göz önüne alındığında, örnek nesne Customer- hem veri nesnesi hem de kendi sorumlulukları olan "gerçek" nesne olarak - çok geniştir; yani çok fazla özellik / sorumluluğu var. Bu, Customer(özelliklerini okuyarak) Customerbağlı olarak birçok bileşene veya birçok bileşene bağlı olarak yol açar . Belki de farklı müşteri görüşleri vardır, belki de her birinin kendine özgü sınıf 1'i olmalıdır :

  • AccountParasal işlemler bağlamında müşteri muhtemelen yalnızca şunları yapmak için kullanılır:

    • insanlara para transferlerinin doğru kişiye gittiğini belirleme konusunda yardım etmek; ve
    • grup Accounts

    Bu müşteri gibi alanları ihtiyacı yoktur DOB, FavouriteColour, Telve belki hatta Address.

  • Bir bankacılık bağlamında oturum açan bir kullanıcı bağlamında müşteri.

    İlgili alanlar:

    • FavouriteColourkişiselleştirilmiş temalar biçiminde gelebilecek;
    • LanguagePreferences, ve
    • GreetingName

    Alıcıları ve ayarlayıcıları ile özellikleri yerine, bunlar tek bir yöntemle yakalanabilir:

    • PersonaliseWebPage (Şablon sayfası);
  • Pazarlama ve kişiselleştirilmiş postalama bağlamında müşteri.

    Burada bir veri nesnesinin özelliklerine dayanmak yerine, nesnenin sorumluluklarından başlayarak; Örneğin:

    • IsCustomerInterestedInAction (); ve
    • GetPersonalDiscounts ().

    Bu müşteri nesnesinin bir FavouriteColourmülkiyeti ve / veya Addressmülkiyeti olması önemsiz hale gelir: belki uygulama bu özellikleri kullanır; ancak bazı makine öğrenme tekniklerini kullanabilir ve müşterinin hangi ürünlerle ilgilenebileceğini keşfetmek için müşteri ile önceki etkileşimlerini kullanabilir.


1. Elbette, Customerve Accountsınıflar örnek teşkil ediyordu ve basit bir örnek veya ev ödevi çalışması için, bu müşteriyi bölmek fazla müstehcen olabilir, ancak bölme örneğinde, bir veri nesnesini bir nesneye dönüştürmenin yönteminin gösterilmesini umuyorum sorumluluklar işe yarayacak.


4
Olumlu bir soru çünkü aslında soruyu yanıtlıyorsunuz :) Ancak, önerilen çözümlerin yalnızca alıcı / belirleyici olmaktan çok daha kötü olduğu açıktır - örneğin, FillInTemplate endişeler ilkesinin ayrılmasını açıkça ihlal etmektedir. Hangi sadece sorunun öncülünün hatalı olduğunu göstermeye gider.
JacquesB

@Kasper van den Berg: Müşteride genellikle olduğu gibi birçok özelliğe sahip olduğunuzda, başlangıçta bunları nasıl belirlersiniz?
IntelliData

2
@IntelliData, değerlerinizi büyük olasılıkla bir veritabanından, XML, vb. Mükemmel değil, ancak genel ayarlayıcılardan kaçınabilirsiniz. (Daha fazla bilgi için cevabımı inceleyin)
user949300

6
Müşteri sınıfının bunların hiçbirini bilmesi gerektiğini sanmıyorum.
CodesInChaos

Peki ya böyle bir şey Customer.FavoriteColor?
Gabe

8

TL; DR

  • Davranış için modelleme iyidir.

  • İyi (!) Soyutlamalar için modelleme daha iyidir.

  • Bazen veri nesneleri gerekli olabilir.


Davranış ve Soyutlama

Alıcılardan ve ayarlayıcılardan kaçınmak için birkaç neden var. Bunlardan biri, not ettiğiniz gibi, veri modellemekten kaçınmaktır. Bu aslında küçük bir sebep. En büyük nedeni soyutlama sağlamaktır.

Örnekte açık olan banka hesabı ile: Bir setBalance()yöntem gerçekten kötü olurdu çünkü bir bakiye ayarlamak bir hesap için kullanılması gereken bir şey değildir. Hesabın davranışı, mevcut bakiyesinden mümkün olduğunca soyutlanmalıdır. Para çekme işleminin başarısız olup olmadığına karar verirken bakiyeyi dikkate alabilir, cari bakiyeye erişim sağlayabilir, ancak bir banka hesabıyla etkileşimi değiştirmek kullanıcının yeni bakiyeyi hesaplamasını gerektirmemelidir. Hesabın kendisi böyle yapmalı.

Hatta bir çift deposit()ve withdraw()yöntem bile bir banka hesabını modellemek için ideal değildir. Daha iyi bir yol transfer(), başka bir hesabı ve argüman olarak bir miktarı alan yalnızca bir yöntemi sağlamak olacaktır . Bu, hesap sınıfının sisteminizde yanlışlıkla para yaratmamanızı / tahrip etmemenizi, çok kullanışlı bir soyutlama sağlamasını ve kullanıcılara özel hesapların kullanımını zorlayacağından daha fazla bilgi vermesini sağlar. kazanılmış / yatırılmış / kaybedilen para (bkz. çift ​​muhasebe ). Elbette, bir hesabın her kullanımı bu soyutlama seviyesine ihtiyaç duymaz, ancak sınıflarınızın ne kadar soyutlama sağlayabileceğini düşünmeye değer.

Soyutlama sağlamak ve veri dahili bilgilerini gizlemek her zaman aynı şey değildir. Hemen hemen her uygulama etkili bir şekilde sadece veri olan sınıfları içerir. Kitaplar, sözlükler ve diziler sık ​​rastlanan örneklerdir. Bir noktanın x koordinatını kullanıcıdan gizlemek istemezsiniz. Bir noktada yapabileceğiniz / yapmanız gereken çok az soyutlama var.


Müşteri Sınıfı

Bir müşteri kesinlikle sisteminizde yararlı soyutlamalar sağlamaya çalışması gereken bir varlıktır. Örneğin, büyük olasılıkla bir alışveriş sepetiyle ilişkilendirilmeli ve alışveriş sepetinin ve müşterinin kombinasyonu, satın alma işlemine izin vermelidir; bu, kendisine istenen ürünleri göndermek, parasını almak (seçtiği ödemesini hesaba katarak) yöntem) vb.

Alıcı, bahsettiğiniz tüm verilerin yalnızca bir müşteriyle ilişkili olmadığı, tüm bu verilerin de değişebilir olduğu. Müşteri hareket edebilir. Kredi kartı şirketlerini değiştirebilirler. E-posta adreslerini ve telefon numaralarını değiştirebilirler. Heck, isimlerini ve / veya cinsiyetlerini bile değiştirebilirler! Bu nedenle, tam özellikli bir müşteri sınıfı gerçekten de tüm bu veri öğelerine tam olarak değiştirilebilen bir erişim sağlamalıdır.

Yine de, belirleyiciler / önemsiz olmayan hizmetleri sunmalıdır edebilirsiniz: Onlar Aynı şekilde, "getters" e-posta-adresses sağlamak gibi üst düzey hizmetleri sağlayabilir e-posta adresleri, posta adresleri doğrulanması vb doğru biçimde sağlayabilirsiniz Name <user@server.com>biçimi ad alanlarını ve yatırılan e-posta adresini kullanarak veya doğru şekilde biçimlendirilmiş bir posta adresini vb. kullanarak kullanma. Tabii ki, bu üst düzey işlevlerden hangisi mantıklı olur? Tamamen overkill olabilir veya başka bir sınıfı doğru şekilde yapması için çağrı yapabilir. Soyutlama seviyesi seçimi kolay değildir.


kulağa hoş gelmiyor olsa da, seks kısmına katılmıyorum ...;)
IntelliData

6

Kasper'ın cevabını genişletmeye çalışırken, belirsizlere karşı durmak ve onları elemek en kolayı. Oldukça belirsiz, el dalgası (ve umarım komik) argümanında:

Müşteri.Adı ne zaman değişir?

Nadiren. Belki evlendiler. Veya tanık korumasına girdi. Ancak bu durumda, akrabalarının ve diğer bilgilerin yanında evlerini kontrol etmek ve muhtemelen değiştirmek isteyebilirsiniz.

DOB ne zaman değişecek?

Sadece ilk oluşturmada veya bir veri girişi azaltmada. Ya da bir Domincan beyzbol oyuncusu ise. :-)

Bu alanlara rutin normal ayarlayıcılarla erişilememelidir. Belki bir Customer.initialEntry()yönteminiz veya Customer.screwedUpHaveToChange()özel izinler gerektiren bir yönteminiz olabilir. Fakat ortak bir Customer.setDOB()yönteminiz yok.

Genellikle bir Müşteri bir veritabanından, bir REST API'sinden, bir miktar XML'den, ne olursa olsun okunur. Bir yönteme sahip olun Customer.readFromDB(), ya da SRP / endişelerin ayrılması konusunda daha katıysanız, örneğin CustomerPersisterbir read()yöntemi olan bir nesne gibi ayrı bir üreticiniz olur . Dahili olarak, bir şekilde alanları belirlerler (paket erişimini veya iç sınıf, YMMV kullanmayı tercih ederim). Fakat yine de, kamuoyu belirleyenlerden kaçının.

(Soru olarak Zeyilname biraz değişti ...)

Diyelim ki başvurunuz ilişkisel veritabanlarını yoğun şekilde kullanıyor. Yapması Customer.saveToMYSQL()ya da Customer.readFromMYSQL()yöntemleri olması aptalca olurdu . Bu, somut, standart olmayan ve varlık değiştirmesi muhtemel olanı için istenmeyen bağlantılar yaratır . Örneğin, şemayı değiştirdiğinizde veya Postgress veya Oracle olarak değiştirdiğinizde.

Ancak, IMO, bir karşı çift Müşteriye kesinlikle kabul edilebilir, soyut standardı , ResultSet. Ayrı bir yardımcı nesne ( CustomerDBHelpermuhtemelen bir alt sınıfı olan onu arayacağım AbstractMySQLHelper), DB'nizin tüm karmaşık bağlantılarını bilir, zorlu optimizasyon ayrıntılarını bilir, tabloları, sorguyu, birleştirmeleri vb. Bilir (veya kullanır. ORM hazırda bekleme gibi) ResultSet oluşturmak için. Nesneniz , değişmesi muhtemel olmayan soyut bir standartResultSet olan ile konuşuyor . Temel veritabanını değiştirdiğinizde veya şemayı değiştirdiğinizde, Müşteri değişmez , ancak CustomerDBHelper bunu yapar. Şanslıysanız, Müşteri, Tüccar, Nakliye vb. İçin değişiklikleri otomatik olarak yapan sadece AbstractMySQLHelper ...

Bu şekilde alıcı ve ayarlayıcılara duyulan ihtiyacı önleyebilir veya azaltabilirsiniz.

Ve Holub makalesinin ana noktası, her şey için alıcılar ve ayarlayıcılar kullandıysanız ve veritabanını değiştirirseniz, yukarıdakileri karşılaştırır ve karşılaştırır.

Benzer şekilde, çok fazla XML kullandığınızı varsayalım. IMO, Müşterinizi Python xml.etree.ElementTree veya bir Java org.w3c.dom.Element gibi soyut bir standartla eşleştirmek iyidir . Müşteri bundan kendini alır ve ayarlar. Yine, alıcılar ve ayarlayıcılara olan ihtiyacı (belki de) azaltabilirsiniz.


Bir Oluşturucu Deseni kullanmanın uygun olduğunu söyler misiniz?
IntelliData

Oluşturucu, bir nesnenin yapısını daha kolay ve daha sağlam hale getirmek ve eğer isterseniz nesnenin değişmez olmasına izin vermek için kullanışlıdır. Bununla birlikte, yine de (kısmen), temeldeki nesnenin bir DOB alanına sahip olduğu gerçeğini ortaya koymaktadır, bu yüzden hepsi bir nihayetinde değildir.
kullanıcı949300,

1

Alıcıların ve ayarlayıcıların bulunması sorunu, bir sınıfın iş mantığında bir şekilde kullanılabileceği gerçeği olabilir, ancak verileri bir veritabanından veya dosyadan veya başka bir kalıcı depodan seri hale getirmek / seri hale getirmek için yardımcı sınıflarınız da olabilir.

Verilerinizi saklamanın / almanın birçok yolu olduğundan ve veri nesnelerini depolandıklarından ayırmak istediğiniz için, kapsülleme, bu üyelerin herkese açık hale getirilmesiyle veya alıcılar aracılığıyla erişilebilir hale getirilmesi yoluyla "tehlikeye atılabilir" olabilir. Neredeyse halka açık hale getirmek kadar kötü olan belirleyiciler

Bunun etrafında çeşitli yollar var. Bunun bir yolu verileri bir "arkadaşa" sunmaktır. Arkadaşlık kalıtsal olmamasına rağmen, arkadaştan bilgi isteyen herhangi bir seri hale getirici ile, yani temel seri hale getirici bilgiyi "ileten" ile bunun üstesinden gelinebilir.

Sınıfınız genel bir "fromMetadata" veya "toMetadata" yöntemine sahip olabilir. Meta veriden bir nesne oluşturur, bu yüzden iyi bir kurucu olabilir. Dinamik olarak yazılmış bir dil ise, meta veriler böyle bir dil için oldukça standarttır ve muhtemelen bu tür nesneler oluşturmanın birincil yoludur.

Diliniz özellikle C ++ ise, bunun bir yolu kamuya açık bir "yapıya" sahip olmak ve sınıfınız için bu "yapı" nın bir üyesi olarak ve aslında depolayacağınız tüm verilerin bir örneğine sahip olmaktır. saklanmak için alın. Verilerinizi birden çok biçimde okumak / yazmak için kolayca "sarmalayıcılar" yazabilirsiniz.

Diliniz C # veya Java ise "yapıya sahip" değilse, benzer şekilde yapabilirsiniz ancak yapı artık ikincil bir sınıftır. Verilerin veya sözleşmelerin gerçek bir "mülkiyeti" kavramı yoktur, bu nedenle verilerinizi içeren bir sınıfın bir örneğini verirseniz ve herkese açıksa, elde tuttuğu her şeyi değiştirebilir. Bu pahalı olsa da "klonlayabilirsin". Alternatif olarak, bu sınıfın özel verileri olmasına rağmen erişimcileri kullanabilirsiniz. Bu, sınıfınızın kullanıcılarına verilere ulaşma konusunda dolambaçlı bir yol sağlar, ancak bu sınıfınızla doğrudan bir arayüz değildir ve gerçekten bir kullanım örneği olan sınıf verilerini depolamakta da bir ayrıntıdır.


0

OOP, nesnelerin içinde saklayıcı ve saklayıcı davranışlarla ilgilidir. Nesneler kara kutulardır. Bu bir şeyi tasarlamanın bir yoludur. Varlık birçok durumda bir başka bileşenin iç durumunu bilmek zorunda değildir ve bunu bilmek zorunda olmamak daha iyidir. Bu fikri esas olarak arayüzler ile veya görünürlüğü olan bir nesnenin içinde uygulayarak ve sadece izin verilen fiillerin / eylemlerin arayanlar için uygun olduğunu uygulayabilirsiniz.

Bu bir tür sorun için iyi sonuç veriyor. Örneğin, bireysel UI bileşenini modellemek için kullanıcı arayüzlerinde. Bir metin kutusuyla etkileşime girdiğinizde, yalnızca metni ayarlama, alma veya metin değiştirme olayını dinleme konusunda kesintiye uğrarsınız. Normalde imlecin bulunduğu yer, metni çizmek için kullanılan yazı tipi veya klavyenin nasıl kullanıldığı ile ilgili olarak müdahale edilmezsiniz. Kapsülleme burada çok şey sağlar.

Aksine, bir şebeke servisi aradığınızda, açık bir girdi sağlarsınız. Genellikle bir gramer (JSON veya XML'de olduğu gibi) vardır ve servisi arama seçeneğinin gizlenmesi için hiçbir neden yoktur. Fikir, hizmeti istediğiniz gibi arayabileceğiniz ve veri formatının herkese açık ve yayınlanmış olmasıdır.

Bu durumda, ya da diğer birçok (bir veritabanına erişim gibi) gerçekten paylaşılan verilerle çalışırsınız. Gibi onu saklamak için hiçbir sebep yoktur, aksine onu kullanılabilir hale getirmek istiyorum. Okuma / yazma erişimi veya veri kontrolü tutarlılığı endişesi olabilir, ancak bu özünde, eğer kamusal ise çekirdek kavramı.

Kapsüllemekten kaçınmak ve işleri halka açık yapmak ve net olarak nesnelerden kaçınmak istediğiniz tasarım gereksinimi için. Gereksinim duyduğunuz şey cisimler değil, C yapıları veya eşdeğerleridir.

Ama aynı zamanda Java gibi dillerde de olabilir, modelleyebileceğiniz tek şey nesneler veya nesneler dizileridir. Onları nesneler birkaç yerli tür tutabilir (int, float ...) ama hepsi bu. Ancak nesneler aynı zamanda sadece kamuya açık alanlara sahip basit bir yapı gibi davranabilirler.

Dolayısıyla, verileri modelliyorsanız, nesnelerin içindeki sadece ortak alanlarla yapılabilir, çünkü daha fazlasına ihtiyacınız yoktur. Kapsülleme kullanmıyorsunuz çünkü ihtiyacınız yok. Bu, birçok dilde bu şekilde yapılır. Java'da, tarihsel olarak, alıcı / ayarlayıcı ile en azından okuma / yazma denetimine sahip olabileceğiniz (örneğin ayarlayıcı ekleyerek) ve içgüdüleme API'sini kullanan araç ve çerçevelerin alıcı / ayarlayıcı yöntemlerini arayacağı ve kullanacağı standart bir gül içeriği otomatik olarak doldur veya tezleri, otomatik olarak oluşturulan kullanıcı arayüzünde değiştirilebilir alanlar olarak göster.

Ayrıca setter yönteminde bazı mantık / kontrol ekleyebileceğiniz bir argüman var.

Gerçekte, alıcıların / ayarlayıcıların neredeyse hiç bir gerekçesi yoktur, çünkü en sık saf verileri modellemek için kullanılırlar. Nesnelerinizi kullanan çerçeveler ve geliştiriciler, alıcı / ayarlayıcının, alanları yeniden ayarlamaktan / almaktan başka bir şey yapmamasını bekler. Alıcı / belirleyici ile, kamuya açık alanlarda yapılabileceklerden daha fazlasını yapamazsınız.

Ama bu eski alışkanlıkların ve yaşlılık alışkanlıklarının çıkarılması zor ... Ne olduğunu ve ne olduklarını daha iyi anlayabilmeleri için arka plandan yoksunlarsa, her yere kör / pastacı kör koyarsanız, meslektaşlarınız veya öğretmeniniz tarafından tehdit altında bile olabilirsiniz. değil.

Muhtemelen tüm tez alıcısı / alıcısı olan kazan kodunu kullanabilmek için dili değiştirmeniz gerekecektir. (C # veya lisp gibi). Bana göre alıcılar / pasör bir milyar dolarlık hata daha ...


6
C # özellikleri gerçekten alıcılar ve ayarlayıcılardan daha fazla kapsülleme yapmazlar ...
IntelliData

1
[Gs] etter'lerin avantajı, genellikle yapmadığınız şeyleri yapabilmeniz (değerleri kontrol etmek, gözlemcilere bildirmek), var olmayan alanları taklit etmek vb. Ve temel olarak daha sonra değiştirebileceğinizdir . İle Lombok , klişe gitti: @Getter @Setter class MutablePoint3D {private int x, y, z;}.
maaartinus,

1
@maaartinus Elbette [gs] etter, başka herhangi bir yöntemde olduğu gibi her şeyi yapabilir. Bu, herhangi bir arayan kodunun, belirledikleri herhangi bir değerin, bir istisna atabilecek, değiştirilebilecek veya potansiyel olarak değişiklik bildirimi gönderebileceğinin ... veya başka bir şey olduğunu bilmesi gerektiği anlamına gelir. Bu az çok [gs] etter bir alana erişim sağlamak yerine hakem kodu yapıyor.
Nicolas Bousquet

@IntelliData C # özellikleri, 1 plakanın yararsız tek karakterini yazmamaya ve tüm bu alıcı / alıcı öğelerine bakmamaya izin verir ... Bu, proje lombok'tan daha iyi bir başarıdır. Ayrıca benim için sadece alıcıları / ayarlayıcıları olan bir POJO kapsülleme sağlamak için burada değil, bir hizmetle takas olarak okumak veya yazmak için serbest olan bir veri formatı yayınlamak için burada. Enkapsülasyon, tasarım gereksiniminin tam tersidir.
Nicolas Bousquet

Özelliklerin gerçekten iyi olduğunu sanmıyorum. Elbette, önek ve parantezleri, yani, arama başına 5 karakter, ancak kaydettiğiniz 1. karıştıran alanlar gibi görünüyorlar. 2. onlar yansıması için desteğe ihtiyacınız olan ek bir şey vardır. Önemli bir şey değil, fakat büyük bir avantajı da yok (Java + Lombok'a kıyasla; saf Java açık bir şekilde kayıp.)
maaartinus

0

Örneğin, müşteri hakkındaki bilgileri, ör. Ad, DOB, Tel, Adres vb. Birçoğunu koruyacak bir Müşteri sınıfı verildiğinde, tüm bu özellikleri almak ve ayarlamak için alıcı / ayarlayıcılar nasıl önlenir? Tüm bu verileri doldurmak için hangi 'Davranış' tipi yöntem yazılabilir?

Bence bu soru çok pahalı çünkü verileri doldurmak için davranış yöntemleri hakkında endişe duyuyorsunuz, ancak Customernesne sınıfının kapsülleme amaçlı olduğu hangi davranışın olduğuna dair herhangi bir gösterge göremiyorum .

Yazılımınızı kullanarak farklı görevler gerçekleştiren bir kullanıcı / aktör olarak 'Müşteri' olan bir nesne sınıfıCustomer olarak karıştırmayın .

Eğer derken müşteri hakkında bilgi sürü halinde tutan bir Müşteri sınıfını verilen Müşteri sınıf bir kayanın ayırd edici küçük var gibi o kadar davranış gider gibi görünüyor. Bir Rockrenge sahip olabilir, ona bir isim verebilir, mevcut adresini saklamak için bir alana sahip olabilirsiniz, ancak bir kayadan herhangi bir zekice davranış beklemiyoruz.

Alıcıların / alıcıların kötülük olduğuna ilişkin bağlantılı makaleden:

OO tasarım süreci, kullanım durumlarına odaklanır: bir kullanıcı, yararlı sonuçlara sahip olan bağımsız görevleri gerçekleştirir. (Oturum açma, bir kullanım durumu değildir, çünkü sorunlu alanda yararlı bir sonuçtan yoksundur. Bir geri ödeme çekme, bir kullanım durumu olur.) Bir OO sistemi, daha sonra, bir kullanım durumunu içeren çeşitli senaryoları yürütmek için gereken faaliyetleri uygular.

Tanımlanan herhangi bir davranış olmadan, bir kayaya atıfta bulunmak Customer, sadece izlemek istediğiniz bazı özelliklere sahip bir nesne olduğu gerçeğini değiştirmez ve alıcılardan uzaklaşmak için hangi hileleri kullanmanız gerektiği önemli değildir. belirleyiciler. Bir kayanın geçerli bir adı olup olmadığını umursamaz ve bir kayanın bir adresin geçerli olup olmadığını bilmesi beklenmez.

Sipariş sisteminiz Rockbir satın alma siparişiyle ilişkilendirebilir ve Rockbir adres tanımlanmış olduğu sürece sistemin bir kısmı bir öğenin bir kayanın teslim edilmesini sağlayabilir.

Bu durumların hepsinde bu Rocksadece bir Veri Nesnesidir ve varsayımlar yerine yararlı sonuçlarla özel davranışlar tanımlayana kadar bir olmaya devam edecektir.


Bunu dene:

'Müşteri' kelimesini potansiyel olarak farklı 2 anlamla aşırı yüklemekten kaçındığınızda, olayları kavramsallaştırmayı kolaylaştırması gerekir.

Bir Rocknesne Sipariş veriyor mu, yoksa sisteminizde eylemleri tetiklemek için UI öğelerine tıklayarak bir insanın yaptığı bir şey mi?


Müşteri, birçok şey yapan bir aktördür, ancak onunla ilişkili birçok bilgiye sahip olabilir; Bu, bir aktör olarak 1 ve veri nesnesi olarak 1 olmak üzere 2 ayrı sınıf oluşturmayı haklı kılıyor mu?
IntelliData

@IntelliData zengin nesneleri katmanlar arasında geçirerek işlerin zorlaştığı yerdir. Nesneyi denetleyiciden bir görünüme gönderiyorsanız, görünüm nesnenin genel sözleşmesini anlamalıdır (örneğin, JavaBeans standardı). Nesneyi telin üzerinden gönderiyorsanız, JaxB veya benzeri bir nesneyi aptal bir nesneye yeniden yerleştirebilmelidir (çünkü onlara tam zengin nesneyi vermediniz). Zengin nesneler veri işlemek için harika - durum aktarımı için yetersiz. Tersine, dilsiz nesneler veriyi işlemek için kötüdür ve durumu aktarmak için tamamdır.

0

Buraya 2 sent ekliyorum, SQL konuşan nesneler yaklaşımından bahsediyorum .

Bu yaklaşım, kendi kendine yeten nesne kavramına dayanmaktadır. Davranışını uygulamak için gereken tüm kaynaklara sahiptir. İşini nasıl yapması gerektiğini söylemeye gerek yok - bildirimde bulunma talebi yeterli. Ve bir nesnenin kesinlikle tüm verilerini sınıf özellikleri olarak tutması gerekmez. Gerçekten nereden geldikleri önemli değil - ve olmamalı.

Toplam hakkında konuşmak , değişmezlik de bir sorun değil. Diyelim ki, toplamın tutabileceği bir durum diziniz var: Saga olarak toplam kök Her durumu bağımsız nesne olarak uygulamak tamamen uygun. Muhtemelen daha da ileri gidebilirsin: etki alanı uzmanınla sohbet et. Muhtemelen, bu agregayı birleşik bir varlık olarak görmemesidir. Muhtemelen her devletin kendi nesnesini hakettiği kendi anlamı vardır.

Son olarak, nesne bulma sürecinin alt sistemlere sistemin ayrıştırılması ile çok benzer olduğunu not etmek isterim . Her ikisi de davranışa dayanıyor, başka bir şey değil.

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.