ユーザーを検索する複数のページがあり、各サイトには異なる検索パラメーターがあります。場合によっては、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を超える場合は、努力する価値があります。
私はこれがはるかに読みやすいと思います:
// 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
のメソッドである可能性があります。
私はビルダーパターンに行きます:
var users = SearchUserWhich
.IsActive()
.FirstNameIs('Joe')
.LastNameIs('Random')
.Fetch();
これで、.Fetch
を呼び出すまで、フィルターを内部的に好きなように組み合わせることができます。
基本的に、SQLを(制限された)DSLでラップしています。理由は何であれ、Nice SQL DSLを使用していないためです(たとえば、その制限が必要な場合や、結合を自動的に処理したい場合など)。
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);
}