7.2. Separating Concerns

 <  Day Day Up  >  

The Model-View-Controller pattern that emerged from Smalltalk is similar to the batch-processing model of Input-Process-Output. Separating the concerns of how input is handled and validated from the way it is processed and then reported or displayed makes a program easier to maintain. This same separation of concerns is applicable to other facets of the system.

SEPARATE CONCERNS TO MAKE SMALLER CONCERNS

Split responsibilities among multiple methods and multiple classes to simplify each method and class .


Sam's system needs to create a rental contract. Contract production can be separated into two steps: preparing the contract and printing the contract. The printed rental contract is a report. The steps for almost every report include the following: [*]

[*] Some report-writing tools, such as Crystal Reports , combine steps 1 and 2.

  1. Calculate all values needed for the report, including counts and totals.

  2. Create an output representation of the report using the appropriate formatting language (plain text, HTML, PostScript, etc.).

  3. Display the output representation on the desired device (display screen, printer, browser window, etc.).

To clarify the distinction of the three steps, we create three separate methods. [ ] Let us call the functions:

[ ] Some operating systems tie the creation of the output representation and its display together. For example, when you prepare output for a particular printer, the operating system automatically passes that output to the spooler for printing on that printer. In that case, the create_rental_contract_report( ) method performs both functions for a printer and display_report( ) is needed only for nonprinter displays.

 calculate_rental_contract( )     create_rental_contract_report( )     display_report( ) 

Separating the functionality into multiple methods lets us reuse many of the methods, even if the details in other methods are changed. In addition, by making the printing separate from the preparation, we can reprint the report in exactly the same form.

As we shall see in the following sections, these methods interact with each other as follows :

  • calculate_rental_contract( ) creates RentalContractDTO

  • create_rental_contract_report( ) creates ReportPlainTextFormat from RentalContractDTO

  • display_report( ) displays ReportPlainTextFormat

7.2.1. Calculating the Rental Contract

You could design the create_rental_contract_report( ) method to pass all objects that contain data used in the report, or you design the method to require a Data Transfer Object (DTO) that includes only the data to display. If the create_rental_contract_report( ) method does not call any calculation methods in the objects passed to it, the decision rests on how much you want the method to depend on the class interfaces. Suppose you pass individual classes to the create_rental_contract_report( ) method. If those classes change, you will have to rewrite that method. If you use a DTO, you change only the code that creates the DTO.

We aim for extreme separation by having calculate_rental_contract( ) create a DTO. The DTO can hold either the data required for the report in primitive types and abstract data types (ADTs, as defined in Chapter 2), or the data converted into CommonString s. In the former case, create_rental_contract_report( ) applies a string conversion method (such as to_string( ) ) to each data item. In the latter case, the values are converted into strings when placed into the corresponding DTO members .

An advantage of a DTO is that testing and creating the system's reports can proceed in parallel with development of the system's business logic. Test DTOs can be filled with values and the resulting reports given to the client for their approval of the format. DTOs can also be used in testing for correctly calculated values regardless of the format in which they appear in a report.

Here are the data values that Sam decided he wanted to have output on the printed contract:

 Rental         start_time         due_time  // When the CDDisc is due     Customer         name     CDDisc         cd_release title         physical_id 

due_time is not currently part of the Rental class. Its calculation is clearly the responsibility of Rental , since that class contains all the information required. Therefore, we add an additional method to Rental :

 Timestamp calculate_due_time( )         {         due_time= start_time + base_rental_period;         return due_time;         } 

The DTO for the rental contract looks like this:

 class RentalContractDTO         {         Timestamp rental_start_time;         Timestamp rental_due_time;         Name customer_name;         PhoneNumber customer_day_phone_number;         CommonString cd_release_title;         PhysicalId cd_disc_physical_id;         } 

The calculate_rental_contract( ) method in Example 7-2 mostly moves data into RentalContractDTO . It calls any necessary routines to obtain the remaining values.

Example 7-2. calculate_rental_contract
 RentalContractDTO calculate_rental_contract(Rental rental,CDDisc cd_disc)     {     RentalContractDTO rental_contract_dto = new RentalContractDTO( );     rental_due_time = rental.calculate_due_time( );     rental_start_time = rental.start_time;     customer_name = rental.customer.name;     customer_day_phone_number = rental.customer.day_phone_number;     cd_release_title = cd_disc.cd_release.title;     cd_disc_physical_id = cd_disc.physical_id;     return rental_contract;     } 

It can be hard to decide which class should contain a method that creates a DTO, since the data will probably come from several classes. The method could be in an entirely separate class, or it could be in one of the classes that provides the data. Since the report is a rental contract, it seems reasonable that Rental should contain calculate_rental_contract( ) .

If we put the calculate_rental_contract( ) method into the Rental class, the first parameter is not needed, but Rental needs to know what the CDDisc is to create the contract. So we alter Rental to include an attribute for the CDDisc :

 class Rental         {         CDDisc cd_disc;         Rental(CDDisc cd_disc)             {             this.cd_disc = cd_disc;             }         }; 

If the method is part of the Rental class, all references to Rental disappear, as shown in Example 7-3. The cd_disc variable in Example 7-3 now refers to the attribute, rather than the parameter of Example 7-2.

Example 7-3. calculate_rental_contract as a method of Rental
 RentalContractDTO calculate_rental_contract( )     {     RentalContractDTO rental_contract_dto = new RentalContractDTO( );     rental_due_time = calculate_due_time( );     rental_start_time = start_time;     customer_name = customer.name;     customer_day_phone_number = customer.day_phone_number;     cd_release_title = cd_disc.cd_release.title;     cd_disc_physical_id = cd_disc.physical_id;     return rental_contract_dto;     } 

7.2.2. Creating the Rental Contract

Next, we turn our attention to the method creating the report, create_rental_contract_report( ) . The form of the report could be XML, HTML, RTF, plain ASCII text, or some other format. For our example, we create a plain-text report with the following method:

 ReportPlainTextFormat create_rental_contract_report(         RentalContractDTO rental_contract_dto) 

This method can be implemented in numerous ways. It can create the report using formatted print statements. It can read a file that contained the report template with tags designating where to insert each value in the DTO. It can convert RentalContractDTO to XML, and then use an XSL transformation. (See XML Bible , 2nd Edition, by Elliotte Rusty Harold [Wiley, 2004]).

A more elaborate report might require a template format that mimics an Active Server Page (ASP) or Java Server Page (JSP). Those technologies have additional features such as the ability to loop over a group of items and to perform conditional formatting. Conditional formatting includes the ability to output one of an alternate set of strings, depending on the DTO values. For example, output could read "1 Widget" or "2 Widgets", instead of "1 Widget(s)".

ReportPlainTextFormat can have just a CommonString that contains all the characters to be displayed. Alternatively, it can be more structured, such as:

 class ReportPlainTextFormat         CommonString title         CommonString [] lines 

With this form of the class, more information is transferred, so the display method can be more flexible in its presentation. For example, if the report is being printed, the display method can place the title at the top of each page.

7.2.3. Displaying a Report

Now that we have a report, we need to do something with it. The final method takes the report and displays it on the appropriate device:

 void display_report(         ReportPlainTextFormat contract_report_plain_text_format) 

Now the display_report( ) method's single job is to display the report on a particular device (e.g., a screen or a printer). That is an easy operation to test. You create an object of ReportPlainTextFormat and call the method. The report should display on the device. For example, if the device is the screen, a window could appear on the screen with the text in a read-only-style edit box.

To display a report on any one of several devices, we place this method in an interface. For example:

 interface DisplayReport         void display_report(ReportPlainTextFormat report_plain_text_format) 

Each device that displays reports implements the DisplayReport interface in a manner appropriate to the device:

 class Screen implements DisplayReport     class Printer implements DisplayReport 

For example, if the device is a printer, and the operating system has a printer spooler, display_report( ) can transfer the text to the printer spooler and let the spooler handle any errors generated by the printer (offline, out-of-paper, network down, etc.).

7.2.4. Changes and Effects

Separating concerns, as shown in the previous subsections, makes it easier to deal with potential changes that might rear their heads down the line. Experience has shown that changes to reports are a frequent customer request. Some typical types of changes and their effects on the contract report are as follows:



The report layout changes

The create_rental_contract_report( ) method needs to change



The items on the report require a different computation

The calculate_rental_contract( ) method needs to change



New data is added to the report

Both the create_rental_contract_report( ) and calculate_rental_contract( ) methods need to change



The system, including the report, is ported to a new platform or environment

Only implementations of display_report( ) need to be altered

As you can see, the impact of a single type of change is generally limited to one method.

 <  Day Day Up  >  


Prefactoring
Prefactoring: Extreme Abstraction, Extreme Separation, Extreme Readability
ISBN: 0596008740
EAN: 2147483647
Year: 2005
Pages: 175
Authors: Ken Pugh

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net