I'll offer some general problem-solving advice first then use it on your problem so you can see how someone with more experience might approach this.
###Problem Domain
As you may have read elsewhere, your design and your code are a representation of your understanding of the problem you're trying to solve. So, before you start coding and before you start designing, you need to analyze the problem itself; this helps make design of your solution easier to create. Along the way, if you can simplify the problem or your view of the problem, then that'll make the solution simpler as well! And simple is good.
When I'm having trouble designing or coding something, a lot of the time it's because I haven't gotten a full understanding of the part of the problem I'm working on. Other times it's because I'm not familiar with some design/implementation technique that would make things easier, so it's good that you're reading up on things!
When you're analyzing a problem, look at who the entities of the problem domain are and how they interact: who does what to whom, who gets what from whom, what processes there are, where does data flow from/to, etc. If you can enumerate the entities and their interactions, this will help ensure that the solution you come up with is complete.
###Design
When you design your program, you take the concepts from your problem space and try to map it into whatever language/paradigm you're working in. In the case of OOP, this is objects and methods.
When designing for OOP, you should look at the nouns and verbs in your problem space: who does what to whom, who gets what from whom, etc. These will map into the objects (the "who(m)") and methods (the "does").
One thing which occurred to me while thinking about this is that it would be beneficial to consider which nouns are the subjects of an action and which are the objects -- i.e. to distinguish the "who" vs "whom". These should naturally lead to what objects perform which action on what. E.g.:
person.putOn(hat)
In this, the person
is the one acting on the hat
, so the method belongs to the person.
Although, sometimes a design might work out better if you reverse things:
hat.sitOn(person)
It depends on the rest of the design and how things map. These are things to look out for and consider.
Hard-and-fast OOP doesn't always allow for things to be mapped from the problem space in the most succinct way; sometimes actions don't seem like they belong to a particular object. In such cases, you could use just a plain function -- otherwise you'll get the noun-ification of verbs that you've noticed into classes. (Others have noticed this as well.)
###Your Project
Let's take the above and apply it to your project.
First we'll look at the problem domain.
The aim of the software is to download images and video from my favourite blogs. (eg, Google Blogger blogs, or WordPress hosted blogs.)
What are the players? The actions? The processes? How are things related (in time, in location, etc.)?
Well, there are blogs, blogs have posts, and posts have media. Blogs are hosted by services. Services have APIs which let you access the blogs/posts/media* -- the APIs allow you to download a blog (feed) in a particular format (JSON, XML etc.) and the blog's content will be arranged in a specific structure in this format; part of this content will be references to media.
*I'm not sure if any of the APIs you will be using will be required to access media items of the service's posts, or whether you can access them via regular web URLs, so we'll err on the side of caution here and assume you need an API to access them.
Now let's consider how data flows in the problem domain: You send a request to the API of the service and get a response with feed content, which includes references to media items. You extract those media references, then use the API to get a copy of the media items, then save them to disk in files. (I find it helpful to draw this sort of interaction out on paper, say with a sequence diagram.)
Next, let's see how the problem domain maps to OOP (+ functions):
I said above that nouns in the problem domain will be mapped to objects in your program. This is true, but not necessarily everything will be mapped; some of the nouns in this problem space are at remote locations (i.e. the servers), so you might not need to model them in your program (but it's handy to know they're there).
Starting with the big items: On the remote side of things, there is the Service. The Service has an API. On our side of things, we have Us (the program), and something to talk to the Service's API. So, the API connection -- or client -- is a concept that we need to implement, which you have done already.
Our client will be connecting to the API to download a blog feed, so the API client class (noun) should have a downloadFeed
method (verb/action).
This is where things get a bit tricky design-wise, as you've found, because we have multiple levels of abstraction for essentially the same concept: there's the abstract notion of a blog and its posts & media, but there is also the more concrete representation (JSON, XML) of the blog feed that the API client will receive from the service.
So, what should we do? Well, if the API client is the only thing that is going to deal with the feed, then we can probably just leave it in the concrete representation in each of the different API clients. But if we are going to have anything else in the program deal with the feed data, then we should use some common representation (e.g. a blog feed class, a blog post class, and a media item or media reference class) which all the API clients should return for rest of the program to use.
Should the feed data be used by other parts of the program? My answer is: yes. We can turn to a couple of Best Practices to see why: the Single Responsibility Principle (SRP), and Separation of Concerns (SoC).
SRP states that every class or method should be responsible for doing one thing, and one thing only. The purpose of our API clients is to communicate with the different APIs on behalf of the rest of the program. Let's look at how keeping our data inside the API client class would fit in with this:
We download data from the API with the client and get a specific representation of the feed. This is ok, it's what the client is supposed to do.
Next, we extract media information from the feed; this is something of a sub-responsibility in this case: the feed format is directly related to the API, but the API client is only for communicating with the API. In this case, extraction is an action so we'll want to use a function, but it would be tolerable to have this function be part of the API client class. A helper class might be needed to help do the extraction, if extracting the data is complex enough -- this is what you have constructed as your Parser
type, which I'll address in a little bit.
Then, the images have to be downloaded (an action, thus a method), which we decided earlier is in the realm of the API client, since it has to communicate with the API to do this.
Finally, the images have to be cached to disk (another action, though this one might need to be noun-ified). This is in violation of the responsibilities of our API client: the client is for communicating with the service API, not for caching things to disk -- caching is a separate concern! Furthermore, having the clients do the caching would mean that the same logic would have to be repeated in every client, which is a violation of DRY.
So, it will be better to have the caching logic outside of the API client so that it can be used across clients. This means that we will need have to have an external, general representation of the feeds so that they can be consumed by the caching code.
Each API client then has to transform (verb) its specific representation ("whom" noun) of the feed to the general representation (another "whom" noun) for use outside the client. This should be done by a function (maybe with a helper class).
So far, our flow is as follows: The API client downloads an API-specific representation of the blog feed, it transforms this representation into a general representation, it then returns this representation to the outside program. The outside program takes this general representation and gives it to the caching logic, which caches it to disk.
This flow is good, except we're missing one thing: the media! We still have to download the images and videos!
The media has to be downloaded at some point between when the blog feed is downloaded and when things are cached to disk -- this is another design point: you should look at when things can be done and when they should be done.
One obvious point to download the media would be right after the data from the feed has been extracted and put into the general representation, but before it is returned from the API client to the rest of the program. The media could be added to the output of the feed function; so things would go something like this:
def downloadFeed():
rawFeed = self.downloadRawFeed()
commonFeed = self.extractFeedDataFrom(rawFeed)
self.downloadAndPopulateImagesOf(commonFeed)
return commonFeed
There is an alternative to this, and that is to have the media downloaded right before they're cached -- this way, you won't end up with a feed that contains every single media item from that feed in memory at once!
What you could do is populate the general representation with, say, instances of a MediaReference
class that holds the information about the media, along with a reference to the API client so that it can actually download it, then the caching code can tell the MediaReference instances to download their media to give to it, one at a time (so that they're not in memory all at once).
And there you have some input on the design of this thing. (Damn, what a long post!)
I actually don't know if I have made good choices here.
Your start was pretty good, so keep at it! You can do it!
###Parser
A quick note on what you're calling a parser.
As some noted in their comments, what you're calling a parser is not an actual parser, though I've seen many people refer to such as a parser, so don't feel bad about that. A real parser (see the computer science defn), along with a lexer, takes an input string (say, some JSON) and turns it into a tree of objects, a hierarchy. That's it. It doesn't do anything with that data after that point.
What you're wanting to do is to scrape the data for information. A parser (either a JSON or XML parser, depending on what your APIs use) could be helpful for this, getting the API data into a form which is easily scrapable by your code so you don't have to do a bunch of string manipulation.
Ok, I'm done now.