
| What is MVC? |
| 摘自: www.oreillynet.com 被阅读次数: 328 |
由 yangyi 于 2007-07-06 19:04:32 提供 |
If you read up on the Model-View-Controller (MVC) design pattern, you might find yourself a bit confused. In fact, I found myself confused by it when I first started reading about it, because there are plenty of resources out there to describe it, but so many of them seem to have different flavors of MVC and different diagrams explaining how data flows that it’s no wonder that programmers are bewildered about it. Fully believing that I don’t want perfect to be the enemy of the good, I’ll show a few practical implementation details of one way of looking at MVC, primarily focused on the Web needs. The primary thing you need to know about MVC is how logic can be distributed:
Just like HTML is moving to a separation of structure (semantic XHTML) and presentation (CSS), so should your applications be geared to understanding the different problem domains and separating out their functionality. The details of this separation can vary widely based on technical needs and developer opinions, but it should happen. With MVC, we have one form of separation which is fairly clear. Your view roughly corresponds to what people see (but it might be an RSS feed that a client is reading). Your model is your data model and the business rules. Your controller only contains application logic and tends to act a bit like a mediator between the the model and the view (different implementations can vary widely on this). The view is the simplest bit as far as the Web is concerned. You typically build HTML pages and the user can fill in forms or click on links and stuff happens. The HTML is what your end user usually sees and there should be little to know application or business logic there. In fact, that’s one of the reasons AJAX is so popular. When done properly, that logic isn’t duplicated in the view layer. When done improperly, it can become a maintenance nightmare. How many times have you written Javascript form validation only to have to rewrite in your back end code? More than once I’ve found that my front end and back end validation didn’t synch. AJAX is a great way of alleviating some of this pain. Getting back to the presentation layer, imagine the following HTML template (written with Template Toolkit syntax. The rules of the template are that users can win “points” and choose prizes based on those points. Further, they should only see the items they have the points to purchase. However, if you’re an administrator of the site, you should see all prizes when you’re administering things. <ul>
[% FOREACH prize IN prize_category %]
[% IF user.points > prize.points %]
<li>[% display(prize).as_html %]</li>
[% END %]
[% END %]
</ul>
The above template seems nice, but you see that conditional in there? What happens when you want to reuse this template? The administrator won’t be able to see all prizes unless they have enough points! You could give administrators an arbitrarily large number of points, but that’s an ugly hack and not truly reflective of what you’re trying to do. It would be better to having something like this in your controller code (Perl psuedo-code): my @prizes = $category->fetch_prizes($user); $view->display( prize_category => \@prizes ); Assuming that the fetch_prizes() method understands the difference between regular users and administrators, your template then becomes reusable: <ul>
[% FOREACH prize IN prize_category %]
<li>[% display(prize).as_html %]</li>
[% END %]
</ul>
Now why do we have as_html at the end of that? Well, what if you want to send data in plain text, as a GIF image, or in PDF format? Your display() function should know how to render your objects in various different ways so that you have a flexible system which can render things any way your end-user wants it. We used a similar system with one company where we resold data and customers could view the data as HTML, PDF, or Microsoft Excel spreadsheets. Because we kept our views very generic and only included presentation logic, we didn’t have to worry about needlessly duplicating a bunch of logic in our presentation layer. A more direct problem, though, is something I see all the time in novice’s Perl code. You need to fix a bug in their code which adds products to a shopping cart and you look at the function which contains this: sub add_product_to_order {
my ( $dbh, $p_id, $o_id ) = @_;
unless ( $o_id && $o_id =~ /^\d+$/ ) {
dienice("Bad order number $o_id");
}
my $o_sql = 'SELECT * FROM orders WHERE id = ?';
my $p_sql = 'SELECT * FROM products WHERE id = ?';
my $order = $dbh->selectrow_hashref($o_sql, {}, $o_id);
my $product = $dbh->selectrow_hashref($p_sql, {}, $p_id);
# snip
my $html = <<'END_HTML';
<html>
<head> ...
}
This is an absolute nightmare to work with, but it’s all too common (particularly the hateful SELECT *, but that’s a rant for another day). When you’re working with Web code, you don’t want to see database handles or HTML. If you’ve done that, you’ve failed. Now let’s clean it up a little bit. sub add_product_to_order {
my ( $self, $o_id, $p_id ) = @_;
my $order = Order->new($o_id) or $self->add_error(Order->error);
my $product = Product->new($p_id) or $self->add_error(Product->error);
$self->handle_errors; # doesn't return if errors
$order->add_to_basket($product);
$self->show_basket($order);
}
Now that code isn’t perfect (I can see several things I’d probably want to change in it), but it’s one heck of a lot better. You can see that it pretty much only contains application logic, not business rules, knowledge of the data store or anything other than application logic (think “control flow”). Further, because we’ve cleanly separated the model, view, and controller code, when we need to go in and clean this up, you can proceed easier knowing that you’re less likely to violate business rules or break the presentation layer. Now you might wonder where that order came from? Here’s the beauty of it: you don’t care. If at first you store all orders in a database, you can use that code. Later, if you decide that you want to use memcached or some Web service to store the orders, you can just change your Order code and keep your interface the same. Imagine trying to do that with all of the responsibilities jumbled up as in the first example! Another question which regularly arises is how to distinguish between business and application logic. Admittedly much of this is dependent on your needs, but a good rule of thumb is that universal logic, that is to say, logic that must always be triggered, should be pushed into the model. For example, if “anonymous” users should never see prizes but they can still get to the page which has the prize list (presumably because there are other things to see), then you don’t want your controller code to be determining this. Your code would be similar to what I showed above. my @prizes = $category->fetch_prizes($user); $view->display( prize_category => \@prizes ); However, if on some pages they can see the prizes, then your controller code should handle this: my @prizes = $view->for_everyone ? $category->fetch_prizes($user) : (); $view->display( prize_category => \@prizes ); You may have noticed that I didn’t really explain MVC itself. That’s because there are several flavors of it and I’m less interested in the particular implementation details as I am with properly keeping different parts of the system separated. Also, it’s OK to only have it only partially implemented. When you’re reworking a large system, you take small steps. Maybe you pull your HTML out into templates to start with. That’s OK. By slowly working towards a proper separation of concerns, you’ll build more maintainable systems that become a joy to work on. 原文链接: http://www.oreillynet.com/onlamp/blog/2007/06/what_is_mvc.html |