I have a following design in Java (7) application:
There is a method where i pass collection of objects of some type and object that I call "predicate" that is used to filter given collection.
Predicate is an interface with one method called test - it takes an object and return a boolean value.
In such situation:
Now I would like to have similar design in C# (4.0) application. I can see two way of doing it - by mimic Java design, or by change Predicate into delegate:
I can try to mimic Java design, but:
I can try achieve similar design using delegates. My filter method will take collection of object and Predicate delegate. In such case:
My question is - what in your opinion will be best way of achieve the same (or as similar as possible) design that I have in Java, that will be considered as correct, clean C#-approach of doing that?
I have few years of experience in programming in Java, but less that a year in C#, so I understand that maybe some problems that I see just doesn't exist in C# world or are not considered as problems at all.
EDIT: here is simplest (I think...) possible example of how my Java code work:
My "domain" object:
public class Person {
private final String firstName;
private final String secondName;
public Person(String firstName, String secondName) {
this.firstName = firstName;
this.secondName = secondName;
}
public String getFirstName() {
return firstName;
}
public String getSecondName() {
return secondName;
}
}
Filtering class:
public class Filter {
public Collection<Person> filter(Collection<Person> collection, Predicate predicate) {
Collection<Person> result = new LinkedList<Person>();
for(Person person: collection) {
if(predicate.test(person)) {
result.add(person);
}
}
return result;
}
}
Predicate interface:
public interface Predicate {
boolean test(Person person);
}
Two simple predefined implementations:
public class FirstNameStartsWithPredicate implements Predicate {
private final String startsWith;
public FirstNameStartsWithPredicate(String startsWith) {
this.startsWith = startsWith;
}
public boolean test(Person person) {
return person.getFirstName().startsWith(startsWith);
}
}
public class LastNameEndsWithPredicate implements Predicate {
private final String endsWith;
public LastNameEndsWithPredicate(String endsWith) {
this.endsWith = endsWith;
}
public boolean test(Person person) {
return person.getSecondName().endsWith(endsWith);
}
}
Utility class:
public final class PredicateUtils {
public static Predicate and(final Predicate first, final Predicate second) {
return new Predicate() {
public boolean test(Person person) {
return first.test(person) && second.test(person);
}
};
}
public static Predicate or(final Predicate first, final Predicate second) {
return new Predicate() {
public boolean test(Person person) {
return first.test(person) || second.test(person);
}
};
}
public static Predicate allwaysTrue() {
return new Predicate() {
public boolean test(Person person) {
return true;
}
};
}
}
And finally, example of usage:
Collection<Person> persons = Arrays.asList(
new Person("John", "Done"),
new Person("Jane", "Done"),
new Person("Adam", "Smith")
);
Filter filter = new Filter();
// Predefined predicates
filter.filter(persons, new FirstNameStartsWithPredicate("J"));
filter.filter(persons, new LastNameEndsWithPredicate("e"));
// anonymous implementation
filter.filter(persons, new Predicate() {
public boolean test(Person person) {
return person.getFirstName().equals("Adam") && person.getSecondName().equals("Smith");
}
});
// utility class
filter.filter(persons, PredicateUtils.allwaysTrue());
filter.filter(persons, PredicateUtils.and(new FirstNameStartsWithPredicate("J"), new LastNameEndsWithPredicate("e")));
filter.filter(persons, PredicateUtils.or(new FirstNameStartsWithPredicate("J"), new FirstNameStartsWithPredicate("A")));
This is totally doable in C#. Let's say we have a few users, and filters to filter them, some filters are built-in and some are implemented by users. Here we should use interfaces like Java instead of delegates.
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
}
public interface IPredicate<T>
{
bool IsValid(T entity);
}
public class UserPredicate : IPredicate<User>
{
/* built-in predicates */
public static UserPredicate Adult = new UserPredicate(u => u.Age >= 18);
public static UserPredicate NoAddress = new UserPredicate(u => string.IsNullOrEmpty(u.Address));
public Func<User, bool> Predicate { get; private set; }
public UserPredicate(Func<User, bool> predicate)
{
this.Predicate = predicate;
}
bool IPredicate<User>.IsValid(User entity)
{
return this.Predicate(entity);
}
}
Users can easily add new predicates like this:
//user's code
var custom = new UserPredicate(MyCustomUserFilter);
bool MyCustomUserFilter(User u)
{
//user's filter logic
}
It's not the same as Java, because in C# anonymous types can't implement an interface.
And also it's very easy to "combine" the predicates into a new one.
var AdultWithNoAddress = new UserPredicate(u => UserPredicate.Adult.Predicate(u)
&& UserPredicate.NoAddress.Predicate(u));
EDIT To make the combination of predicates more clearer, you can put the combination logic into predicates itself.
public interface IPredicate<T>
{
bool IsValid(T entity);
IPredicate<T> And(IPredicate<T> another);
IPredicate<T> Or(IPredicate<T> another);
}
public class UserPredicate : IPredicate<User>
{
public static UserPredicate Adult = new UserPredicate(u => u.Age >= 18);
public static UserPredicate NoAddress = new UserPredicate(u => string.IsNullOrEmpty(u.Address));
private Func<User, bool> _predicate;
public UserPredicate(Func<User, bool> predicate)
{
_predicate = predicate;
}
public bool IsValid(User entity)
{
return _predicate(entity);
}
public IPredicate<User> And(IPredicate<User> another)
{
return new UserPredicate(u => this.IsValid(u) && another.IsValid(u));
}
public IPredicate<User> Or(IPredicate<User> another)
{
return new UserPredicate(u => this.IsValid(u) || another.IsValid(u));
}
}
//usage
var AdultWithNoAddress = UserPredicate.Adult.And(UserPredicate.NoAddress);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With