CSC/ECE 517 Summer 2008/wiki3 8 jb

From Expertiza_Wiki
Jump to navigation Jump to search

This wiki will explore some of Bertrand Meyer's contribution to OO design, including the principles of small interfaces, explicit interfaces, uniform-access, self-documentation, and single-choice. We intend to show good examples of each principle, discuss their support in languages other than Eiffel, and discuss whether it is difficult to follow these principles in certain OO languages.

Background

Bertrand Meyer is a professor of Computer Science at ETH Zurich, and is the creator of the Eiffel programming language. Meyer authored a book, titled Object-Oriented Software Construction in which he presented five principles of good Object-oriented design. This wiki will explore these five principles.

Small Interfaces

"If any two modules communicate at all, they should exchange as little information as possible"

Discussion

The goal of Meyer's small interfaces principle is to reduce coupling between classes. There are a two ways to interpret this principle, First, a small interface could mean that it has a small parameter list. Minimizing the number of parameters, regardless of the parameter's data type would have the tendency to reduce coupling. Second, a small interface could refer to the size of the individual parameters being minimized. In this case, you would also achieve a reduction in coupling by passing around smaller objects, since they would on average have less hooks to the rest of the code.

ftp://ftp.idc.ac.il/pub/courses/cs/oosc/ch1-3.ppt

This principle also has the tendency to improve cohesion, and could be seen as related to Skrien's principle of "A method should do one thing only and do it well". [Skrein, 87], [1]. Methods taking a high number of parameters, on average, are probably doing several tasks and requiring several inputs, and would not be cohesive. Another observation that could be made about methods with too many parameters, is that the parameters are somehow related and should be grouped together in a separate class. Support for the small interface principle is provided in all of the major O-O languages, notably Java and C++.

Example

The following two code snippets, found at http://www.artima.com/designtechniques/cohesion.html illustrate the benefits of small interfaces. The first snippet is the declaration of a method which converts ounces to milliliters. It only takes one parameter, the ounces to convert.

convertOzToMl(int ounces)

The counter example perhaps might appear a bit more generic, but not as easy to understand. The additional parameters take away from the methods cohesion.

int convert(int fromUnits, int toUnits, int fromAmount)

Explicit Interfaces

"If two modules communicate, this must be obvious from the text of either or both"

Discussion

The idea behind explicit interfaces is that if someone makes a change to a class, they would want a way to know about all other classes that could be affected [2]. The figure below illustrates the concept with an example of the problem that explicit interfaces attempt to solve. Consider two classes A and B, which communicate through a common data item X. A modifies x and B accesses x. How should the maintainer of A be made aware of the downstream implications of changing the way in which x is calculated, or changing the allowable range of values over x? The principle of explicit interfaces is closely related to the concept of Design by contract. Design by contract focuses on specifying preconditions, postconditions, and class invariants, and supporting these assertions explicitly within the language. Explicit interfaces using as design by contract is not supported in the major O-O languages.

ftp://ftp.idc.ac.il/pub/courses/cs/oosc/ch1-3.ppt

Example

The following Eiffel exampled, found at http://archive.eiffel.com/doc/manuals/technology/contract/ illustrates Eiffel's support for explicit interfaces. The keywords to pay attention to are require and ensure. These keywords implement Eiffel's precondition and postcondition constraints.

     put (x: ELEMENT; key: STRING) is
                     -- Insert x so that it will be retrievable through key.
             require
                     count <= capacity
                     not key.empty
             do
                     ... Some insertion algorithm ...
             ensure
                     has (x)
                     item (key) = x
                     count = old count + 1
             end

Uniform-access

"All services offered by a module should be available through a uniform notation,
which does not betray whether they are implemented through storage or through computation"

Discussion

There are two concepts covered under the uniform-access principle. The first is closely related to Skrien's guideline, "A well-formed class has a consistent interface", and the second goes a step further and specifically talks about the notion of cached off values within a class. The image below illustrates the concept. There are two different implementations of a bank account, A1 and A2. A1 maintains an explicit balance variable while A2 would derive the balance when queried. The uniform-access principle states that the getBalance() method of classes A1 and A2 should not look any appear to the caller. Support for uniform access is found in all major O-O languages.

ftp://ftp.idc.ac.il/pub/courses/cs/oosc/ch1-3.ppt

Example

The uniform access principle would dictate that the two preceding implementations of a bank account would have methods with identical signatures for retrieving the balance, such as:

int getBalance()

Another example of the general notion of uniform notation can be taken from the lecture notes from lecture 20. The question was posed of a class having two methods; one that that sets the ith person’s name and another that sets the ith person’s age. The uniform access principle follows that the two methods should have a uniform notation for describing the two similar methods.

setName(String name, int index);
setAge(int age, int index);

Self-documentation

"The designer of a module should strive to make all information about the module part of the module itself"

Discussion

The principle of self-documenting code is that the language should support constructs that enable the unification of the design document and the code. Imaging if the language that you were writing your application in supported design features of a design language such as UML. Even better, imagine if the compiler could turn your "designs" into code. This is the vision for self-documenting code. The concept of literate programming put forth by Donald Knuth is another effort towards self-documenting code. Skrien talks about the principle of self documenting code in section 4.5, with the guideline "Always strive for self documenting code that is so clear that no comments are necessary" [Skrien 92].

Meyer's Eiffel language supports notations embedded in the language that support this principle [3]. Support for self-documentation is limited in languages other than Eiffel. The closest thing that major O-O languages such as Java and C++ have are systems such as Javadoc and Doxygen. These systems implement parsers which extract comments encoded with formatting tags to generate rich HTML documentation straight from the code.

Example

The following example shows what doxygen comments look like in c++ and what the HTML output looks like. The relevant doxygen tags in the example are author and @param

/**
  * The time class represents a moment of time.
  *
  * @author John Doe
  */
 
  class Time {
 
    public:
 
       /**
         * Constructor that sets the time to a given value.
         * @param timemillis is a number of milliseconds passed since Jan 1. 1970
         */
       Time(int timemillis) {
       }
 
       /**
         * Get the current time.
         * @return A time object set to the current time.
         */
       static Time now() {
       }
     };

Example source code taken from [4]

http://en.wikipedia.org/wiki/Doxygen

Single-choice

"Whenever a software system must support a set of alternatives, one and only one module in the system should know their exhaustive list"

Discussion

At face value the single-choice principle seems extremely logical, and in some contexts of "alternatives", it makes perfect sense. Consider an application of the command patten, where an some module supported execution of commands deriving from a generic command interface. It would probably be useful to have a list of all of the classes implementing the command interface in a single place. This could perhaps be used to pass the alternatives back to a user interface.

There are some other aspects of the single-choice principle that are contentious. In the context of a list of methods being the "alternatives", O-O systems employing interfaces would require each class implementing an interface to provide an implementation of the method. In this context, the lists of alternatives, the methods, exist in multiple places. This is in contrast to how such a system might be implemented in a procedural language, where the list of alternatives might be kept in a single place, at the expense of having a massive if statement to drive the logic instead of polymorphism.

Example

The best example of the single choice principle in practice is an object factory [5]. A object factory is a class which implements behavior to construct objects. It is intended to be the single place in a system where object allocation occurs. An object factory would have to have knowledge of all of the different classes in the system, and support some means for the caller to describe the type of the object they wanted to allocate. Internally the object factory would implement a large switch/case statement that would allocate the class matching the caller's input.

The following example source code was found at http://www.gamedev.net/reference/programming/features/objectfactory/

class ShapeFactory
{
public:
  Shape *Create(int shape)
  {
    if (shape == SQUARE)
      return new Square;
    else if (shape == CIRCLE)
      return new Circle;
    else if (shape == TRIANGLE)
      return new Triangle;
  }
};

ShapeFactory shape_factory;

Shape *shape1 = shape_factory.Create(TRIANGLE);
Shape *shape2 = shape_factory.Create(SQUARE);

Links

Wikipedia page for Bertrand Meyer
Wikipedia page for Meyer's book: Object Oriented Software Construction
Wikipedia page for Eiffel
Lesson on OO drawing on Bertrand's principles
Design guidelines for quality
Interview with Meyer discussing Design by Contract