Here's briefly what I'm trying to do.
The user supplies me with a link to a photo from one of several photo-sharing websites (such as Flickr, Zooomr, et. al). I then do some processing on the photo using their respective APIs.
Right now, I'm only implementing one service, but I will most likely add more in the near future.
I don't want to have a bunch of开发者_如何学Go if/else or switch statements to define the logic for the different websites (but maybe that's necessary?) I'd rather just call GetImage(url) and have it get me the image from whatever service the url's domain is from. I'm confused how the GetImage function and classes should be designed.
Maybe I need the strategy pattern? I'm still reading and trying to understand the various design patterns and how I could make one fit in this case. I'm doing this in C#, but this question is language-agnostic.
You don't really need a pattern yet. Just implement the service in a clean fashion, so that somewhere there is an Image FlikrApi.GetImage(Url)
method.
// client code
Image image = flickApi.GetImage(url);
When you come to implement your second service, then you will have some requirements as to how to decide which function to call based on the Url. You can then decide how to do it - for two services it might be as simple as switching on the top-level domain name. So you have a switch which calls one or another method on one or another object.
readonly FlickrApi flikrApi = new FlickrApi();
readonly WhateverApi whateverApi = new WhateverApi(); // third party
Image GetImage (Url uri) {
switch (url.TopLevelDomain()) {
case "flickr.com":
return flikrApi.GetImage(url);
break;
case "whatever.com":
return whateverApi.GetWhateverImage(url);
break;
default:
throw new UnhandledUriException(uri);
}
// client code
Image image = GetImage(uri);
Learn to count - one, two, many. When you hit many, then think about refactoring to a pattern. It might be that you don't get to implement more than two services, as you're working on a framework for them, rather than doing something useful.
If you do have a requirement for more dynamic services, and can select based on the top-level domain only, then I'd probably have a map - a Dictionary<string, Func<Url,Image>>
populated with the tlds and service delegates.
readonly Dictionary<string, Func<Url,Image>> apis = new ...;
ImageApi () {
apis["flickr.com"] = new FlickrApi().GetImage;
apis["whatever.com"] = new WhateverApi().GetWhateverImage;
apis["zzze.com"] = (uri) => Zoobers.GetWhateverImage(new ZooberCreds(), uri.ToString());
}
static Image GetImage (Url uri) {
string tld = urli.TopLevelDomain();
if (!imageApis.ContainsKey(tld)) throw new UnhandledUriException(uri);
return imageApis[tld](uri);
}
// client code unchanged
In a language without closures/delegates you'd define an interface and use those, but C# is better than that, and using the built-in function type lets you use any suitable function rather than having to create a class just to conform to an interface. It's not quite the strategy pattern as there is no structural relationship between a context and a strategy - in the strategy pattern, there is a context which has-a strategy, one strategy only, and that strategy can change. Here we're selecting between strategies based on a simple condition.
If you have more complicated requirements on deciding which service to use, then you might end up iterating over a list of interfaces to IImageApi
, where the interface includes a bool HandlesUrl(Url)
method to ask the service whether it recognises the Url. In that case, instead of using delegates to talk to any third-party code, you would have to use a wrapper.
interface IImageApi {
bool HandlesUri(Url);
Image GetImage(Url);
}
readonly List<IImageApi> apis = new ...;
ImageApi () {
apis.Add(new FlickrApi()); // you wrote this, so it can implement the interface
apis.Add(new WhateverApiAdapter()); // third party requires adapter
apis.Add(new ZoobersApiAdapter()); // ditto
// or you can use something like MEF to populate the list
}
static Image GetImage (Url uri) {
foreach (var api in apis)
if (api.HandlesUri(uri))
return api.GetImage(uri);
throw new UnhandledUriException(uri);
}
// client code unchanged
Do the simplest thing first, the second-most simplest thing second and the most complicated thing if you have to.
Yes, Strategy sounds like a good solution to this. An alternative could be Template Method.
The strategies associated to different sites can be produced by an Abstract Factory / Factory Method, but if they are stateless, they could also be simply stored in a map, with keys being the relevant part of the site URL.
Yes, strategy pattern is a good choice.
You should define an interface
to define your own API, and after to implement it in several strategies.
Take a look in wikipedia the c# example : Strategy pattern
interface IStrategy
{
void Execute();
}
// Implements the algorithm using the strategy interface
class ConcreteStrategyA : IStrategy
{
public void Execute()
{
Console.WriteLine( "Called ConcreteStrategyA.Execute()" );
}
}
class ConcreteStrategyB : IStrategy
{
public void Execute()
{
Console.WriteLine( "Called ConcreteStrategyB.Execute()" );
}
}
Looks like you want to do two things:
- Separate processing implementations for each service. Here, you can use the strategy pattern.
- Hide the fact that there are different processing implementation details for each service from you caller. Here, use a factory pattern. The factory pattern will encapsulate any logic that you write to distinguish between services, and construct the appropriate strategy.
In general, I'd recommend you delay your abstractions and usage of design patterns to the point where it helps you -- Different patterns solve different problems. A bunch of design patterns that makes your code harder to read doesn't serve much purpose (unless you're just trying to learn about design patterns).
Other patterns you may find useful:
- Adapter Pattern - Useful if you want to define your own API, and "adapt" each particular service's API to fit your own.
- Template Method Pattern - Useful if your algorithm stays essentially the same, but you need particular sections to be "pluggable" depending on the implementation
If you're using only one method GetImage
then strategy is a good choice, you can use Factory method to set the correct factory.
class Context
{
IStrategy strategy;
String url;
void GetImage( url )
{
strategy = StrategyFactory.GetStrategyFor( url );
image = strategy.GetImage( url );
}
}
IStrategy
{
Image GetImage( string url );
}
FlickrStrategy:IStrategy
{
Image GetImage( string url )
{
FlickerService service = new FlickrService();
service.authenticate();
//
return service.get( url );
}
}
ImageShackUs:IStrategy
{
Image GetImage( string url )
{
HttpClient service = new HttpClient(url);
service.Connect();
byte [] data = service.read()
......
}
}
If you're using more methods, then the structure is basically the same but is not called strategy, it is simple delegate object.
精彩评论