web-dev-qa-db-ja.com

「using」キーワードを使用するときにDRY原則を実装する方法は?

次の方法を検討してください。

_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();
    }
}
_

ブロックの使用は同じであり、ここでは3回繰り返されています(もちろん、実際のアプリケーションでは100回以上)。 usingブロックのDRY(Do n't Repeat Yourself)プリンシパルを実装することはどのように可能ですか?DRYプリンシパルの違反と見なされますか?全然?

更新:usingブロック内に実装されているものについては話していません。ここで実際に意味するのは、using (Entities entities = new Entities())です。この行は100回以上繰り返されます。

23
Saeed Neamati

1つのアイデアは、Funcを受け取る関数でそれをラップすることです。

このようなもの

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

次に、上記のコードは

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());
}

Entitiesも型パラメーターにしています。これは、これを使用する型が複数あると想定しているためです。そうでない場合は、それを削除して、戻り値の型にtype paramを使用できます。

正直に言うと、この種のコードは読みやすさをまったく助けません。私の経験では、より多くのJrの同僚も、それに非常に苦労しています。

更新検討するヘルパーの追加のバリエーション

//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.
24
Brook

私にとって、これは同じコレクションを複数回foreachすることを心配するようなものです。それをさらに抽象化しようとすると、コードがはるかに読みにくくなります。

23
Ben Hughes

「1回だけ」の原則をDRY原則と混同しているようです。DRY原則は次のように述べています。

すべての知識は、システム内で単一の明確で信頼できる表現を持つ必要があります。

ただし、1回限りの原則は少し異なります。

[DRY]の原理は、OnceAndOnlyOnceに似ていますが、目的が異なります。 OnceAndOnlyOnceでは、コードと機能の重複を排除するためにリファクタリングすることをお勧めします。 DRYを使用して、システムで使用されるすべての知識の単一の決定的なソースを特定し、そのソースを使用して、その知識の適切なインスタンス(コード、ドキュメント、テストなど)を生成しようとします。

DRY原則は通常、実際のロジックのコンテキストで使用されますが、usingステートメントはそれほど冗長ではありません。

プログラムの構造を維持するDRYは難しく、価値が低くなります。1か所にのみ表示されるのは、ビジネスルール、ifステートメント、数式、メタデータです。WETの要素-HTMLページ、冗長なテストデータ、カンマ、{}区切り文字-すべて無視しやすいため、それらを乾燥させることはそれほど重要ではありません。

ソース

9
Phil

ここでusingの使用を確認できません:

どうですか:

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();
}

あるいは、毎回新しいオブジェクトを作成する必要はないと思うので、さらに良いでしょう。

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の違反については、DRYはこのレベルには適用されません。実際には、読みやすさの原則を除いて、原則は実際には適用されません。DRY levelは実際には単なるアーキテクチャ上のマイクロ最適化であり、すべてのマイクロ最適化と同様に bike-shedding であり、問​​題は解決されませんが、新しい問題を導入するリスクさえあります。
私自身の経験から、そのレベルでコードの冗長性を削減しようとすると、本当に明確で単純なものを難読化することにより、コードの品質に悪影響を及ぼすことがわかります。

編集:
OK。したがって、問題は実際にはusingステートメントではなく、毎回作成するオブジェクトへの依存関係です。コンストラクタを注入することをお勧めします:

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
7
back2dos

重複コードを使用しているだけでなく(重複コードであり、実際にtry..catch..finallyステートメントと比較しています)、toListも使用しています。私はあなたのコードを次のようにリファクタリングします:

 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));
}
4
Cohen

ここには、最後のものを除いて、どのようなビジネスロジックもありません。私の考えでは、それは実際には乾燥していません。

最後のものはusingブロックにDRY=がありませんが、where句は使用した場所を変更する必要があると思います。

これは、コードジェネレーターの典型的なジョブです。コードジェネレータを記述してカバーし、タイプごとに生成させます。

3
arunmur

同じ使い捨てオブジェクトを何度も作成および破棄しているので、クラス自体がIDisposableパターンを実装するのに適しています。

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();
    }
}

これにより、クラスのインスタンスを作成するときに「使用」するだけで済みます。クラスがオブジェクトの破棄を担当しないようにする場合は、メソッドに依存関係を引数として受け入れさせることができます。

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();
}
2
Sean

不可解な魔法の私のお気に入りのビット!

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());
  }
}

Wrapは、それを抽象化するため、または必要な魔法を実行するためにのみ存在します。私はいつもこれをお勧めするかどうかはわかりませんが、使用することは可能です。 「より良い」アイデアは、StructureMapのようなDIコンテナーを使用し、Entitiesクラスをリクエストコンテキストにスコープし、それをコントローラーに挿入して、コントローラーが必要とせずにライフサイクルを処理することです。

1
Travis