"Kullan" anahtar kelimesini kullanırken DRY prensibi nasıl uygulanır?


23

Bu yöntemleri göz önünde bulundurun:

public List<Employee> GetAllEmployees()
{
    using (Entities entities = new Entities())
    {
        return entities.Employees.ToList();
    }
}

public List<Job> GetAllJobs()
{
    using (Entities entities = new Entities())
    {
        return entities.Jobs.ToList();
    }
}

public List<Task> GetAllTasksOfTheJob(Job job)
{
    using (Entities entities = new Entities())
    {
        return entities.Tasks.Where(t => t.JobId == job.Id).ToList();
    }
}

Blok kullanımı aynıdır ve burada 3 kez tekrar edilmiştir (tabii ki gerçek uygulamada 100'den fazla). usingBlok için DRY (Kendini Tekrar Etme) prensibini uygulamak nasıl mümkün olabilir ? DRY müdürünün ihlali sayılır mı?

Güncelleme: usingBloğun içinde uygulananlardan bahsetmiyorum . Aslında burada kastediyorum using (Entities entities = new Entities()). Bu çizgi 100 kez veya daha fazla tekrarlanır.


2
bu C # Sorunuzun cevabı dile bağlı olabilir
David,

Evet @David, dilimden bahsetmediğim için üzgünüm. Cevabı nasıl etkileyebilir?
Saeed Neamati

bazı dillerde, kodunuzu biraz belirlemenize yardımcı olabilecek belirli bir sözdizimi olabilir. C # bilmiyorum, ama Ruby'de, kullanım kısmını etkilemek için blokları kullanabileceğini düşünüyorum.
David

Using ifadesi aslında ile kaynak bertarafını yönetirken kodlama önlenmesine yardımcı tekrarlayan DRY prensibini uygulamak için C # dil desteği sağlar imha tasarım deseni . Bu, daha iyi şeyler yapmanın yollarını bulamayacağımız anlamına gelmez! Şahsen ben DRY'yi özyinelemeli bir süreç olarak düşünüyorum.
John Tobler

Yanıtlar:


24

Bir fikir onu alan bir işlevle sarmak olacaktır Func.

Böyle bir şey

public K UsingT<T,K>(Func<T,K> f) where T:IDisposable,new()
{
    using (T t = new T())
    {
        return f(t);
    }
}

Ardından yukarıdaki kodunuz olur

public List<Employee> GetAllEmployees()
{
    return UsingT<Entities,List<Employee>>(e=>e.Employees.ToList());
}

public List<Job> GetAllJobs()
{
    return UsingT<Entities,List<Job>>(e=>e.Jobs.ToList());
}

public List<Task> GetAllTasksOfTheJob(Job job)
{
    return UsingT<Entities,List<Task>>(e=>e.Tasks.Where(t => t.JobId == job.Id).ToList());
}

Ben de Entitiesbir tür param yaptım , çünkü bunu yaptığınız birden fazla tip olduğunu farz ediyorum. Değilse, onu kaldırabilir ve dönüş türü için sadece param türünü kullanabilirsiniz.

Dürüst olmak gerekirse, bu tür bir kod okunabilirliğe hiç yardımcı olmuyor. Tecrübelerime göre, daha fazla Jr çalışanı onunla gerçekten zor anlar yaşadı.

Güncelle Göz önünde bulundurabileceğiniz yardımcılara ilişkin ek varyasyonlar

//forget the Entities type param
public T UsingEntities<T>(Func<Entities,T> f)
{
    using (Entities e = new Entities())
    {
        return f(e);
    }
}
//forget the Entities type param, and return an IList
public IList<T> ListFromEntities<T>(Func<Entities,IEnumerable<T>> f)
{
    using (Entities e = new Entities())
    {
        return f(e).ToList();
    }
}
//doing the .ToList() forces the results to enumerate before `e` gets disposed.

1
+1 Güzel çözüm, asıl soruna hitap etmese de (asıl soruya dahil değil), yani birçok oluşumunu Entities.
back2dos

@Doc Brown: Sanırım sizi fonksiyon ismiyle yenmiştim. Arayan kişinin geri getirmesini istediği T özelliklerinin IEnumerablebulunmadığı durumlarda işlevden çıktım IEnumerable, ama haklısın, biraz temizlerdi. Belki hem Single için hem de IEnumerablesonuçlar için bir yardımcıya sahip olmak iyi olurdu. Yani, hala özellikle jenerik bir sürü ve lambdas (SO üzerinde DEĞİLDİR örn İş arkadaşların :)) kullanmaya alışkın olmayan biri için, kodu ne tanınmasını yavaşlatır düşünüyorum dedi
Brook

+1 Bu yaklaşımın iyi olduğunu düşünüyorum. Okunabilirlik artırılabilir. Örneğin, "ToList " ifadesini yerine WithEntitieskullanın, kullanın ve "WithEntities" öğesine daha iyi bir ad verin (SelectEntities gibi). Ve "Varlıklar" ın burada genel bir parametre olması gerektiğini düşünmüyorum. Func<T,IEnumerable<K>>Func<T,K>
Doktor Brown

1
Bu işi yapmak için, kısıtlamalar olması gerekir where T : IDisposable, new()gibi usinggerektirir IDisposableişin amacıyla.
Anthony Pegram

1
@Anthony Pegram: Sabit, teşekkürler! (Bir tarayıcı penceresinde C # kodlaması için elde ettiğim şey bu)
Brook

23

Bana göre bu, aynı koleksiyona defalarca katılmaktan endişe etmek gibi olurdu: Bu sadece yapmanız gereken bir şey. Daha fazla soyutlama girişimi, kodu daha az okunabilir hale getirir.


Büyük analoji @Ben. +1
Saeed Neamati

3
Katılmıyorum üzgünüm. OP'nin işlem kapsamı hakkındaki yorumlarını okuyun ve bu tür bir kodu 500 kez yazdığınızda ne yapmanız gerektiğini düşünün ve ardından aynı şeyi 500 kez değiştirmek zorunda kalacağınızı unutmayın. Bu tür bir kod tekrarı sadece bu işlevlerden <10 tanesi varsa tamam olabilir.
Doktor Brown

Oh, ve unutmamak gerekir ki, eğer her biri için aynı koleksiyona 10 defadan fazla benzer şekilde, her birinin içinde aynı görünen kodla, kesinlikle bazı genellemeler hakkında düşünmelisiniz.
Doktor Brown

1
Bana göre sadece oneliner içine üç astar yapıyorsun ... hala kendini tekrar ediyorsun.
Jim Wolff,

Bağlam bağımlıdır, eğer foreachçok büyük bir koleksiyonun üzerindeyse veya foreachdöngü içindeki mantık zaman alıcıysa. Kabul etmeye başladığım bir slogan: Takıntı yapma ama her zaman yaklaşımına dikkat et
Coops

9

DRY ilkesiyle "Bir Kez ve Bir Kez" ilkesini karıştırıyormuşsunuz gibi geliyor. KURU ilkesi şöyledir:

Her bilgi parçasının bir sistem içinde tek, açık ve net bir temsiline sahip olması gerekir.

Bununla birlikte, Bir Kez ve Bir Kez prensibi biraz farklıdır.

[KURU] ilkesi OnceAndOnlyOnce'e benzer, ancak farklı bir amacı vardır. OnceAndOnlyOnce ile, çoğaltılmış kod ve işlevselliği ortadan kaldırmak için yeniden yönlendirici kullanmaya teşvik edilir. DRY ile, sisteminizde kullanılan her bilgi parçasının tekil ve kesin kaynağını tanımlamaya çalışırsınız ve ardından bu kaynağı bu bilginin uygulanabilir örneklerini (kod, dokümantasyon, testler, vb.) Üretmek için kullanırsınız.

DRY prensibi genellikle gerçek mantık bağlamında kullanılır, ifadeleri kullanmak çok fazla değil:

Bir programın yapısını korumak DRY daha zordur ve daha düşük bir değerdir. İş kuralları, if ifadeleri, matematik formülleri ve yalnızca bir yerde görünmesi gereken meta verilerdir. WET sayfalarında - HTML sayfaları, yedekli test verileri, virgüller ve {} sınırlayıcılar - bunların tümü göz ardı edilebilir, bu nedenle bunları KURUTMAK daha az önemlidir.

Kaynak


7

usingBurada kullanımını göremiyorum :

Peki ya:

public List<Employee> GetAllEmployees() {
    return (new Entities()).Employees.ToList();
}
public List<Job> GetAllJobs() {
    return (new Entities()).Jobs.ToList();
}
public List<Task> GetAllTasksOfTheJob(Job job) {
    return (new Entities()).Tasks.Where(t => t.JobId == job.Id).ToList();
}

Daha da iyisi, her seferinde yeni bir nesne oluşturmanız gerekmediğini düşünüyorum.

private Entities entities = new Entities();//not sure C# allows for that kind of initialization, but you can do it in the constructor if needed

public List<Employee> GetAllEmployees() {
    return entities.Employees.ToList();
}
public List<Job> GetAllJobs() {
    return entities.Jobs.ToList();
}
public List<Task> GetAllTasksOfTheJob(Job job) {
    return entities.Tasks.Where(t => t.JobId == job.Id).ToList();
}

DRY ihlalinde gelince: DRY bu seviyede geçerli değildir. Aslında okunabilirlik dışında hiçbir prensip yoktur. DRY'yi bu seviyede uygulamaya çalışmak gerçekten sadece mimari mikro-optimizasyondur, ki tüm mikro-optimizasyonlar sadece bisiklet dökümüdür ve herhangi bir problemi çözmez, hatta yenilerini ortaya koyma riski de vardır.
Kendi tecrübelerime göre, bu seviyedeki kod yedekliliğini azaltmaya çalışırsanız, gerçekten net ve basit olanı gizleyerek kod kalitesi üzerinde olumsuz bir etki yarattığınızı biliyorum.

Düzenleme:
Tamam. Yani sorun aslında using ifadesi değildir, sorun her zaman yarattığınız nesneye bağımlılıktır. Bir yapıcı enjekte etmenizi öneririm:

private delegate Entities query();//this should be injected from the outside (upon construction for example)
public List<Employee> GetAllEmployees() {
    using (var entities = query()) {//AFAIK C# can infer the type here
        return entities.Employees.ToList();
    }
}
//... and so on

2
Ancak @ back2dos, using (CustomTransaction transaction = new CustomTransaction())aa işleminin kapsamını tanımlamak için kodumuzda kod bloğunu kullandığımız birçok yer var . Tek bir nesneye birleştirilemez ve bir işlemi kullanmak istediğiniz her yerde bir blok yazmalısınız. Şimdi o işlemin türünü değiştirmek ne isterseniz CustomTransactioniçin BuiltInTransaction500'den fazla yöntemlerde? Bu bana tekrar eden bir görev ve DRY sorumlusunun ihlali örneği gibi görünüyor.
Saeed Neamati

3
Burada "kullanma" olması veri içeriğini kapatır, bu nedenle bu yöntemlerin dışında tembel yükleme yapılamaz.
Steven Striga

@ Saeed: Bağımlılık enjeksiyonuna baktığınız zaman. Ancak bu, soruda belirtildiği gibi durumdan oldukça farklı görünüyor.
Bir CVn

@Saeed: Yayın güncellendi.
back2dos

@WeekendWarrior using(bu bağlamda) hala daha bilinmeyen bir "uygun sözdizimi" mi? Kullanımı neden bu kadar güzel =)
Coops

4

Sadece bir kod kullanmak değil (bu arada kod bir koddur ve aslında bir try..catch..finally deyimi ile karşılaştırılır). Bu şekilde kodunuzu tekrar gözden geçiririm:

 public List<T> GetAll(Func<Entities, IEnumerable<T>> getter) {
    using (Entities entities = new Entities())
    {
        return getter().ToList();
    }
 }

public List<Employee> GetAllEmployees()
{
    return GetAll(e => e.Employees);
}

public List<Job> GetAllJobs()
{
    return GetAll(e => e.Jobs);
}

public List<Task> GetAllTasksOfTheJob(Job job)
{
    return GetAll(e => e.Tasks.Where(t => t.JobId == job.Id));
}

3

Sonuncusu dışında, burada hiçbir iş mantığı olmadığı için. Benim görüşüme göre, gerçekten kuru değil.

Sonuncusu, kullanım bloğunda herhangi bir DRY'ye sahip değil ama sanırım nerede bir cümle, kullanılmış olan yeri değiştirmeli.

Bu, kod üreteçleri için tipik bir iştir. Kod üretecini yazıp kapatın ve her tür için oluşturmasına izin verin.


@Arunmur yok. Burada bir yanlış anlaşılma vardı. DRY ile using (Entities entities = new Entities())blok demek istedim . Yani, bu kod satırı 100 kez tekrarlanıyor ve gittikçe daha fazla tekrarlanıyor.
Saeed Neamati

DRY, öncelikle, çoğu zaman bir yerde bir hatanın ve 100 yerde tamir etmeniz gerektiği anlamına gelen test senaryoları yazmanız gerektiği gerçeğinden gelir. Kullanımı (Varlıklar ...) çok basit bir kod kırmak için. Ayrılması veya başka bir sınıfa konması gerekmez. Hala basitleştirmek konusunda ısrar ediyorsanız. Yakuttan bir Yeild geri çağırma işlevi öneririm.
arunmur

1
@ arnumur - kullanmak her zaman kırmak için çok kolay değildir. Veri bağlamında hangi seçeneklerin kullanılacağını belirleyen çok sayıda mantığa sahibim. Yanlış bağlantı dizgisinin içeri
girilebilmesi

1
@roncode - Bu durumlarda, onu bir işleve zorlamak mantıklıdır. Ancak bu örneklerde kırılması oldukça zordur. Düzeltmeyi kırsa bile, düzeltmeler genellikle, ayrı bir test durumu ile ele alınması gereken Varlıklar sınıfının tanımındadır.
arunmur

+1 @arunmur - Katılıyorum. Bunu genelde kendim yaparım. Bu işlev için testler yazarım, ancak kendi kullanım ifadeniz için bir test yazmak için üste biraz görünmüyor.
Morgan Herlocker

2

Aynı tek kullanımlık nesneyi tekrar tekrar yarattığınız ve yok ettiğiniz için, sınıfınız, yeniden şekillendirilebilir modeli uygulamak için iyi bir adaydır.

class ThisClass : IDisposable
{
    protected virtual Entities Context { get; set; }

    protected virtual void Dispose( bool disposing )
    {
        if ( disposing && Context != null )
            Context.Dispose();
    }

    public void Dispose()
    {
        Dispose( true );
    }

    public ThisClass()
    {
        Context = new Entities();
    }

    public List<Employee> GetAllEmployees()
    {
        return Context.Employees.ToList();
    }

    public List<Job> GetAllJobs()
    {
        return Context.Jobs.ToList();
    }

    public List<Task> GetAllTasksOfTheJob(Job job)
    {
        return Context.Tasks.Where(t => t.JobId == job.Id).ToList();
    }
}

Bu, yalnızca sınıfınızın bir örneğini oluştururken "kullanmaya" ihtiyaç duymanıza izin verir. Nesnelerin atılmasından sınıfın sorumlu olmasını istemiyorsanız, yöntemlerin bağımlılığı bir argüman olarak kabul etmesini sağlayabilirsiniz:

public static List<Employee> GetAllEmployees( Entities entities )
{
    return entities.Employees.ToList();
}

public static List<Job> GetAllJobs( Entities entities )
{
    return entities.Jobs.ToList();
}

public static List<Task> GetAllTasksOfTheJob( Entities entities, Job job )
{
    return entities.Tasks.Where(t => t.JobId == job.Id).ToList();
}

1

En sevmediğim telafi edilemez sihir!

public class Blah
{
  IEnumerable<T> Wrap(Func<Entities, IEnumerable<T>> act)
  {
    using(var entities = new Entities()) { return act(entities); }
  }

  public List<Employee> GetAllEmployees()
  {
    return Wrap(e => e.Employees.ToList());
  }

  public List<Job> GetAllJobs()
  {
    return Wrap(e => e.Jobs.ToList());
  }

  public List<Task> GetAllTasksOfTheJob(Job job)
  {
    return Wrap(e => e.Tasks.Where(x ....).ToList());
  }
}

WrapSadece bunu ortaya çıkarmak veya ihtiyacınız olan her türlü sihri özetlemek için var. Bunu her zaman tavsiye edeceğimden emin değilim ama kullanmak mümkün. “Daha iyi” fikri, StructureMap gibi bir DI kap kullanmak ve sadece Varlıklar sınıfını istek bağlamına dahil etmek, kontrol ünitesine enjekte etmek ve kontrol ünitesine gerek duymadan yaşam döngüsüne bakmasını sağlamak olacaktır.


Func <IEnumerable <T>, Varlıklar> için tür paramaterleriniz yanlış sırada. (temelde aynı olan cevabımı görün)
Brook

Düzeltmek için yeterince kolay. Doğru sıranın ne olduğunu asla hatırlamıyorum. Kullandığım Funcyapayım yeter.
Travis,
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.