A filter system, for retrieving data from a ListFrom WikiJavabuy this book
In the article Downloading stock market quotes from Yahoo! finance I showed you how to download stock quotations using Yahoo's Ichart service, and to save them in a The reason why Once you dump in the In this article I will introduce a mechanism to retrieve and select specific DataRanges from Product, in a very dynamic way. We'll do it using a filtering mechanism, that has several benefits:
In the article I will explain the whole concept using also some class diagrams.
The modelIn extreme synthesis the class diagram of my domain model is like in the image below, which shows what the Product contains, and what's the structure of a DataRange: Where This is very versatile, because as long as my new The problem (which I solve in this article) is that once a DataRange object is saved in the I'll analyse the problem in the next section. How to retrieve the dataAs a matter of facts, to solve this problem we will probably need to redesign my We could start thinking about a solution like: You could put in each option a String (or similar) where, by convention, you encode the information necessary to uniquely identify the data. In this way when you want to retrieve, you just formulate a matching String and search in the ArrayList for a DataRange matching it. This would work, unfortunately the convention needs to be very well designed, so to be able to deal with evolutions of the software, which we can't foresee now. Additionally the convention is an additional complexity to the system, and we don't want to go there. The best solution here should not require any modification to the You could keep somehow a list of what you have stored in the
If you think carefully to it this solution is not much different from the previous. It just prevents you from saving data inside the You could do a data traversal (i.e. iterating through the List) and check each DataRange against the properties you need to retrieve, for instance you could use the
This is indeed a good idea, as it does not require any addition to the implementations of the My solution is very similar to this last, the difference is that it centralizes the selection process of the implementation in a way that it improves drastically the maintanability, reduces at the most the quantity of code to write eacn time you need to retrieve the data, and maintains extremely well the expandibility of the software. In the next sections I'll explain in detail how the solution works and provide parts of the code that implements it. The solutionThe solution is shown in the following class diagram (again in it's most essential form): The earth of everything is the I'll start from the Filter interface and with example implementations of it. The filtersThe filters are very simple classes that implement the package org.wikijava.stockfriend.model.quotations.filters; import org.wikijava.stockfriend.model.quotations.DataRange; public interface Filter { boolean matches(DataRange datarange); } For example a simple filter on the type is: package org.wikijava.stockfriend.model.quotations.filters; import org.wikijava.stockfriend.model.quotations.DataRange; import org.wikijava.stockfriend.model.quotations.DataRangeType; public class TypeFilter implements Filter { private DataRangeType type; public TypeFilter(DataRangeType type) { this.type = type; } @Override public boolean matches(DataRange datarange) { return datarange.getType() == this.type; } } It just returns The fun comes with the next component: the One level of abstraction up, the DataRangeFilterThe It's really all as simple as: public boolean matches(DataRange datarange) { for (Filter curFilter : filters) { if (!curFilter.matches(datarange)) { return false; } } return true; } This simple yet powerful mechanism assures that we can arbitrarily retrieve any of the DataRanges stored, with great versatility, and being able to reuse the code. We can even generate a new Class, containing as public static final a list of the most used filters, not to have to regenerate them all the time. Before going to the next section I just would like to show you a couple more methods from this class, that are also very powerful and I'll use in the next section. The first is a constructor: public DataRangeFilter(Filter... filter) { filters = new ArrayList<Filter>(); if (filter == null) { return; } for (int i = 0; i < filter.length; i++) { filters.add(filter[i]); } } Which basically adds any filter passed in the constructor to the DataRangeFilter being created. The second method is the public DataRangeFilter addFilter(Filter filter) { if (this.filters.size() == 0) { this.filters.add(filter); return this; } DataRangeFilter result = new DataRangeFilter(this); result.filters.add(filter); return result; } Which maintains the immutability of the class, by returning a new object (generated using the copy constructor, omitted in this article). Defining the default filtersSaid all the above, we can think of several, most commonly used, DataRangeFilters. We can predefine them somewhere, as static final variables, so that we can reuse them when we like. Here's the (very simple) class diagram: Here's the content of a possible package org.wikijava.stockfriend.model.quotations.filters; import org.wikijava.stockfriend.backend.calculations.indicators.LowCrossingIndicator; import org.wikijava.stockfriend.backend.calculations.indicators.LowOscillatorIndicator; import org.wikijava.stockfriend.backend.calculations.indicators.MovingAverage; import org.wikijava.stockfriend.backend.calculations.indicators.RSI; import org.wikijava.stockfriend.model.quotations.DataRangeType; import org.wikijava.stockfriend.model.quotations.OriginalDataRange; public class DefaultFilters { /** * filters the close Dataranges */ public final static DataRangeFilter closes = new DataRangeFilter( new ClassFilter(OriginalDataRange.class), new TypeFilter( DataRangeType.Close)); /** * filters all the moving averages */ public final static DataRangeFilter movingAverage = new DataRangeFilter( new ClassFilter(MovingAverage.class), new TypeFilter( DataRangeType.MovingAverage)); /** * filters all the RSI Objects */ public static final DataRangeFilter RSI = new DataRangeFilter( new ClassFilter(RSI.class), new TypeFilter(DataRangeType.Momentum)); /** * filters all the LowOscillatorIndicator Objects */ public static final DataRangeFilter LowOscillatorIndicator = new DataRangeFilter( new ClassFilter(LowOscillatorIndicator.class), new TypeFilter( DataRangeType.Signal)); /** * filters all the LowCrossingIndicator Objects */ public static final DataRangeFilter LowCrossingIndicator = new DataRangeFilter( new ClassFilter(LowCrossingIndicator.class), new TypeFilter( DataRangeType.Signal)); } As you can see all these DataRangeFilters are generated using the constructor mentioned above. These default DataRangeFilters can also be espanded anytime, for example by: DataRangeFilter filter = DefaultFilters.RSI .addFilter(new PeriodsFilter(periods)); Which thanks to the immutability of the Using the whole filtering systemAt this point, the whole system is ready to be used, and there's nothing to add to it. To use it it is very simple. The following spike puts it all together:
package org.wikijava.stockfriend.backend.controller; import org.wikijava.stockfriend.backend.data.YahooDownloader; import org.wikijava.stockfriend.model.DownloaderException; import org.wikijava.stockfriend.model.calendar.TradingDay; import org.wikijava.stockfriend.model.calendar.TradingDaysRange; import org.wikijava.stockfriend.model.product.BaseProduct; import org.wikijava.stockfriend.model.quotations.filters.DefaultFilters; /** * <Replace this with a short description of the class.> * * @author Giulio Giraldi */ public class DownloadDataAndRetrieveOpens { public static void main(String[] args) { YahooDownloader yahooDownloader = new YahooDownloader(); BaseProduct product = new BaseProduct("YHOO", "Yahoo! inc."); TradingDay start = TradingDay.getSingleton(-300); TradingDay end = TradingDay.getSingleton(); TradingDaysRange range = new TradingDaysRange(); range.setStart(start); range.setEnd(end); try { yahooDownloader.getQuotationRange(product, range); } catch (DownloaderException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(product.getRanges(DefaultFilters.closes)); } } Final notesIn this article I showed you a very simple way to extract data from a List in a very versatile way. I showed it applied to the financial context, but it can be easily reapplied to many other contexts. Seen carefully the mechanism implemented here looks quite similar to the design pattern Chain of responsibility, this is because the action is passed, one by one to each of the filters, who have the responsibility of refusing a DataRange if it doesn't match. I know you won't be able to execute the code in this article, because I omitted quite some code, I omitted it for conciseness. Though, all I omitted is trivial, and it should not be a problem rewriting the required code.
|



