Özel nesnelerden parametre olarak kaçınmalı mıyız?


49

Özel bir nesnem olduğunu varsayalım Öğrenci :

public class Student{
    public int _id;
    public String name;
    public int age;
    public float score;
}

Ve bir öğrencinin bilgilerini göstermek için kullanılan bir sınıf, Pencere :

public class Window{
    public void showInfo(Student student);
}

Oldukça normal görünüyor, ancak Window'un tek tek test etmenin kolay olmadığını gördüm çünkü işlevi çağırmak için gerçek bir Student nesnesine ihtiyaç duyuyor . Bu yüzden showInfo öğesini doğrudan Student nesnesini doğrudan kabul etmeyecek şekilde değiştirmeye çalışıyorum :

public void showInfo(int _id, String name, int age, float score);

Pencereyi tek tek test etmek daha kolay :

showInfo(123, "abc", 45, 6.7);

Ancak değiştirilmiş versiyonun başka problemleri olduğunu buldum:

  1. Öğrenci Değiştir (örneğin: yeni özellikler ekle) showInfo yönteminin imzasını değiştirmeyi gerektirir

  2. Eğer Öğrenci birçok özelliğe sahipse, Öğrenci metodunun imzası çok uzun olacaktır.

Öyleyse, özel nesneleri parametre olarak kullanmak veya nesnelerdeki her özelliği parametre olarak kabul etmek, hangisi daha iyi korunabilir?


40
Ve 'gelişmiş'iniz' showInfogerçek bir String, gerçek bir float ve iki gerçek giriş gerektirir. Gerçek bir Stringnesneyi gerçek bir nesneyi sağlamaktan daha iyi nasıl sağlar Student?
Bart van Ingen Schenau

28
Parametrelerin doğrudan girilmesi ile ilgili büyük bir sorun: Artık iki intparametreniz var. Çağrı sitesinden, onları doğru sırayla geçirdiğiniz doğrulanmadı. Ya takas yaparsanız idve age, ya da firstNameve lastName? Yüzünüze patlayana kadar tespit edilmesi çok zor olabilecek potansiyel bir başarısızlık noktası ortaya koyuyorsunuz ve her arama sitesine ekliyorsunuz .
Chris Hayes

38
@ ChrisHayes ah, eski showForm(bool, bool, bool, bool, int)yöntem - Ben onları seviyorum ...
Örümcek Boris

3
@ ChrisHayes en azından JS değil ...
Jens Schauder

2
Testlerin hiç beğenilmeyen bir özelliği: Testlerde kendi nesnelerinizi oluşturmak / kullanmak zorsa,
API'niz

Yanıtlar:


131

İlgili parametreleri gruplamak için özel bir nesne kullanmak aslında önerilen bir kalıptır. Bir yeniden düzenleme olarak, Giriş Parametre Nesnesi olarak adlandırılır .

Senin sorunun başka yerde yatıyor. İlk olarak, genel WindowÖğrenci hakkında hiçbir şey bilmemelidir. Bunun yerine, StudentWindowyalnızca görüntüleme hakkında bir şeyler biliyor olmalısınız Students. İkincisi, Studentsınamayı ciddi şekilde karmaşık hale getirecek karmaşık bir mantık içermediği StudentWindowsürece, sınama örneği oluşturma konusunda kesinlikle bir sorun yoktur . Bu mantığı varsa o zaman bir arayüz yapmak ve alay etmek tercih edilmelidir.StudentStudentWindowStudent


14
Yeni nesne gerçekten mantıklı bir gruplandırma değilse, başınızı belaya sokabileceğinize dair uyarı. Her parametreyi tek bir nesneye atmaya çalışmayın; duruma göre karar ver. Sorunun içindeki örnek açıkça bunun için iyi bir aday gibi görünüyor. StudentGruplama mantıklı ve uygulamanın diğer alanlarda çıkabilirler muhtemeldir.
jpmc26

Zaten nesneye sahipseniz, bilerek konuşursak, örneğin Student, Bir Tüm Nesneyi Koru
abuzittin gillifirca

4
Ayrıca Demeter Yasasını da hatırlayın . Vurulacak bir denge var, ancak a.b.cyöntem alırsa tldr yapmaz a. Metodunuz, kabaca 4'ten fazla parametreye veya 2 seviye derinlikte mülk erişimine ihtiyaç duyduğunuz noktaya ulaşırsa, muhtemelen hesaba katılması gerekir. Ayrıca bunun bir rehber olduğuna dikkat edin - diğer tüm kurallar gibi, kullanıcının takdirine bağlı olmasını gerektirir. Körü körüne takip etme.
Dan Pantry

7
Bu cevabın ilk cümlesinin ayrıştırılması son derece zor buldum.
Helrich

5
@ Qwerky Çok şiddetle katılmıyorum. Öğrenci GERÇEKTEN, yalnızca öğrencinin durumu için bir konteyner olan nesne grafiğindeki bir yaprak (belki Ad, DateOfBirth vb. Gibi diğer önemsiz nesneleri engelleyen) gibi ses çıkarır. Bir öğrencinin inşa etmek zor olmasının bir nedeni yoktur çünkü bir kayıt türü olmalıdır. Test oluşturmak, bir öğrenci için iki katına çıkması zor testler ve / veya bazı fantezi izolasyon çerçevelerine ağır bağımlılık için bir reçete gibi geliyor.
sara,

26

Sen diyorsun

Tek tek test etmek kolay değildir, çünkü fonksiyonu çağırmak için gerçek bir Student nesnesine ihtiyaç duyar

Ancak pencerenize geçmek için sadece bir öğrenci nesnesi oluşturabilirsiniz:

showInfo(new Student(123,"abc",45,6.7));

Aramak çok daha karmaşık görünmüyor.


7
Sorun , hiçbiri kullanmayan s ve s ile birçok s ve s'ye Studentatıfta bulunan bir s Universityanlamına geldiğinde ortaya çıkar , ancak testlerin bunu yalnızca "öğrenmesini" sağlayan ve yalnızca ilgili öğrenciyi sağlamasına izin veren herhangi bir arayüz tanımlamamışsınızdır. tüm organizasyonu kurmadan veri. Örnek , düz bir veri nesnesidir ve dediğiniz gibi, testler onunla çalışmaktan mutlu olmalıdır. FacultyCampusProfessorBuildingshowInfoStudent
Steve Jessop

4
Sorun, Öğrenci, Profesörler ve Binalar ile birçok Fakülte ve Kampüsü ifade eden bir Üniversiteye başvurduğunda, kötülere dinlenmek yok.
abuzittin gillifirca 12:16

1
@ abuzittingillifirca, "Object Mother" bir çözümdür, ayrıca öğrenci nesneniz de çok karmaşık olabilir. Sadece UniversityId'e sahip olmak ve bir UniversityId'den bir Üniversite nesnesine verecek bir bağımlılık (enjeksiyon enjeksiyonu kullanarak) kullanmak daha iyi olabilir.
Ian

12
Öğrenci çok karmaşıksa veya başlatması zorsa, sadece onunla alay edin. Test, Mockito veya diğer dil eşdeğerleri gibi çerçevelerle daha güçlüdür.
Borjab

4
ShowInfo Üniversitesi umursamıyorsa, basit null değerine ayarlayın. Boşlar üretimde korkunç ve testlerde tanrının gönderdiği bir şey. Bir testte boş olarak bir parametre belirleyebilmek, niyeti iletti ve "bu şeye burada ihtiyaç duyulmadığını" söylüyor. Her ne kadar, sadece ilgili verileri içeren öğrenciler için bir çeşit görünüm modeli oluşturmayı da düşünmeme rağmen, showInfo'nun bir UI sınıfındaki bir yöntem gibi göründüğünü düşünürsek
sara,

22

Layman'ın terimiyle:

  • "Özel nesne" dediğiniz şeye genellikle basitçe nesne denir.
  • Önemsiz olmayan bir program veya API tasarlarken veya önemsiz olmayan bir API veya kütüphane kullanırken nesneleri parametre olarak geçirmekten kaçınamazsınız.
  • Nesneleri parametre olarak iletmek tamamen sorun değil. Java API'sine bir göz atın; nesneleri parametre olarak alan birçok arayüz göreceksiniz.
  • Kütüphanelerde, sizin ve benim gibi sadece ölümlülerin yazdığı yerlerde kullandığınız, yani yazdıklarımız “özel” değil , sadece onlar.

Düzenle:

As @ Tom.Bowen89 devletler çok daha karmaşık ShowInfo yöntemini test etmek değildir:

showInfo(new Student(8812372,"Peter Parker",16,8.9));

3
  1. Öğrenci örneğinizde, ShowInfo'ya geçmek üzere bir öğrenci oluşturmak için Öğrenci yapıcısını aramanın önemsiz olduğunu farz ediyorum. Yani sorun yok.
  2. Örneğin, Öğrenci bu soru için kasıtlı olarak önemsiz olduğunu ve inşa edilmesinin daha zor olduğunu varsayarak, bir test iki katı kullanabilirsiniz . Martin Fowler'in makalesinde seçim yapabileceğiniz test çiftleri, alaylar, taslaklar vb. İçin bir dizi seçenek bulunmaktadır.
  3. ShowInfo işlevini daha genel hale getirmek istiyorsanız, genel değişkenler üzerinde yinelenebilir veya belki de iletilen nesnenin genel erişimcileri arasında geçiş yapabilir ve hepsi için şov mantığını uygulayabilirsiniz. Sonra o sözleşmeye uyan herhangi bir nesneyi geçebilir ve beklendiği gibi çalışır. Bu, bir arayüz kullanmak için iyi bir yer olurdu. Örneğin, yalnızca öğrencilere bilgi göstermeyen, aynı zamanda arabirimi uygulayan herhangi bir nesnenin bilgisini de gösterebilen showInfo işlevine bir Showable veya ShowInfoable nesnesinde iletme (açıkçası bu arabirimlerin, nesneye ne kadar belirli veya genel olarak geçmesini istediğinize bağlı olarak daha iyi adlara ihtiyacı vardır) Olmak ve bir öğrencinin alt sınıfı nedir).
  4. İlkellerin etrafından geçmek genellikle daha kolaydır ve bazen performans için gerekli olabilir, ancak benzer kavramları bir arada grupladığınızda kodunuz genellikle ne kadar anlaşılır olur. Dikkat edilmesi gereken tek şey, fazladan yapmamaya çalışmak ve kurum fizzbuzz'ı bulmaktır .

3

Code Complete'teki Steve McConnell, özellikleri kullanmak yerine yöntemlere geçmenin faydalarını ve sakıncalarını tartışarak bu konuyu ele aldı.

Bazı detayları yanlış anlarsam beni affet, kitaba girdiğimden beri bir yıldan fazla olduğu gibi hafızamdan çalışıyorum:

Bir nesneyi kullanmamanın daha iyi olduğu sonucuna varıyor, bunun yerine sadece yöntem için kesinlikle gerekli olan özellikleri gönderiyor. Yöntemin, işlemlerinin bir parçası olarak kullanacağı özelliklerin dışındaki nesne hakkında bir şey bilmesi gerekmemelidir. Ayrıca, zaman içinde, eğer nesne hiç değişmişse, nesneyi kullanan yöntem üzerinde istenmeyen sonuçlar doğurabilir.

Ayrıca, birçok farklı argümanı kabul eden bir yöntemle karşılaşırsanız, bu muhtemelen yöntemin çok fazla iş yaptığının ve daha küçük yöntemlere bölünmesi gerektiğinin bir işaretidir.

Ancak, bazen, bazen, aslında çok fazla parametreye ihtiyacınız vardır. Verdiği örnek, birçok farklı adres özelliğini kullanarak tam bir adres oluşturan bir yöntem olacaktır (bunun hakkında düşündüğünüzde bir dize dizisi kullanılarak elde edilebilse de).


7
Kod 2 tamamlandı. Bu konuda özel bir sayfa var. Sonuç, parametrelerin doğru soyutlama seviyesinde olması gerektiğidir. Bazen bu, bütün bir nesneyi, bazen sadece bireysel öznitelikleri geçmeyi gerektirir.
GELMEKTEDİR

UV. Referans Kodu Tamamlandı çift ​​artı iyi. Test uygunluğu konusunda tasarımın güzel bir şekilde ele alınması. Tasarımı tercih et, McConnell'ın bağlamımızda söyleyeceğini düşünüyorum. Bu nedenle, mükemmel bir sonuç “parametre nesnesini tasarıma entegre etmek” olacaktır ( Studentbu durumda). Ve bu, testin tasarımın bütünlüğünü korurken en çok oyu alan cevabı tamamen benimseyerek, tasarımı bilgilendirmesidir .
radarbob

2

Tüm nesneyi geçerseniz, testler yazmak ve okumak çok daha kolaydır:

public class AStudentView {
    @Test 
    public void displays_failing_grade_warning_when_a_student_with_a_failing_grade_is_shown() {
        StudentView view = aStudentView();
        view.show(aStudent().withAFailingGrade().build());
        Assert.that(view, displaysFailingGradeWarning());
    }

    private Matcher<StudentView> displaysFailingGradeWarning() {
        ...
    }
}

Karşılaştırma için,

view.show(aStudent().withAFailingGrade().build());

Değerleri ayrı ayrı iletirseniz satır şöyle yazılabilir:

showAStudentWithAFailingGrade(view);

gerçek yöntem çağrısı gibi bir yere gömülmüş

private showAStudentWithAFailingGrade(StudentView view) {
    int someId = .....
    String someName = .....
    int someAge = .....
    // why have been I peeking and poking values I don't care about
    decimal aFailingGrade = .....
    view.show(someId, someName, someAge, aFailingGrade);
}

Gerçek yöntem çağrısını sınamaya koyamayacağınız nokta, API'nizin kötü olduğuna dair bir işarettir.


1

Mantıklı olanı, bazı fikirleri geçmelisin:

Test etmek daha kolay. Nesnelerin düzenlenmesi gerekiyorsa, en az yeniden düzenlemeyi gerektiren şey nedir? Bu işlevi başka amaçlar için tekrar kullanmak işe yarar mı? Amacını yapmak için bu işlevi vermem gereken en az bilgi nedir? (Ayrıştırarak - bu kodu tekrar kullanmanıza izin verebilir - bu işlevi yapan tasarım deliğinden aşağıya düşmek ve ardından yalnızca bu nesneyi kullanmak için her şeyi tıkamaktan kaçının.)

Tüm bu programlama kuralları sadece doğru yöne düşünmenizi sağlayacak rehberlerdir. Sadece bir kod canavarı oluşturma - emin değilseniz ve devam etmeniz gerekiyorsa, bir yön / kendi önerinizi veya bir öneri seçin ve düşündüğünüz bir noktaya değerseniz 'ah, bunu yapmalıydım. yol '- muhtemelen daha sonra geri dönüp oldukça kolay bir şekilde yeniden şekillendirebilirsiniz. (Örneğin, Öğretmen sınıfınız varsa - Öğrenci ile aynı özelliğe ihtiyaç duyar ve Kişi formunun herhangi bir nesnesini kabul etmek için işlevinizi değiştirirsiniz)

Ana nesnenin geçmesini sağlama konusunda en meyilli olurdum - çünkü nasıl kodlayacağımı bu fonksiyonun ne yaptığını daha kolay açıklayacağım.


1

Bunun etrafındaki ortak rota, iki işlem arasına bir arayüz yerleştirmektir.

public class Student {

    public int id;
    public String name;
    public int age;
    public float score;
}

interface HasInfo {
    public String getInfo();
}

public class StudentInfo implements HasInfo {
    final Student student;

    public StudentInfo(Student student) {
        this.student = student;
    }

    @Override
    public String getInfo() {
        return student.name;
    }

}

public class Window {

    public void showInfo(HasInfo info) {

    }
}

Bu bazen biraz dağınık oluyor ancak bir iç sınıf kullanıyorsanız Java'da işler biraz daha derine giriyor.

interface HasInfo {
    public String getInfo();
}

public class Student {

    public int id;
    public String name;
    public int age;
    public float score;

    public HasInfo getInfo() {
        return new HasInfo () {
            @Override
            public String getInfo() {
                return name;
            }

        };
    }
}

Daha sonra Windowsınıfı sadece sahte bir HasInfonesne vererek test edebilirsiniz .

Bunun Dekoratör Örüntüsünün bir örneği olduğundan şüpheleniyorum .

Eklendi

Kodun sadeliğinden kaynaklanan karışıklık var gibi görünüyor. İşte tekniği daha iyi gösterebilecek başka bir örnek.

interface Drawable {

    public void Draw(Pane pane);
}

/**
 * Student knows nothing about Window or Drawable.
 */
public class Student {

    public int id;
    public String name;
    public int age;
    public float score;
}

/**
 * DrawsStudents knows about both Students and Drawable (but not Window)
 */
public class DrawsStudents implements Drawable {

    private final Student subject;

    public DrawsStudents(Student subject) {
        this.subject = subject;
    }

    @Override
    public void Draw(Pane pane) {
        // Draw a Student on a Pane
    }

}

/**
 * Window only knows about Drawables.
 */
public class Window {

    public void showInfo(Drawable info) {

    }
}

ShowInfo yalnızca öğrencinin adını görüntülemek istiyorsa, neden sadece adı iletmiyorsunuz? anlamsal olarak anlamlı bir adlandırılmış alanın, dizinin neyi temsil ettiği konusunda ipucu içermeyen bir ip içeren bir soyut ara yüze sarılması, hem bakım hem de anlaşılabilirlik açısından, BÜYÜK bir düşüşe benziyor.
sara,

@kai - İade türü için Studentve Stringburada kullanımı tamamen gösterim amaçlıdır . Muhtemelen eğer çizilirse çizilmesi getInfogibi ek parametreler olacaktır Pane. Buradaki kavram, işlevsel bileşenleri orijinal nesnenin dekoratörleri olarak iletmektir .
OldCurmudgeon

Bu durumda, öğrenci birliğini UI çerçevenize sıkıca bağlarsınız, bu daha da kötü gelir ...
sara

1
@kai - Tam tersi. Kullanıcı Arayüzüm yalnızca HasInfonesneler hakkında bilgi sahibidir. StudentNasıl olunacağını biliyor.
OldCurmudgeon

Eğer getInfogeri dönüş boşluğu verirseniz, onu Paneçizmek için bir pas verin , o zaman uygulama ( Studentsınıfta) aniden sallanmaya ya da ne kullanıyorsanız kullanın. Eğer bir string döndürür ve 0 parametre alırsanız, kullanıcı arayüzünüz sihirli varsayımlar ve örtük kuplaj olmadan string ile ne yapacağınızı bilemez. Eğer yaparsanız getInfogerçekte daha alakalı özelliklere sahip bazı görüş modelini iade Studentsınıf tekrar sunum mantığına birleştirilmiştir. Bu alternatiflerin hiçbirinin arzu edildiğini sanmıyorum
sara

1

Çok sayıda iyi yanıtınız var, ancak alternatif bir çözüm görmenize izin verebilecek birkaç öneri:

  • Örnekte, bir Pencereye (görünüşte görünüm düzeyinde bir nesne) geçirilen bir Öğrenci (açıkça bir model nesnesi) gösterilmektedir. Zaten bir tane yoksa, bir kullanıcı Arabirimi veya Presenter nesnesi, kullanıcı arayüzünüzü modelinizden izole etmenize olanak sağlayan yararlı olabilir. Denetleyici / sunum yapan kişi, UI testi için değiştirilebilecek bir arabirim sağlamalı ve model nesnelerine gönderme yapmak ve bunları test için her ikisinden de ayırabilmek için nesneleri görüntülemek için arabirimler kullanmalıdır. Bunları oluşturmak veya yüklemek için bazı soyut yollar sağlamanız gerekebilir (örn. Fabrika nesneleri, Depo nesneleri veya benzeri).

  • Model nesnelerinizin ilgili bölümlerini Veri Aktarım Nesnesine aktarmak, modeliniz çok karmaşık olduğunda arabirim oluşturmak için kullanışlı bir yaklaşımdır.

  • Öğrenci Arabirim Ayrıştırma İlkesini ihlal ediyor olabilir. Eğer öyleyse, çalışması kolay olan çoklu arayüzlere ayırmak faydalı olabilir.

  • Tembel Yükleme, büyük nesne grafiklerinin oluşturulmasını kolaylaştırabilir.


0

Bu aslında iyi bir soru. Buradaki asıl mesele, biraz belirsiz olabilen jenerik "nesne" teriminin kullanılmasıdır.

Genel olarak, klasik bir OOP dilinde, "nesne" terimi "sınıf örneği" anlamına gelmiştir. Sınıf örnekleri oldukça ağır olabilir - kamusal ve özel mülkler (ve bunlar arasındakiler), yöntemler, kalıtım, bağımlılıklar, vb. Gerçekten de bazı mülkleri geçmek için böyle bir şey kullanmak istemezsiniz.

Bu durumda, bir nesneyi sadece bazı ilkel maddeleri tutan bir kap olarak kullanıyorsunuz. C ++ 'da bunun gibi nesneler structs(ve hala C # gibi dillerde varlar) olarak bilinirdi . Aslında, yapılar tam olarak konuştuğunuz kullanım için tasarlandı - mantıklı bir ilişki kurduklarında ilgili nesneleri ve ilkelleri birlikte grupladılar.

Bununla birlikte, modern dillerde, kodu yazarken yapı ile sınıf arasında gerçekten bir fark yoktur , bu nedenle bir nesneyi kullanmanız yeterlidir. (Ancak, perde arkasında, bilmeniz gereken bazı farklılıklar vardır - örneğin, bir yapı referans türü değil, bir değer türüdür.) Temel olarak, nesneyi basit tuttuğunuz sürece, kolay olacak manuel olarak test etmek için. Modern diller ve araçlar, bunu biraz azaltmanıza izin verir, ancak (arayüzler, alaycı çerçeveler, bağımlılık enjeksiyonu vb.)


1
Bir referansı geçmek pahalı değildir, nesne bir milyar terabayt büyük olsa bile, referans hala çoğu dilde yalnızca bir int boyutundadır. Alma yönteminin çok büyük bir api'ye maruz bırakılıp bırakılmadığı ve olayları istenmeyen bir şekilde birleştirmeniz durumunda daha fazla endişelenmelisiniz. İş nesnelerini ( Student) görünüm modellerine ( StudentInfoveya StudentInfoViewModelvb.) Çeviren bir eşleme katmanı oluşturmayı düşünürdüm , ancak gerekli olmayabilir.
sara,

Sınıflar ve yapılar çok farklı. Biri değere göre (bir kopyanın alındığı yöntem anlamına gelir ) ve diğeri referansa göre iletilir (alıcı sadece orijinali işaret eder). Bu farkı anlamamak tehlikelidir.
RubberDuck

@kai Bir referans göndermenin pahalı olmadığını biliyorum. Demek istediğim, tam bir sınıf örneği gerektiren bir işlev yaratmanın, o sınıfın bağımlılıklarına, yöntemlerine vb. Bağlı olarak test etmek daha zor olabilir - bir şekilde bu sınıfla alay etmek zorunda kalacaksınız.
öğle yemeği317,

Şahsi olarak, dış sistemlere (dosya / ağ IO) erişen sınır sınıfları veya deterministik olmayanlar (örneğin, rastgele, sistem zamana dayalı vb.) Dışındaki tüm şeylerle alay etmeye karşıyım. Test ettiğim bir sınıfın test edilen mevcut özellik ile ilgili olmayan bir bağımlılığı varsa, mümkün olduğunda null geçmeyi tercih ederim. Ancak, parametre nesnesini alan bir yöntemi test ediyorsanız, bu nesnenin bir sürü bağımlılığı varsa, genel tasarım için endişeleniyorum. Bu tür nesneler hafif olmalıdır.
sara,
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.