1. Trang chủ
  2. » Công Nghệ Thông Tin

PHP in Action phần 3 pptx

55 245 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 55
Dung lượng 839,6 KB

Nội dung

SUMMARY 85 class Document function getSummary() { preg_match('/^.*?\./',$this->body,$m); return array_shift($m); } } This is the kind of small adjustment to the data that might give objects value even in simple applications. Another example is outputting a date in different formats. But the real benefits of using domain objects are realized when they embody busi- ness rules or business logic. For example, an e-commerce system might need to calcu- late prices using discounts based on varying criteria, including the kind of product, the season, and the type of customer. Domain objects are also useful for expressing complex relationships. An example might be a tree structure such as a discussion forum with expandable/collapsible threads. A moderately simple web application might have relatively little use for advanced domain objects. But the rest of the application can still benefit from using objects. Both database access and user interaction can gain from using object-oriented tech- niques. These objects have even less of a resemblance to “real” objects. Some of them might represent fragments of what the user sees in the application, but often the objects are “engines” that process, organize, or move data around. Template engines (described in detail in chapter 13) are an interesting example. In the section on the Adapter design pattern in chapter 7, we will see the difference between the objects used by two popular PHP template engines: Smarty and PHPTAL. PHPTAL objects repre- sent something almost “real” (or at least familiar to anyone with experience of PHP web programming), a template containing HTML markup. A Smarty object, on the other hand, is an engine that can process any template. You feed the Smarty object a template and it generates the HTML output. Other types of objects that are commonly used in web programming are controllers and filters to process and interpret user input and objects that transform and move data into and out of a database. 4.4 SUMMARY Object orientation helps make complex programs more manageable and maintain- able by providing lots of options in the structure and organization of a program, by making program code easier to understand, by breaking the program into manage- able chunks, and by encapsulating operations and data. But skill and insight is required to make this happen. We need to understand how to do it and why. We need to know the difference between good and bad design even when there are no absolute rules that apply. In general, objects and classes do not represent real-world objects and categories. Some do, but the correspondence is always imperfect and ruled ultimately by the user’s requirements of the software rather than by a need to represent reality faithfully. 86 CHAPTER 4 UNDERSTANDING OBJECTS AND CLASSES In the next chapter, we will familiarize ourselves with the basic relationships between classes—primarily class inheritance and object composition—and consider how they can be used optimally in object design. 87 CHAPTER 5 Understanding class relationships 5.1 Inheritance 88 5.2 Object composition 94 5.3 Interfaces 96 5.4 Favoring composition over inheritance 99 5.5 Summary 101 Not long ago, I was watching a television talk show featuring actor Sven Nordin, who plays the Norwegian version of the solo theatrical performance Defending the Cave- man. Nordin convincingly demonstrated the art of banging your head on a hard sur- face, although he did admit that it was a painful procedure. A medical expert who was also present remarked dryly that “he shouldn’t be doing that.” Obviously. It’s easy to understand how that kind of abuse might be bad for your brain. On the other hand, it might be a vicious cycle: the more you rattle your brain, the less you understand how bad it is. Throwing books at yourself may be marginally better. Just watch out for ideas that are too obvious; they may knock you temporarily unconscious. An idea that is too obvious is the traditional view of inheritance in object-oriented programming. For example, an eagle is a bird. Thus the Eagle class must be a child class of Bird. Well, not always. Let’s study it a bit more closely. First, we’ll consider 88 CHAPTER 5 UNDERSTANDING CLASS RELATIONSHIPS traditional class inheritance. For contrast, we’ll take a look at the alternative, which is often called object composition. Then we’ll discuss interfaces and how they work in object-oriented design. Finally, we’ll see how all this comes together in the now-classic principle of favoring object composition over class inheritance. 5.1 INHERITANCE Inheritance is a lucrative concept if you marry rich or peddle a commercial object- oriented language. Most object-oriented languages, including PHP, support inherit- ance. It means that a class can get automatic access to all the features of another class. Inheritance is important to understand, but relatively hard to apply. When exactly is it a good idea to use it? When is it better to avoid it? We will be investigating this issue in this chapter and later. Traditionally, different languages refer to the inheritance relationship in different terms. Depending on the context, a class inherits from a “parent” class, a “superclass,” or a “base” class. PHP uses the keyword “parent,” so “parent” and “child” might be the most appropriate terms in PHP, but it’s a good idea to know the other terms. For instance, there is a standard refactoring called Extract Superclass that we will be looking at shortly. In this section, we start with the concept of inheritance and see the benefits and limitations of using it to guide our thinking about object design. Then, to illustrate the idea of inheritance and get a feel for how it relates to real code, we’ll do a refac- toring exercise, using inheritance to eliminated duplication. 5.1.1 Inheritance as a thinking tool Inheritance is an eminently logical concept. Since we structure real-world objects into categories and subcategories, why not do the same with software? All eagles are birds, so Eagle is a subclass of Bird. Eagles have characteristics and behaviors that are typical of birds in general (such as feathers or flying). They also have characteristics and behaviors that are not shared by all birds, such as a preference for foods such as rats or fish. In software, this is expressed by an inheritance relationship: objects in the Eagle class get all the behaviors, methods, and data that are built into the Bird class. In addition, the specific eagle behaviors can be implemented in the Eagle class itself. Although the inheritance relationship between classes is an attempt to model the real world, the use of the word “inheritance” doesn’t correspond to its meaning in real life. A real eagle inherits its characteristics from mommy eagle and daddy eagle, not from an abstract “Bird.” “Parent class” expresses the fact that Bird is the “conceptual parent” of Eagle. But inheritance between classes does create a hierarchical relationship that resembles a family tree. The theoretical idea behind inheritance is that it expresses an “is-a” relationship. An eagle is a bird. Similarly, a news article is a document. So, by this token, a NewsArticle class should have a parent called Document. INHERITANCE 89 The practical rationale for using inheritance is code reuse. The Document class can contain code that is common to both news articles and discussion forum messages, while a NewsArticle class and a DiscussionMessage class contain code that is specific to these two kinds of documents. Figure 5.1 is a pseudo-real class diagram of a Bird class hierarchy. It illustrates the theoretical idea of class inheritance. Some behaviors and properties are common to birds, some differ; the class diagram illustrates this relationship. It also gives a clue to some of the problems in applying the theory. What about flightless birds? Do they need to be a child class of bird, and do ostriches, penguins and kiwis need to be rep- resented by child classes of the flightless bird class? The simple answer, as far as software is concerned, is that we model only what’s required. The user requirements determine what needs to be represented. If we’re not concerned with flightless birds, it’s fine for the Bird class to have a fly() method. 5.1.2 Refactoring to inheritance It’s not necessarily easy to use inheritance in an appropriate way. The is a relationship may be a good clue, but try searching for the extends keyword in the code for some PEAR packages, and you may start to wonder. The classes generally inherit from the PEAR class. For example, you might see: class Mail extends PEAR So does this mean that a Mail object is a PEAR? What is a PEAR, anyway? It’s a “PHP Extension and Application Repository.” No, the Mail object probably is not an exten- sion and application repository. On the other hand, the Mail object may be considered a “ PEAR-compatible object” or some such. So you could consider this a trivial naming problem. When you see extends PEAR, you just have to read it as extends PearCompatibleObject. It’s confusing, though, and confusion is the greatest obstacle to writing clean, well- designed code. That’s why naming is not trivial. My own understanding of inheritance improved a lot after I started refactoring. Typically, the opportunity to use inheritance arises when two classes have a lot in com- mon and you can do the refactoring known as Extract Superclass. Figure 5.1 Eagles and parrots are both birds; they share some behaviors and differ in others. 90 CHAPTER 5 UNDERSTANDING CLASS RELATIONSHIPS Let’s try it. As an example, we will use two classes that have parallel responsibilities, but in different contexts. We have a NewsFinder class for finding news articles in a database and a UserFinder class for finding users. The NewsFinder class is shown in listing 5.1. It’s simplistic in having only one method, but nevertheless similar to a real- world example. require_once 'DB.php'; class NewsFinder { private $db; public function __construct() { $this->db = DB::Connect( 'mysql://user:password@localhost/webdatabase'); if (DB::isError($this->db)) { throw new Exception($this->db->getMessage()); } } public function findAll() { $result = $this->db->query( "SELECT headline,introduction,text,". "author,unix_timestamp(created) as created,". "news_id ". "FROM News"); if (DB::isError($result)) { throw new Exception( $result->getMessage()."\n".$query."\n"); } while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) { $news[] = $row; } return $news; } public function setConnection($connection) { $this->db = $connection; } } b We use the PEAR DB package for this example. We store the DB object representing the database connection in an instance variable in the NewsFinder object. This is a simple and straightforward object-oriented way of handling database connections, but only one possibility of several. This will be discussed more fully in chapter 19. For the sake of simplicity, there is no way in this example to configure the data source URL (the string starting with mysql). In practice, there usually will be. Listing 5.1 NewsFinder class for getting news articles from a database Use PEAR DB b c Simple error handling d Example method e Execute SQL f Return result as array INHERITANCE 91 c The error handling is similarly simple, using an unspecified type of exception. We’re not introducing anything that there’s no obvious use for. d The findAll() method is just an example of what this class might do. It might have any number of other methods, but one is sufficient to illustrate the refactoring. e The PEAR DB object has a query() method that executes an SQL query and returns a PEAR_Result object. f Again keeping it simple, we collect the results from the DB_Result object as an array of associative arrays representing the rows. The other Finder class is a UserFinder. The similarity to the NewsFinder class is fairly obvious. It might actually be less obvious if there were more methods, since these might be methods such as findByLastName() that would be relevant only for users. Listing 5.2 shows the UserFinder class. class UserFinder { private $db; public function __construct() { $this->db = DB::Connect( 'mysql://user:password@localhost/webdatabase'); if (DB::isError($this->db)) { throw new Exception($this->db->getMessage()); } } public function findAll() { $result = $this->db->query( "SELECT user_id, email, password, name ". "FROM Users"); if (DB::isError($result)) { throw new Exception( $result->getMessage()."\n".$query."\n"); } while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) { $users[] = $row; } return $users; } public function setConnection($connection) { $this->db = $connection; } } Listing 5.2 UserFinder class, similar to the NewsFinder class 92 CHAPTER 5 UNDERSTANDING CLASS RELATIONSHIPS The only parts of this class that differ from the NewsFinder class are the ones shown in bold. In the real world, duplication is frequently less clear-cut than in this case. In any case, it pays to look closely at what’s similar and what’s different. To make it easy, I’ve marked the differences in bold text. The constructor is the same in these two classes. The findAll() method has two differences: the SQL statement and the naming of the array that’s returned. Figure 5.2 is a simple UML class diagram of the two classes. Although the diagram alone doesn’t prove that there is duplicated code (the two fin- dAll() methods might be completely different), it does sum up the situation. If we want to eliminate the duplication, we can extract a parent class that will be common to these two. Extracting the DatabaseClient class But what would be a good name for this parent class? A good name needs to say something about what these two classes have in common. We could call it Finder. Alternatively, since the common code we have extracted does database access, a good name might be DatabaseClient. Since naming is important, let’s test this by appealing to the principle that inheritance expresses an is-a relationship. Is the NewsFinder a database client? Yes, clearly. And so is the UserFinder. The constructor is easy to move into the Data- baseClient class. But what about the duplicated code in the findAll() method? We'll need to first extract a method to execute a query and return the result. Now let’s look at the refactored result. Figure 5.3 shows the result in UML. The query() method contains the code that was common to the find- All() methods in the two original classes. Now let’s see how this works in actual code. Listing 5.3 is the DatabaseClient class. Figure 5.2 Two very similar Finder classes Figure 5.3 Extracting the com- mon code from the findAll() methods into a query() meth- od in a parent class INHERITANCE 93 class DatabaseClient { protected $db; public function __construct() { $this->db = DB::Connect( 'mysql://user:password@localhost/webdatabase'); if (DB::isError($this->db)) { throw new Exception($this->db->getMessage()); } } public function query($sql) { $result = $this->db->query($sql); if (DB::isError($result)) { throw new Exception( $result->getMessage()."\n".$query."\n"); } return $result; } } Although we’re seeing the final result here, in practice it’s always a good idea to do this kind of refactoring one step at a time, running unit tests after each change. The sequence of steps in this case is 1 Create the DatabaseClient class. 2 Change declarations of the two finder classes, adding extends DatabaseClient to each of them. 3 Move the constructor from one of the finder classes to DatabaseClient. 4 Delete the constructor in the other finder class. 5 Extract a query() method in both of the finder classes. 6 Move the query() method from one of the classes into the DatabaseClient class. 7 Delete the query() method in the other finder class. The simplified UserFinder class The UserFinder class is now simpler and easier to read and understand (see listing 5.4). Database connection and error handling is conceptually different from manipulating data using SQL, so it’s not surprising that sorting them into different classes helps. Listing 5.3 DatabaseClient: extracted parent class to be used by NewsFinder and UserFinder 94 CHAPTER 5 UNDERSTANDING CLASS RELATIONSHIPS class UserFinder extends DatabaseClient { public function findAll() { $result = $this->query( "SELECT user_id, email, password, name ". "FROM Users"); while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) { $users[] = $row; } return $users; } } The NewsFinder class will be similar, and there is still a bit of duplication due to the similar way the array is built from the DB_Result object. By renaming the array that’s called $users in the UserFinder, we could extract four more common lines of code. The reason we haven’t done so is because in a realistic case, we would want to wait and see what happens first, since in practice it would be more complicated: some methods will return single rows and some multiple rows, so it’s better to have infor- mation on that before proceeding. We have studied some of the ins and outs of inheritance. The alternative is object composition. Before moving on to interfaces and the idea of favoring composition over inheritance, we will take a look at how object composition works. 5.2 OBJECT COMPOSITION In UML, there are a number of distinctions that express various ways that objects can relate by calling and referring to each other without inheritance: dependency, associa- tion, aggregation, composition. I’m lumping all of these under the heading of “com- position,” to clarify the contrast between all of these relationships on the one hand, and inheritance on the other, which corresponds approximately to the usage in the “Gang of Four” book [Design Patterns] as well. Conceptually, the principle is simple: one object “has” or “uses” another object or class. Technically, the greatest difference is between different ways of getting and maintaining the other object. One possibility is to hold the other object in an instance variable. The UML categories of association, aggregation, and composition refer to this type of strategy. Or the object can be used locally in a single method; the UML category for that is called dependency. Table 5.1 lists some of the possibilities. It focuses more on differences that are expressed in code and less on theoretical, semantic distinctions. It’s a good idea to know these possibilities and to be able to choose and compare them when programming. Besides Extract Method and Extract Superclass, another common refactoring is called Extract Class. You take parts of one class, typically a few methods and the data those Listing 5.4 UserFinder: the class uncluttered by database basics [...]... courses of action, as in listing 6.2 Listing 6.2 Using separate classes instead of the switch statement abstract class Inserter { abstract public function insert($data); } class TopicInserter extends Inserter { public function insert($data) { $sql = "INSERT INTO Topics (name) VALUES('" $data['name']."')"; // Insert into database } } class NewsInserter extends Inserter { public function insert($data)... depend on IDate instead of Date Since PHP 5 has an interface construct, does this make sense in PHP 5 as well? It might not seem that way In PHP 5, assuming we are not using type hints, an interface has no practical consequences So pretending it’s there is the same in practice as actually having it there We can always make a different class that conforms to the interface we haven’t formally defined, and... needs the PHP end tag (?>) eval() expects straight PHP code with no PHP tags around it But since $string is the contents of the included PHP template file, it will contain HTML markup or PHP sections surrounded by PHP tags f 114 To separate the mechanics of output buffering from the template processing proper, we have a separate method to process the template d 6 .3. 3 We start off by instantiating both... template engine API But it illustrates the basic mechanics To make sure we understand what we’re doing, here is the plain PHP way to do what a template engine does using include We set one or more variables, include a PHP file, and the variables can be displayed by using echo in the included file $hello ='Hello, world!'; include 'Test .php' ; To replace this, we can create a template engine that uses PHP as... sensible thing when it’s as simple as using a variable But with more complex behavior than inserting a name, it may take some work to figure out how to do it 6.2.2 Replacing cases with classes So how does it work in the real world? If we have a PHP class that specializes in inserting news articles into a database and we want to make it insert topics into a topic list instead, we will have to do something... interesting experiment 6 .3. 2 Figure 6 .3 Class diagram of the simplest-possible template engine An experiment: separating the responsibilities As mentioned, the template engine can be seen as having three separate responsibilities: • Storing variables • Reading a template file • Processing the template; combining the variables and the contents of the file The template engine in listing 6 .3 might be too simple... the interface gives no indication as to how it does the job, since an interface cannot contain any code that’s actually executed at runtime In this section, we’ll look at how interfaces can be used to think about object-oriented design Since interfaces, unlike classes, allow multiple inheritance, we’ll also examine that idea and its ramifications 5 .3. 1 The interface as a thinking tool If a parent class... insert($data) { $sql = "INSERT INTO News (headline,body) VALUES('" $data['headline']."','".$data['body']."')"; // Insert into database } } This will allow us to write something like this: $inserter = new NewsInserter; $inserter->insert(array('headline' => 'Man bites dog')); Figure 6.2 is a UML class diagram showing this simple design THE OPEN-CLOSED PRINCIPLE (OCP) 107 Figure 6.2 Inserter class hierarchy;... “closed” because it’s open It’s like the tree that bends in the storm instead of breaking In this section, we will first gain a basic understanding of the OCP by studying a trivial example Then we’ll look at a slightly more realistic case Finally, we’ll find out how relevant the OCP is in PHP compared to other programming languages 6.2.1 OCP for beginners In its simplest form, the OCP is trivial For example,... That implies that any class implementing the template interface is able to do all these things, but it implies nothing about how it does them So again, interfaces in the formal sense may seem rather pointless, because they do so little This is particularly true in dynamically typed languages such as PHP By INTERFACES 97 making a template interface, all we do is constrain ourselves We must implement those . responsibilities, but in different contexts. We have a NewsFinder class for finding news articles in a database and a UserFinder class for finding users. The NewsFinder class is shown in listing 5.1. It’s. idea of inheritance and get a feel for how it relates to real code, we’ll do a refac- toring exercise, using inheritance to eliminated duplication. 5.1.1 Inheritance as a thinking tool Inheritance. sees in the application, but often the objects are “engines” that process, organize, or move data around. Template engines (described in detail in chapter 13) are an interesting example. In the

Ngày đăng: 12/08/2014, 21:21