web-dev-qa-db-ja.com

メソッドのグループ化

ユーザーを検索する複数のページがあり、各サイトには異なる検索パラメーターがあります。場合によっては、2つのパラメーターがあり、4つあり、これらのパラメーターのほとんどが重複しています。したがって、このコードは(簡略化された)一種です。

SearchUser()
{
    // Get values from somewhere
    Service.SearchActiveUser(firstname, lastname);
}
SearchUserSomewhereElse()
{
    // Get values from somewhere
    Service.SearchUser(lastname, id);
}

// some other layer
SearchActiveUser(string firstname, string lastname)
{
    var users = from user in allusers
                where user.IsActive == true
                select user;

    if (!String.IsNullOrEmpty(firstname))
        users = users.Where(user => user.Firstname == firstname);
    if (!String.IsNullOrEmpty(lastname))
        users = users.Where(user => user.Lastname == lastname);

    return users
}

SearchUser(string lastname, int? id)
{
    var users = from user in allusers
                select user;

    if (!String.IsNullOrEmpty(lastname))
        users = users.Where(user => user.Lastname== lastname);
    if (id.HasValue)
        users = users.Where(user => user.Id == id);

    return users;
}

これで、他のデータの別の検索が行われ、1つの巨大な検索値オブジェクト(30以上の検索プロパティ)と、それをすべてフィルターする1つのメソッドが取得されました。これをユーザーの状況に適用すると、次のようになります。

SearchUser()
{
    // Get values from somewhere
    var searchVO = new UserSearchVO { Firstname = firstname, Lastname = lastname, IsActive = true };
    Service.SearchUser(searchVO);
}
SearchUserSomewhereElse()
{
    // Get values from somewhere
    var searchVO = new UserSearchVO { Lastname = lastname, Id = id };
    Service.SearchUser(searchVO);
}

// some other layer
SearchUser(UserSearchVO searchVO)
{
    var users = from user in allusers
                select user;

    if (searchVO.IsActive.HasValue)
        users = users.Where(user => user.IsActive == searchVO.IsActive);
    if (!String.IsNullOrEmpty(searchVO.Firstname))
        users = users.Where(user => user.Firstname == searchVO.Firstname);
    if (!String.IsNullOrEmpty(searchVO.Lastname))
        users = users.Where(user => user.Lastname == searchVO.Lastname);
    if (searchVO.Id.HasValue)
        users = users.Where(user => user.Id == searchVO.Id);
    if (!String.IsNullOrEmpty(searchVO.SomeFutureValue))
        users = users.Where(user => user.SomeFutureValue == searchVO.SomeFutureValue);

    return users;
}

最初のアプローチが開始アプローチであることを理解しています。2つのパラメーターを持つ検索オブジェクトが必要ない場合は、検索オブジェクトを使用しません。しかし、後者のアプローチでは、クリーンなコードのいくつかの原則に違反するような気がします。

2番目のアプローチを使用すると、クリーンなコードのどの原則に違反しますか?1つのオブジェクトをフィルターするための5000の異なるメソッドがなくても、コードを読み取り可能に保つ別のクリーンな方法はありますか?

既存のUserオブジェクトを大幅に変更したくない場合は、リフレクションを使用して

  • UserSearchVOのプロパティを反復する

  • Userの同じ名前のプロパティを確認します

  • 必要なデータ型のこれらのプロパティの一般的な比較を実装する

このアプローチは、UserSearchVOのプロパティにカスタム属性を提供して、比較を行う方法を宣言することによって拡張できます(@MarcelKirscheによるコメントで述べられているように)。

これは確かに5つのプロパティに対して過剰ですが、実際に30を超える場合は、努力する価値があります。

2
Doc Brown

私はこれがはるかに読みやすいと思います:

// some other layer
SearchUser(UserSearchVO searchVO)
{
    var users = from user in allusers
                select user;

    users = MatchIsActive(users, searchVO);
    users = MatchFirstname(users, searchVO);
    users = MatchLastname(users, searchVO);
    users = MatchId(users, searchVO);
    users = MatchSomeFutureValue(users, searchVO);

    return users;
}

はい、それは舞台裏で少し多くの仕事を作成しますが、それが属する舞台裏でです。一度にすべてのことを見せないでください。これらの関数はUserSearchVOのメソッドである可能性があります。

4
candied_orange

私はビルダーパターンに行きます:

var users = SearchUserWhich
  .IsActive()
  .FirstNameIs('Joe')
  .LastNameIs('Random')
  .Fetch();

これで、.Fetchを呼び出すまで、フィルターを内部的に好きなように組み合わせることができます。

基本的に、SQLを(制限された)DSLでラップしています。理由は何であれ、Nice SQL DSLを使用していないためです(たとえば、その制限が必要な場合や、結合を自動的に処理したい場合など)。

1
9000

UserSearchVOは単なる検索式です

UserSearchVOオブジェクトは検索式を表します。したがって、UserSearchVOクラスがそれ自体をExpression<Func<User,bool>>に変換し、Where句に提供する方法を知っていることは理にかなっているようです。単純な例は次のようになります(おかげで this answer ):

class UserSearchVO
{
    public bool? IsActive { get; set; }
    public bool? Firstname { get; set; }

    public Expression<Func<User, bool>> ToExpression()
    {
        var type = typeof(User);
        List<Expression> expressions = new List<Expression>();
        var parameter = Expression.Parameter(type, "x");

        if (IsActive.HasValue)
        {
            var expression = Expression.Equal(Expression.MakeMemberAccess(parameter, type.GetProperty("IsActive")), Expression.Constant(IsActive.Value));
            expressions.Add(expression);
        }
        if (Firstname.HasValue)
        {
            var expression = Expression.Equal(Expression.MakeMemberAccess(parameter, type.GetProperty("Firstname")), Expression.Constant(IsActive.Value));
            expressions.Add(expression);
        }

        //Put other properties (e.g. Lastname) here

        Expression final = expressions.First();
        foreach (var expression in expressions.Skip(1))
        {
            final = Expression.And(final, expression);
        }

        Expression<Func<User, bool>> predicate =
            (Expression<Func<User, bool>>)Expression.Lambda(final, parameter);

        return predicate;
    }
}

検索メソッドでこれを行うことができます:

SearchUser(UserSearchVO searchVO)
{
    var users = from user in allusers
                select user;
    users = users.Where(searchVO.ToExpression());
    return users;
}

...それは私にはかなり透明に見え、カプセル化を維持します。

他の方法については、最初の方法を活用できます。

SearchActiveUser(string firstname, string lastname)
{
    var search = new UserSearchVO
    {
        IsActive = true, 
        Firstname = firstname, 
        LastName = lastname
    };

    return SearchUser(search);
}
1
John Wu