简体   繁体   中英

Interface method that has different parameters in Java

Looking for some guidance on designing some code in Java.

Currently I have something like this....

@Service
class SomeService {
    @Autowired
    private FilterSoldOut filterSoldOut;
    @Autowired
    private FilterMinPriceThreshold filterMinPriceThreshold;

    public List<Product> getProducts() {
        List<Product> products = //...code to get some products

        // Returns list of in-stock products
        products = filterSoldOut.doFilter(products); 

        // Returns list of products above min price
        products = filterMinPriceThreshold.doFilter(minPrice, products);

        return products; 
    }
}

What I would like to be able to do is create a Filter interface with a doFilter method and then in SomeService create a List filters, which is autowired by Spring. Then in the getProducts method I can iterate the filters list and invoke doFilter. This way in the future I can, create new classes that implement the Filter interface and add them to the list via Spring configuration, and have the new filter applied without having to change the code.

But, the problem is that the parameters to the doFilter method can be different. I've read about the Command Pattern, and the Visitor Pattern but they don't quite seem to fit the bill.

Can anyone suggest a good pattern to achieve what I've described?

Thanks.

There are many ways to do this. Some are complicated, some are simpler. The simplest one would be to use varargs or an array of Object elements. The problem here is that you have to cast each objetc to its proper type in order to use them and that can be a little tricky if there are multiple types in an unknown order.

Another option is to use a Map<String,Object> (which you can wrap in a class of your own if required, something lile FilterParams ) that stores parameters based on a name, and you can then obtain them and cast them accordingly.


Edit

Considering that the parameters vary on runtime, you'll need someone "well informed" about the current configuration.

Not pattern-wise but I'd rather keep it simple without using too many fancy names. What about introducing a FilterConfigurator that has a simple overloaded method configure that recieves the particular filter and configures it based on its type?. This configurator is the informed entity that knows the current values for those parameters.

The goal is to rid Service from the responsibility of configuring a filter.

In addition, if you create your Filter class, you'll be able to implement a single doFilter that you can invoke without changes.

There's another Idea... and it involves a FilterFactory that creates and initializes filters, thus having a filter 100% configured from scratch. This factory can rely on the very same FilterConfigurer or do it itself.

As Cris say you can use next function definition:

  public List<Product> doFilter(Object...args) {
    if (args.length != 2)
      throw new IllegalArgumentException();
    if (! (args[0] instanceof String))
      throw new IllegalArgumentException();
    if (! (args[2] instanceof Integer))
      throw new IllegalArgumentException();

    String stringArgument = (String) args[0];
    Integer integerArgument = (Integer) args[1];

    // your code here

    return ...;
  }

or with command pattern:

public interface Command {
}

public class FirstCommand implements Command {
  private String string;

  // constructor, getters and setters
}

public class SecondCommand implements Command {
  private Integer integer;

  // constructor, getters and setters
}

// first service function
public List<Product> doFilter(Command command) {
  if (command instanceof FirstCommand)
    throw new IllegalArgumentException();
  FirstCommand firstCommand = (FirstCommand) command;

  return ...;
}

// second service function
public List<Product> doFilter(Command command) {
  if (command instanceof SecondCommand)
    throw new IllegalArgumentException();
  SecondCommand secondCommand = (SecondCommand) command;

  return ...;
}

EDIT:

Ok, i understand your question. And think you can create various session scoped filters.

@Service
class SomeService {
    @Autowired(required = false)
    private List<Filter> filters;

    public List<Product> getProducts() {
        List<Product> products = //...code to get some products

        if (filters != null) {
          for (Filter filter : filters)
            products = filter.doFilter(products);
        }

        return products; 
    }
}

And then create filters with settings fields:

public PriceFilter implements Filter {
  private Integer minPrice;
  private Integer maxPrice;

  // getters and setters

  public List<Product> doFilter(List<Product> products) {
     // implementation here
  }
}

public ContentFilter implements Filter {
  private String regexp;

  // getters and setters

  public List<Product> doFilter(List<Product> products) {
     // implementation here
  }
}

Then user can configure this filters for session and use service function getProducts to get result.

old :

I'd suggest you setting the filter state at construction time or at least before you getProducts() .

In your example with the two filters one of them is (probably) checking a database for availability of the product and the other one is comparing the product's price to some preset value. This value ( minPrice ) is known before the filter is applied. It can also be said that the filter depends on it, or that it's part of the filter's state. Therefore I'd recommend you putting the minPrice inside the filter at construction time (or via a setter) and then only pass the list of products you want to filter. Use the same pattern for your other filters.

new suggestion (came up with it after the comments):

You can create a single object (AllFiltersState) that holds all the values for all the filters. In your controller set whatever criteria you need in this object (minPrice, color, etc.) and pass it to every filter along the products - doFilter(allFiltersState, products).

Having a list of filters getting autowired is not a very good approach to solve your problem. Every filter depends on different types of parameters which would need to be passed to the doFilter method. Needing to do so makes the approach highly unflexible. Yes you could use varargs but it would just create a mess. That's why it's probably easier to implement a builder to build you a chain of filters to be applied to the collection of products. Adding new filters to the builder becomes a trivial task. The Builder Pattern is very useful when a lot of different parameters are at play.

Consider having this interface:

public interface CollectionFilter<T> {
    public Collection<T> doFilter(Collection<T> collection);
}

A filter chaining class which applies all filters to the collection:

public class CollectionFilterChain<T> {
    private final List<CollectionFilter<T>> filters;

    public CollectionFilterChain(List<CollectionFilter<T>> filters) {
        this.filters = filters;
    }

    public Collection<T> doFilter(Collection<T> collection) {
        for (CollectionFilter<T> filter : filters) {
            collection = filter.doFilter(collection);
        }

        return collection;
    }
}

The two CollectionFilter<T> implementations:

public class InStockFilter<T> implements CollectionFilter<T> {

    public Collection<T> doFilter(Collection<T> collection) {
        // filter
    }
}


public class MinPriceFilter<T> implements CollectionFilter<T> {

    private final float minPrice;

    public MinPriceFilter(float minPrice) {
        this.minPrice = minPrice;
    }

    public Collection<T> doFilter(Collection<T> collection) {
        // filter
    }
}

And a builder to let you build the filter chain in a easy way:

public class CollectionFilterChainBuilder<T> {
    List<CollectionFilter<T>> filters;

    public CollectionFilterChainBuilder() {
        filters = new ArrayList<CollectionFilter<T>>();
    }

    public CollectionFilterChainBuilder<T> inStock() {
        filters.add(new InStockFilter<T>());
        return this;
    }

    public CollectionFilterChainBuilder<T> minPrice(float price) {
        filters.add(new MinPriceFilter<T>(price));
        return this;
    }

    public CollectionFilterChain<T> build() {
        return new CollectionFilterChain<T>(filters);
    }
}

With the builder it's easy to create a filter chain as follows:

CollectionFilterChainBuilder<Product> builder = 
    new CollectionFilterChainBuilder();

CollectionFilterChain<Product> filterChain = 
    builder.inStock().minPrice(2.0f).build();

Collection<Product> filteredProducts = 
    filterChain.doFilter(products);

In a more dynamic settings you could use the builder like:

CollectionFilterChainBuilder<Product> builder = new CollectionFilterChainBuilder();

if (filterInStock) {
    builder.inStock();
}

if (filterMinPrice) {
    builder.minPrice(minPrice);
}

// build some more

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM