I'm writing a C# library that's a wrapper around the Yahoo Finance YQL API. I'm stuck on how I want to expose my API to consumers. My design thus far consists of separate interfaces for the different parts of the Yahoo Finance API:
public interface IYahooFinanceCharts {
Task<Image> GetChartAsync(string tickerSymbol, ChartSize size, ChartTimeSpan timeSpan);
}
public interface IYahooFinanceQuotes {
Task<IEnumerable<Quote>> GetQuotesAsync(params string[] tickerSymbols);
}
public interface IYahooFinanceStocks {
Task<IEnumerable<Stock>> GetStocksAsync(params string[] tickerSymbols);
}
public interface IYahooFinanceTickerSymbols {
Task<IEnumerable<TickerSymbol>> SearchAsync(string symbolOrKeyword);
}
I have internal
classes that implement these interfaces to handle the details of building a request for the YQL API and parsing the result of the response. I don't want to expose these classes to consumers.
I was thinking of using a facade to reduce the main entry point into my API to one class. Consumers could cast, instantiate, or register (DI container) the facade using the various interfaces to zero-in on the parts they're interested in.
public class YahooFinance : IYahooFinanceCharts, IYahooFinanceQuotes, IYahooFinanceStocks, IYahooFinanceTickerSymbols
{
// Delegating calls to the internal classes that implement these interfaces...
}
...
IYahooFinanceStocks stocks = new YahooFinance();
var results = await stocks.GetStocksAsync("MSFT", "GOOG");
I'm not sure if I like this because if consumers simply use
var yahooFinance = new YahooFinance();
the entire API is exposed to them, defeating the purpose of the separate interfaces. On the other hand, not using a facade and forcing consumers to instantiate the concrete class for each interface is clumsy, increases the surface area of the API, and I'd have to expose the concrete types...
1 Answer 1
What you're looking for is actually the factory pattern.
Your implementation could be something like
public class YahooFinanceFactory{
public IYahooFinanceQuotes GetQuotes()
{
return new YahooFinanceQuotesConreteImplementation();
}
[...]
}
The concrete implementations are still marked as internal so they are not exposed.
Alternatively, you could use reflection for this.
public static T ResolveConcreteType<T>()
{
return (from t in Assembly.GetExecutingAssembly().GetTypes()
where t.GetInterfaces().Contains(typeof(T))
&& t.IsClass //Find the classes that implement the interface we want
select (T)Activator.CreateInstance(t, null))
.SingleOrDefault();
}
Usage:
var quotes = YahooFinanceFactory.ResolveConcreteType<IYahooQuotes>();
This makes the assumption that your concrete types support empty constructors however.
-
\$\begingroup\$ My concretes types do have empty constructors so that's not a problem. My concern with this design is that it's duplicating the work of a DI container (i.e. resolving concrete types from interfaces) and is essentially a service locator. \$\endgroup\$Brett– Brett2014年05月20日 22:04:29 +00:00Commented May 20, 2014 at 22:04