API tasarımında “çok fazla parametre” sorunu nasıl önlenir?


160

Bu API işlevi var:

public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d, 
     string e, string f, out Guid code)

Sevmiyorum. Çünkü parametre sırası gereksiz yere önemli hale gelir. Yeni alanlar eklemek zorlaşıyor. Nelerin geçtiğini görmek daha zor. Alt fonksiyonlardaki tüm parametreleri geçirmenin başka bir yükünü oluşturduğu için yöntemi daha küçük parçalara yeniden düzenlemek daha zordur. Kodun okunması daha zordur.

En açık fikirle geldim: verileri çevreleyen bir nesne var ve her parametreyi tek tek geçmek yerine onu iletiyor. İşte ben geldim:

public class DoSomeActionParameters
{
    public string A;
    public string B;
    public DateTime C;
    public OtherEnum D;
    public string E;
    public string F;        
}

Bu, API beyanımı şu şekilde düşürdü:

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)

Güzel. Çok masum görünüyor ama aslında büyük bir değişiklik getirdik: değişebilirlik getirdik. Çünkü daha önce yaptığımız şey aslında anonim değişmez bir nesneyi geçmekti: yığın üzerindeki fonksiyon parametreleri. Şimdi çok değişebilen yeni bir sınıf yarattık. Arayanın durumunu değiştirme yeteneğini yarattık . Bu berbat. Şimdi nesnemin değişmez olmasını istiyorum, ne yapmalıyım?

public class DoSomeActionParameters
{
    public string A { get; private set; }
    public string B { get; private set; }
    public DateTime C { get; private set; }
    public OtherEnum D { get; private set; }
    public string E { get; private set; }
    public string F { get; private set; }        

    public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d, 
     string e, string f)
    {
        this.A = a;
        this.B = b;
        // ... tears erased the text here
    }
}

Gördüğünüz gibi aslında asıl sorunumu yeniden yarattım: çok fazla parametre. Bu yolun gitmediği açık. Ne ben yapacağım? Böyle bir değişmezliği elde etmek için son seçenek şöyle bir "salt okunur" yapı kullanmaktır:

public struct DoSomeActionParameters
{
    public readonly string A;
    public readonly string B;
    public readonly DateTime C;
    public readonly OtherEnum D;
    public readonly string E;
    public readonly string F;        
}

Bu, çok fazla parametreye sahip yapıcılardan kaçınmamıza ve değişmezliğe ulaşmamıza izin verir. Aslında tüm sorunları düzeltir (parametre sıralaması vb.). Hala:

İşte o zaman kafam karıştı ve bu soruyu yazmaya karar verdi: C # 'de değişebilirliğe neden olmadan "çok fazla parametre" probleminden kaçınmanın en kolay yolu nedir? Bu amaç için salt okunur bir yapı kullanmak, ancak yine de kötü bir API tasarımına sahip olmak mümkün müdür?

AÇIKLAMALAR:

  • Tek bir sorumluluk ilkesinin ihlal edilmediğini varsayalım. Orijinal durumumda, fonksiyon sadece verilen parametreleri tek bir DB kaydına yazar.
  • Verilen işleve özel bir çözüm aramıyorum. Bu tür sorunlara genel bir yaklaşım arıyorum. Ben özellikle değişebilirlik ya da korkunç bir tasarım getirmeden "çok fazla parametre" sorunu çözmek ilgilendi.

GÜNCELLEME

Burada verilen cevapların farklı avantajları / dezavantajları vardır. Bu yüzden bunu bir topluluk wikisine dönüştürmek istiyorum. Ben kod örneği ve Artıları / Eksileri ile her cevap gelecekte benzer sorunlar için iyi bir rehber olacağını düşünüyorum. Şimdi nasıl yapılacağını öğrenmeye çalışıyorum.


Temiz Kod: Robert C. Martin ve Martin
Fowler'in

1
İsteğe bağlı parametreleriniz olan C # 4 kullanıyorsanız bu Builder çözümü gereksiz olmaz mı ?
khellang

1
Aptal olabilirim, ancak DoSomeActionParametersyöntem çağrısından sonra atılacak olan bir ıskarta nesnesi olduğunu düşünerek bunun nasıl bir sorun olduğunu göremiyorum .
Vilx-

6
Bir yapıdaki salt okunur alanlardan kaçınmanız gerektiğini söylemiyorum; değişmez yapılar en iyi uygulamadır ve salt okunur alanlar kendiliğinden belgelenen değişmez yapılar oluşturmanıza yardımcı olur. Benim demek ki sen salt okunur alanlar asla değişime kutlanıyor güvenmemelidir bir salt okunur alan bir yapı içinde size verdiği bir garantisi değildir çünkü. Bu, değer türlerine, sanki referans türleri gibi davranmamanız gerektiğine dair daha genel tavsiyelerin özel bir örneğidir; onlar çok farklı bir hayvandır.
Eric Lippert

7
@ssg: Bunu da çok isterdim. Değişkenliği teşvik eden özellikleri (nesne başlatıcılar gibi) eklediğimiz gibi C # 'a değişmezliği (LINQ gibi) destekleyen özellikler ekledik. Değişmez türleri teşvik etmek için daha iyi bir sözdizimine sahip olmak güzel olurdu. Bu konuda çok düşünüyoruz ve bazı ilginç fikirlerimiz var, ancak bir sonraki sürüm için böyle bir şey beklemem.
Eric Lippert

Yanıtlar:


22

Çerçevelerde kucaklanan bir stil genellikle ilgili parametreleri ilgili sınıflara gruplamak gibidir (ancak yine de değişebilirlikle sorunludur):

var request = new HttpWebRequest(a, b);
var service = new RestService(request, c, d, e);
var client = new RestClient(service, f, g);
var resource = client.RequestRestResource(); // O params after 3 objects

Tüm dizeleri bir dize dizisine yoğunlaştırmak, yalnızca ilgili oldukları takdirde mantıklıdır. Argümanların sırasına göre, hepsi tamamen ilişkili değil gibi görünüyor.
icktoofay

Bunun yanı sıra, yalnızca parametre ile aynı türde çok şeyiniz varsa işe yarayacağını kabul etti.
Timo Willemsen

Sorudaki ilk örneğimden farkı nedir?
Sedat Kapanoglu

Bu, .NET Framework'te bile her zamanki gibi kucaklanan bir stildir, ilk örneğinizin küçük değişebilirlik sorunları getirmesine rağmen yaptığı şeyde oldukça doğru olduğunu gösterir (bu örnek açıkça başka bir kütüphaneden olsa da). Bu arada güzel bir soru.
Teoman Soygul

2
Zamanla bu stile daha yakınlaşıyorum. Görünüşe göre önemli miktarda "çok fazla parametre" sorunu iyi mantıksal gruplar ve soyutlamalar ile çözülebilir. Sonunda kodu daha okunaklı ve modüler hale getirir.
Sedat Kapanoglu

87

Oluşturucu ve alana özgü dil stili API'sı - Akıcı Arayüz kombinasyonunu kullanın. API biraz daha ayrıntılıdır, ancak intellisense ile yazılması çok hızlı ve anlaşılması kolaydır.

public class Param
{
        public string A { get; private set; }
        public string B { get; private set; }
        public string C { get; private set; }


  public class Builder
  {
        private string a;
        private string b;
        private string c;

        public Builder WithA(string value)
        {
              a = value;
              return this;
        }

        public Builder WithB(string value)
        {
              b = value;
              return this;
        }

        public Builder WithC(string value)
        {
              c = value;
              return this;
        }

        public Param Build()
        {
              return new Param { A = a, B = b, C = c };
        }
  }


  DoSomeAction(new Param.Builder()
        .WithA("a")
        .WithB("b")
        .WithC("c")
        .Build());

+1 - bu tür bir yaklaşım (yaygın olarak "akıcı arayüz" olarak adlandırılan) tam olarak aklımda olan şeydi.
Daniel Pryden

+1 - aynı durumda bulduğum şey bu. Tek bir sınıf vardı ve DoSomeActionbunun bir yöntemi vardı.
Vilx-

+1 Ben de bunu düşündüm, ama akıcı arayüzlerde yeni olduğum için, burada mükemmel bir uyum olup olmadığını bilmiyordum. Kendi sezgilerimi doğruladığınız için teşekkürler.
Matt

Bu soruyu sormadan önce Builder desenini duymamıştım, bu yüzden bu benim için anlayışlı bir deneyim oldu. Ne kadar yaygın olduğunu merak ediyorum. Çünkü kalıbı duymamış herhangi bir geliştirici, dokümantasyon olmadan kullanımı anlamakta güçlük çekebilir.
Sedat Kapanoglu

1
@ssg, bu senaryolarda yaygındır. BCL, örneğin bağlantı dizesi oluşturucu sınıflarına sahiptir.
Samuel Neff

10

Sahip olduğunuz şey, söz konusu sınıfın Tek Sorumluluk İlkesini ihlal ettiğinden oldukça emin bir gösterge çünkü çok fazla bağımlılığı var. Bu bağımlılıkları Cephe Bağımlılıkları kümelerine yeniden yansıtmanın yollarını arayın .


1
Bir veya daha fazla parametre nesnesini tanıtarak yine de refactor olurdum, ancak açıkçası, tüm argümanları tek bir değişmez türe taşıdığınızda hiçbir şey başaramazsınız. İşin püf noktası, daha yakından ilişkili olan argüman kümelerini aramak ve ardından bu kümelerin her birini ayrı bir parametre nesnesine yeniden yansıtmaktır.
Mark Seemann

2
Her grubu kendi içinde değişmez bir nesneye dönüştürebilirsiniz. Her nesnenin yalnızca birkaç parametre alması gerekir, bu nedenle gerçek bağımsız değişken sayısı aynı kalırken, tek bir kurucuda kullanılan bağımsız değişken sayısı azalır.
Mark Seemann

2
+1 @ssg: Kendimi böyle bir şey konusunda her zaman ikna etmeyi başarabildiğimde, zaman içinde bu düzey parametreler gerektiren büyük yöntemlerden faydalı soyutlama çekerek kendimi yanlış kanıtladım. Evans'ın DDD kitabı size bunun hakkında nasıl düşüneceğiniz konusunda bazı fikirler verebilir (sisteminiz potansiyel olarak bu tür kalıpların uygulanmasına kadar ilgili bir yerden çok uzak gibi görünüyor) - (ve her iki şekilde de harika bir kitap).
Ruben Bartelink

3
@Ruben: Hiçbir aklı başında kitap "iyi bir OO tasarımında bir sınıfın 5'ten fazla mülke sahip olmaması gerektiğini" söylemez. Sınıflar mantıksal olarak gruplandırılmıştır ve bu tür bir bağlamsallaştırma niceliklere dayanamaz. Bununla birlikte, C # 'nin değişmezlik desteği, iyi OO tasarım ilkelerini ihlal etmeye başlamadan önce belirli sayıda mülkte sorun yaratmaya başlar .
Sedat Kapanoglu

3
@ Ruben: Bilginizi, tavrınızı veya öfkenizi yargılamadım. Sizden de aynısını yapmanızı bekliyorum. Önerilerinizin iyi olmadığını söylemiyorum. Sorunumun en mükemmel tasarımda bile ortaya çıkabileceğini ve burada gözden kaçan bir nokta gibi göründüğünü söylüyorum. Deneyimli insanların neden en yaygın hatalar hakkında temel sorular sorduğunu anlasam da, birkaç tur sonra bunu netleştirmek daha az zevkli hale geliyor. Tekrar söylemeliyim ki bu problemi mükemmel bir tasarımla elde etmek çok mümkün. Ve upvote için teşekkürler!
Sedat Kapanoglu

10

Sadece bir adresinin parametre veri yapısını değiştirmek classbir etmek structve sen iyi gitmek.

public struct DoSomeActionParameters 
{
   public string A;
   public string B;
   public DateTime C;
   public OtherEnum D;
   public string E;
   public string F;
}

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code) 

Yöntem şimdi yapının kendi kopyasını alacaktır. Değişken değişkeninde yapılan değişiklikler yöntem tarafından gözlenemez ve yöntemde değişken üzerinde yapılan değişiklikler arayan tarafından gözlenemez. İzolasyon değişmezlik olmadan sağlanır.

Artıları:

  • Uygulaması en kolay
  • Altta yatan mekanikte en az davranış değişikliği

Eksileri:

  • Değişmezlik açık değildir, geliştiricinin dikkatini gerektirir.
  • Değişmezliği korumak için gereksiz kopyalama
  • Yığın alanı kaplar

+1 Bu doğru ancak "alanları doğrudan ortaya çıkarmak kötülük" konusunu çözmüyor. Ben bu API özellikleri yerine alanları kullanmayı tercih ederseniz nasıl kötü şeyler daha kötü olabilir emin değilim.
Sedat Kapanoğlu

@ssg - Bu yüzden onları alanlar yerine herkese açık mülkler yapın. Bunu hiçbir zaman kodu olmayacak bir yapı olarak ele alırsanız, özellikleri veya alanları kullanmanız fark etmez. Kod (doğrulama veya başka bir şey gibi) vermeye karar verirseniz, kesinlikle onlara özellik yapmak isteyeceksiniz. En azından kamusal alanlarda, hiç kimse bu yapıda var olan değişmezler hakkında herhangi bir yanılsama yaşamaz. Parametrelerin olduğu gibi yöntemle doğrulanması gerekecektir.
Jeffrey L Whitledge

Ah, bu doğru. Müthiş! Teşekkürler.
Sedat Kapanoğlu

8
Bence-alanlar-şeytan kuralının nesne yönelimli bir tasarımdaki nesneler için geçerli olduğunu düşünüyorum. Önerdiğim yapı, sadece çıplak metal bir parametre kabı. İçgüdüleriniz değişmez bir şeyle gideceğinden, böyle bir temel kabın bu durum için uygun olabileceğini düşünüyorum.
Jeffrey L Whitledge

1
@JeffreyLWhitledge: Açık alan yapılarının kötü olduğu fikrinden gerçekten hoşlanmıyorum. Böyle bir iddia bana tornavidaların kötü olduğunu ve insanların çekiç kullanması gerektiğini söyleyerek eşdeğer olarak karşımıza çıkıyor çünkü tornavidaların noktaları çivi başlarını kırıyor. Bir çivi sürmek gerekiyorsa, bir çekiç kullanmalı, ancak bir vidayı sürmesi gerekiyorsa, bir tornavida kullanmalıdır. Açıkta kalan bir alanın iş için tam olarak doğru araç olduğu birçok durum vardır. Bu arada, get / set özelliklerine sahip bir yapının gerçekten doğru araç olduğu çok daha az var (çoğu durumda ...
Supercat

6

Veri sınıfınızda bir oluşturucu sınıfı oluşturmaya ne dersiniz. Veri sınıfı tüm ayarlayıcılara özel olarak sahip olacak ve sadece üretici bunları ayarlayabilecektir.

public class DoSomeActionParameters
    {
        public string A { get; private set; }
        public string B  { get; private set; }
        public DateTime C { get; private set; }
        public OtherEnum D  { get; private set; }
        public string E  { get; private set; }
        public string F  { get; private set; }

        public class Builder
        {
            DoSomeActionParameters obj = new DoSomeActionParameters();

            public string A
            {
                set { obj.A = value; }
            }
            public string B
            {
                set { obj.B = value; }
            }
            public DateTime C
            {
                set { obj.C = value; }
            }
            public OtherEnum D
            {
                set { obj.D = value; }
            }
            public string E
            {
                set { obj.E = value; }
            }
            public string F
            {
                set { obj.F = value; }
            }

            public DoSomeActionParameters Build()
            {
                return obj;
            }
        }
    }

    public class Example
    {

        private void DoSth()
        {
            var data = new DoSomeActionParameters.Builder()
            {
                A = "",
                B = "",
                C = DateTime.Now,
                D = testc,
                E = "",
                F = ""
            }.Build();
        }
    }

1
+1 Bu son derece geçerli bir çözüm ama bence böyle basit bir tasarım kararını sürdürmek için çok fazla iskele var. Özellikle "salt okunur yapı" çözümü "çok" ideale yakın olduğunda.
Sedat Kapanoglu

2
Bu, "çok fazla parametre" sorununu nasıl çözer? Sözdizimi farklı olabilir, ancak sorun aynı görünüyor. Bu bir eleştiri değil, sadece merak ediyorum çünkü bu kalıba aşina değilim.
alexD

1
@alexD, çok fazla işlev parametresine sahip olmak ve nesneyi değişmez tutmakla sorunu çözer. Yalnızca builder sınıfı özel özellikleri ayarlayabilir ve parametreler nesnesini aldıktan sonra değiştiremezsiniz. Sorun, çok sayıda iskele kodu gerektirmesidir.
marto

5
Çözümünüzdeki parametre nesnesi değiştirilemez.
Yapıcıyı

1
@ Astef'in noktasına gelince, şu anda yazıldığı gibi, bir DoSomeActionParameters.Builderörnek tam olarak bir DoSomeActionParametersörnek oluşturmak ve yapılandırmak için kullanılabilir . Özelliklerinde Build()daha sonra yapılan değişiklikler çağrıldıktan sonra orijinal örneğin özelliklerini Builderdeğiştirmeye devam edecek ve sonraki çağrıları aynı örneği döndürmeye devam edecektir . Gerçekten olmalı . DoSomeActionParametersBuild()DoSomeActionParameterspublic DoSomeActionParameters Build() { var oldObj = obj; obj = new DoSomeActionParameters(); return oldObj; }
BACON

6

Ben bir C # programcısı değilim ama C # adlı bağımsız değişkenleri desteklediğine inanıyorum: (F # yapar ve C # büyük ölçüde bu tür bir şey için uyumlu özelliktir) Yapar: http://msdn.microsoft.com/tr-tr/library/dd264739 .aspx # Y342

Böylece orijinal kodunuzu aramak:

public ResultEnum DoSomeAction( 
 e:"bar", 
 a: "foo", 
 c: today(), 
 b:"sad", 
 d: Red,
 f:"penguins")

bu daha fazla alan / düşünce gerektirmez ve caydırıcı sistemde olup bitenleri hiç değiştirmediğiniz gerçeğinin tüm faydalarına sahiptir. Bağımsız değişkenlerin adlandırıldığını belirtmek için hiçbir şeyi yeniden kodlamanız gerekmez

Düzenleme: işte bu konuda bulduğum bir makale. http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ C # 4.0 destekli argümanlardan bahsetmeliyim, 3.0


Gerçekten bir gelişme. Ancak bu yalnızca kod okunabilirliğini çözer ve bunu yalnızca geliştirici adlandırılmış parametreleri kullanmayı seçtiğinde yapar. Birinin isimleri belirtmeyi ve faydalarını vermeyi unutması kolaydır. Kodun kendisi yazılırken yardımcı olmaz. örneğin, işlevi daha küçük olanlara yeniden düzenleme ve verileri tek bir pakette aktarma.
Sedat Kapanoglu

6

Neden sadece değişmezliği zorlayan bir arayüz oluşturmuyoruz (yani sadece alıcılar)?

Temelde ilk çözümünüzdür, ancak parametreye erişmek için işlevi arabirimi kullanmaya zorlarsınız.

public interface IDoSomeActionParameters
{
    string A { get; }
    string B { get; }
    DateTime C { get; }
    OtherEnum D { get; }
    string E { get; }
    string F { get; }              
}

public class DoSomeActionParameters: IDoSomeActionParameters
{
    public string A { get; set; }
    public string B { get; set; }
    public DateTime C { get; set; }
    public OtherEnum D { get; set; }
    public string E { get; set; }
    public string F { get; set; }        
}

ve işlev bildirimi şöyle olur:

public ResultEnum DoSomeAction(IDoSomeActionParameters parameters, out Guid code)

Artıları:

  • structÇözüm gibi yığın alanı sorunu yok
  • Dil semantiği kullanarak doğal çözüm
  • Değişmezlik açıktır
  • Esnek (tüketici isterse farklı bir sınıf kullanabilir)

Eksileri:

  • Bazı tekrarlanan çalışmalar (iki farklı kuruluşta aynı beyanlar)
  • Geliştirici DoSomeActionParametersbunun eşleştirilebilecek bir sınıf olduğunu tahmin etmelidirIDoSomeActionParameters

+1 Neden böyle düşünmediğimi bilmiyorum? :) Sanırım nesnenin hala çok fazla parametreye sahip bir kurucudan muzdarip olacağını düşündüm ama durum böyle değil. Evet, bu da çok geçerli bir çözüm. Aklıma gelen tek sorun API kullanıcısı için verilen arayüzü destekleyen doğru sınıf adını bulmak gerçekten kolay değil. En az dokümantasyon gerektiren çözüm daha iyidir.
Sedat Kapanoglu

Bunu beğendim, haritanın çoğaltılması ve bilgisi yeniden birleştirici ile işleniyor ve beton sınıfının varsayılan yapıcısını kullanarak varsayılan değerleri sağlayabiliyorum
Anthony Johnston

2
Bu yaklaşımı sevmiyorum. Keyfi bir uygulamaya başvurusu olan IDoSomeActionParametersve buradaki değerleri yakalamak isteyen birinin, referansı tutmanın yeterli olup olmayacağını veya değerleri başka bir nesneye kopyalayıp kopyalamayacağını bilmesinin bir yolu yoktur. Okunabilir arabirimler bazı bağlamlarda yararlıdır, ancak işleri değişmez kılmak için bir araç olarak kullanılmaz.
supercat

"IDoSomeActionParameters parametreleri" DoSomeActionParameters'a aktarılabilir ve değiştirilebilir. Geliştirici, parametreleri değişken hale getirme girişimini atlattıklarını bile fark etmeyebilir
Joseph Simpson

3

Bunun eski bir soru olduğunu biliyorum ama aynı problemi çözmek zorunda kaldığım için önerimden vazgeçeceğimi düşündüm. Kuşkusuz, benim sorunum sizinkinden biraz farklıydı, çünkü kullanıcıların bu nesneyi kendileri inşa etmelerini istememe ek gereksinimim vardı (verilerin tüm hidrasyonu veritabanından geldi, böylece tüm inşaatları dahili olarak hapse atabilirim). Bu, özel bir kurucu ve aşağıdaki modeli kullanmama izin verdi;

    public class ExampleClass
    {
        //create properties like this...
        private readonly int _exampleProperty;
        public int ExampleProperty { get { return _exampleProperty; } }

        //Private constructor, prohibiting construction outside of this class
        private ExampleClass(ExampleClassParams parameters)
        {                
            _exampleProperty = parameters.ExampleProperty;
            //and so on... 
        }

        //The object returned from here will be immutable
        public ExampleClass GetFromDatabase(DBConnection conn, int id)
        {
            //do database stuff here (ommitted from example)
            ExampleClassParams parameters = new ExampleClassParams()
            {
                ExampleProperty = 1,
                ExampleProperty2 = 2
            };

            //Danger here as parameters object is mutable

            return new ExampleClass(parameters);    

            //Danger is now over ;)
        }

        //Private struct representing the parameters, nested within class that uses it.
        //This is mutable, but the fact that it is private means that all potential 
        //"damage" is limited to this class only.
        private struct ExampleClassParams
        {
            public int ExampleProperty { get; set; }
            public int AnotherExampleProperty { get; set; }
            public int ExampleProperty2 { get; set; }
            public int AnotherExampleProperty2 { get; set; }
            public int ExampleProperty3 { get; set; }
            public int AnotherExampleProperty3 { get; set; }
            public int ExampleProperty4 { get; set; }
            public int AnotherExampleProperty4 { get; set; } 
        }
    }

2

Oluşturucu tarzı bir yaklaşım kullanabilirsiniz, ancak DoSomeActionyönteminizin karmaşıklığına bağlı olarak , bu dokunuş ağır olabilir. Bu çizgiler boyunca bir şey:

public class DoSomeActionParametersBuilder
{
    public string A { get; set; }
    public string B { get; set; }
    public DateTime C { get; set; }
    public OtherEnum D { get; set; }
    public string E { get; set; }
    public string F { get; set; }

    public DoSomeActionParameters Build()
    {
        return new DoSomeActionParameters(A, B, C, D, E, F);
    }
}

public class DoSomeActionParameters
{
    public string A { get; private set; }
    public string B { get; private set; }
    public DateTime C { get; private set; }
    public OtherEnum D { get; private set; }
    public string E { get; private set; }
    public string F { get; private set; }

    public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d, string e, string f)
    {
        A = a;
        // etc.
    }
}

// usage
var actionParams = new DoSomeActionParametersBuilder
{
    A = "value for A",
    C = DateTime.Now,
    F = "I don't care for B, D and E"
}.Build();

result = foo.DoSomeAction(actionParams, out code);

Ah, marto beni inşaatçı önerisine yendi!
Chris White

2

Manji cevabına ek olarak - bir operasyonu birkaç küçük operasyona bölmek de isteyebilirsiniz. Karşılaştırmak:

 BOOL WINAPI CreateProcess(
   __in_opt     LPCTSTR lpApplicationName,
   __inout_opt  LPTSTR lpCommandLine,
   __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
   __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
   __in         BOOL bInheritHandles,
   __in         DWORD dwCreationFlags,
   __in_opt     LPVOID lpEnvironment,
   __in_opt     LPCTSTR lpCurrentDirectory,
   __in         LPSTARTUPINFO lpStartupInfo,
   __out        LPPROCESS_INFORMATION lpProcessInformation
 );

ve

 pid_t fork()
 int execvpe(const char *file, char *const argv[], char *const envp[])
 ...

POSIX bilmeyenler için çocuğun yaratılması şu kadar kolay olabilir:

pid_t child = fork();
if (child == 0) {
    execl("/bin/echo", "Hello world from child", NULL);
} else if (child != 0) {
    handle_error();
}

Her tasarım seçeneği, hangi işlemleri yapabileceği konusunda ödünleşimi temsil eder.

PS. Evet - inşaatçıya benzer - sadece ters (yani arayan yerine callee tarafında). Bu özel durumda yapıcıdan daha iyi olabilir veya olmayabilir.


Bu küçük bir işlemdir, ancak tek sorumluluk ilkesini ihlal eden herkes için iyi bir öneri. Sorum, diğer tüm tasarım iyileştirmeleri yapıldıktan hemen sonra ortaya çıkıyor.
Sedat Kapanoglu

Güç kaynağı. Üzgünüz - Düzenlemeden önce bir soru hazırladım ve daha sonra yayınladım - dolayısıyla fark etmemiştim.
Maciej Piechotka

2

burada Mikeys'den biraz farklı bir şey ama yapmaya çalıştığım her şeyi mümkün olduğunca az yazmak

public class DoSomeActionParameters
{
    readonly string _a;
    readonly int _b;

    public string A { get { return _a; } }

    public int B{ get { return _b; } }

    DoSomeActionParameters(Initializer data)
    {
        _a = data.A;
        _b = data.B;
    }

    public class Initializer
    {
        public Initializer()
        {
            A = "(unknown)";
            B = 88;
        }

        public string A { get; set; }
        public int B { get; set; }

        public DoSomeActionParameters Create()
        {
            return new DoSomeActionParameters(this);
        }
    }
}

DoSomeActionParameters olabildiğince değişmez ve varsayılan kurucusu özel olduğu için doğrudan oluşturulamaz

Başlatıcı değişmez değil, sadece bir aktarımdır

Kullanım, Başlatıcıdaki başlatıcıdan yararlanır (sapmamı alırsanız) ve Başlatıcı varsayılan yapıcısında varsayılanlara sahip olabilirim

DoSomeAction(new DoSomeActionParameters.Initializer
            {
                A = "Hello",
                B = 42
            }
            .Create());

Parametreler burada isteğe bağlı olacaktır, eğer bazılarının gerekli olmasını istiyorsanız bunları Initializer varsayılan yapıcısına koyabilirsiniz

Ve doğrulama Oluştur yöntemine girebilir

public class Initializer
{
    public Initializer(int b)
    {
        A = "(unknown)";
        B = b;
    }

    public string A { get; set; }
    public int B { get; private set; }

    public DoSomeActionParameters Create()
    {
        if (B < 50) throw new ArgumentOutOfRangeException("B");

        return new DoSomeActionParameters(this);
    }
}

Şimdi öyle görünüyor

DoSomeAction(new DoSomeActionParameters.Initializer
            (b: 42)
            {
                A = "Hello"
            }
            .Create());

Hala biraz kooki biliyorum, ama yine de deneyeceğim

Düzenleme: create yöntemini parametreler nesnesindeki bir statik haline taşımak ve başlatıcıyı geçen bir temsilci eklemek, bazı cookieness çağrıdan çıkarır

public class DoSomeActionParameters
{
    readonly string _a;
    readonly int _b;

    public string A { get { return _a; } }
    public int B{ get { return _b; } }

    DoSomeActionParameters(Initializer data)
    {
        _a = data.A;
        _b = data.B;
    }

    public class Initializer
    {
        public Initializer()
        {
            A = "(unknown)";
            B = 88;
        }

        public string A { get; set; }
        public int B { get; set; }
    }

    public static DoSomeActionParameters Create(Action<Initializer> assign)
    {
        var i = new Initializer();
        assign(i)

        return new DoSomeActionParameters(i);
    }
}

Yani çağrı şimdi böyle görünüyor

DoSomeAction(
        DoSomeActionParameters.Create(
            i => {
                i.A = "Hello";
            })
        );

1

Yapıyı kullanın, ancak ortak alanlar yerine ortak mülklere sahip olun:

• Herkes (FXCop ve Jon Skeet dahil) kamuya açık alanların ifşa edilmesinin kötü olduğunu kabul eder.

Jon ve FXCop, alanları değil, özellikleri ortaya koyduğunuz için tatmin olacaktır.

• Eric Lippert ve arkadaşları değişmezlik için salt okunur alanlara güvenmenin bir yalan olduğunu söylüyor.

Eric, satisifed edilecektir çünkü özellikleri kullanarak, değerin sadece bir kez ayarlanmasını sağlayabilirsiniz.

    private bool propC_set=false;
    private date pC;
    public date C {
        get{
            return pC;
        }
        set{
            if (!propC_set) {
               pC = value;
            }
            propC_set = true;
        }
    }

Bir yarı değişmez nesne (değer ayarlanabilir ancak değiştirilemez). Değer ve Referans türleri için çalışır.


+1 Belki de salt okunur alanlar ve yalnızca alıcıya ait mülkler, daha az ayrıntılı bir çözüme olanak sağlamak için bir yapıda birleştirilebilir.
Sedat Kapanoglu

Değişen yapılar üzerindeki kamusal okuma-yazma özellikleri thiskamusal alanlardan çok daha kötüdür. Neden sadece bir kez değer ayarlamak şeyler "Tamam" yapar sanmıyorum emin değilim; biri bir yapının varsayılan bir örneğiyle başlarsa, this-mutating özellikleriyle ilişkili sorunlar devam eder.
supercat

0

Samuel'in aynı problemi yaşadığımda projemde kullandığım cevabının bir çeşidi :

class MagicPerformer
{
    public int Param1 { get; set; }
    public string Param2 { get; set; }
    public DateTime Param3 { get; set; }

    public MagicPerformer SetParam1(int value) { this.Param1 = value; return this; }
    public MagicPerformer SetParam2(string value) { this.Param2 = value; return this; }
    public MagicPerformer SetParam4(DateTime value) { this.Param3 = value; return this; }

    public void DoMagic() // Uses all the parameters and does the magic
    {
    }
}

Ve kullanmak için:

new MagicPerformer().SeParam1(10).SetParam2("Yo!").DoMagic();

Benim durumumda parametreler kasıtlı olarak değiştirilebilirdi, çünkü ayarlayıcı yöntemler olası tüm kombinasyonlara izin vermedi ve sadece bunların ortak kombinasyonlarını ortaya çıkardı. Çünkü parametrelerimin bazıları oldukça karmaşıktı ve olası tüm durumlar için yazma yöntemleri zor ve gereksiz olurdu (çılgın kombinasyonlar nadiren kullanılır).


Bu değişebilir bir sınıftır.
Sedat Kapanoglu

@ssg - Evet, öyle. DoMagicO nesneyi değiştirmek olmaz gerçi onun sözleşmede vardır. Ama sanırım kazara yapılan değişikliklerden korunmuyor.
Vilx-
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.