CSC/ECE 517 Fall 2012/ch2b 2w22 sk

From Expertiza_Wiki
Jump to navigation Jump to search

Requirements

The introduction to the GoF book gives three principles for writing reusable and flexible software.

  • Program to an interface, not an implementation.
  • Favor object composition over class inheritance.
  • Consider what should be variable in your design (originally, Encapsulate the behavior that varies).

Explain these principles, and illustrate them with original examples.

Program to an interface, not an implementation

This principle is really about dependency relationships which have to be carefully managed in a large app - Erich Gamma <ref>http://www.artima.com/lejava/articles/designprinciples.html</ref>

In this principle, the word interface is more conceptual and should not be seen from the viewpoint of a language like Java or C# which has a keyword by the same name. Interface relates to the OO principle of capabilities that the object is able to support. So by this definition an object can have many types or interfaces, and objects of different classes can have the same interface or type<ref>http://www.fatagnus.com/program-to-an-interface-not-an-implementation/</ref> <ref>http://blogs.msdn.com/b/sachin/archive/2008/06/12/program-to-an-interface-not-an-implementation.aspx</ref>. In this context, the main idea behind this principle is how to decouple the implementation from the interface so that the code can be extended polymorphically ie., to reduce the implementation dependencies. So this approach gives you flexibility, but it also separates the really valuable part, the design, from the implementation, which allows clients to be decoupled from the implementation.

Dynamic Binding, Polymorphism and Interfaces<ref>http://www.as3dp.com/2009/02/design-pattern-principles-for-actionscript-30-program-to-an-interface-not-an-implementation</ref>

The first principle of design patterns is also tied to polymorphism as noted above. Let us look at this part in more detail.

When we type an object, such as,

var myInstance:String;

we tend to think of String as a class type, but in fact it denotes a particular interface. “Any request that matches a signature in the object’s interface may be sent to the object” (GoF 13).

Bumping this up to user created classes, we type to the interface, which is found in the supertype relative to a subtype. That is, a subclasses’ type resides in the parent class. Because all knowledge of an object is known only through its interface, the request does not address the implementation. As a result, objects with different implementations can have the same interface.

Several different objects can have the same interface (primarily based on the superclass) but different implementations, and since the actual operation that is performed is dependent on both the request and receiving object’s name, the actual outcome occurs at run-time. Such an association between the a request and its operation at run-time is known as dynamic binding. The process of dynamic binding lets you substitute objects with identical interfaces at run-time. You probably know this process by another name, polymorphism.

Another way to look at this principle is that one should focus not only on developing one single version of an application, but to design it keeping in mind that one have to maintain and keep the API stable for a sustained period of time.

Just by following this rule, you get two very important benefits as stated in the GOF book:

  1. Clients remain unaware of the specific types of objects they use, as long as the objects adhere to the interface that the clients expect.
  2. Clients remain unaware of the classes that implement these objects. Clients only know about the base/abstract classes or interfaces (C# interface) defining the interface. And this greatly reduces the implementation dependencies.

An example why we have to program to an interface is shown below:

   
public ProcessedBeans processBeans(Beans beans){
    ProcessedBeans processedBeans = beans.getProcessedBeans();  
    return processedBeans;  
}

In the above code, we are programming for an implementation assuming that beans is the only item that needs processing. But, as we know, this is not the case. We have to process other food items as well before consumption. Due to the way we have programmed it now we will have to add many more functions for each type of food item that needs processing. This can be avoided, however, by programming to an interface.

public ProcessedFood processFood(FoodItem foodItem){  
    ProcessedFood processedFood = foodItem.getProcessedItem();  
    return processedFood;
}  

By doing so, we have eliminated the need to add new functions for each food type as already noted. The UML diagram for the interface would look something like this:-

Favor object composition over class inheritance

Definitions:

  • Object Composition is a way to combine simple objects or data types into more complex ones.
  • Composition over inheritance (or Composite Reuse Principle) in object-oriented programming is a technique by which classes may achieve polymorphic behavior and code reuse by containing other classes that implement the desired functionality instead of through inheritance.

Why do we need this?<ref>http://programmers.stackexchange.com/questions/65179/where-does-this-concept-of-favor-composition-over-inheritance-come-from</ref>
Both methods - object composition and inheritance provide a means for code reuse which is one of the fundamental principles of OO programming. With inheritance the subclasses are tightly coupled to the parent classes which reduces the flexibility. Each subclass knows the internal representation of the parent class which break encapsulation. This creates a dependency between the subclass and the superclass which is not a desirable trait. This is why inheritance is generally referred to as white box reuse. In most scenarios you do not want to expose the internal implementation of the super class and that is where composition comes into play.

Inheritance is good to represent is-a relationships but is not suitable for portraying has-a relationships. Many relationships between classes or components do not fall into the is-a category. (for example, a Car class is likely not a HashMap, but it may have a HashMap). In these scenarios it follows the composition is a better idea for modeling relationships between classes rather than inheritance.

Below is one simple example where composition is much suited than inheritance.

Class Location {
  String name;
  ...
}

Class Journey {
  Location source, destination;
  double getDistance() {...}
  ...
}

Class Flight extends Journey {...}
Class Bus extends Journey {...}

In the above example, instead of having a sub-class for Flight, class Journey can have a new field which is TransportMode which represents different modes of transport such as flight, bus, train etc.

Class Location {
  String name;
  ...
}

Class TransportMode {
  TransportType type;
  double getCost(double distance) {...}
  ...
}

Class Journey {
  Location source, destination;
  TransportMode mode;
  double getDistance() {...}
  double getCost() { mode.getCost( getDistance() ) }
  ...
}

However this is not to say that inheritance should not be used at all. Below is another example which shows that inheritance is the one which we should be using. Here the Human class represents a is-a relationship with Animal class which is quite natural and the proper way to look at the classes.

abstract class Animal {
    private String name;
    public String getName(){
        return name;
    }
    abstract String isIntelligent();
}

class Cat extends Animal{
    public String isIntelligent(){
        return "No";
    }
}

class Human extends Animal{
    public String isIntelligent(){
        return "Yes";
    }
}

In short, one should use inheritance for behavioral purposes ie., subclasses should override methods to specialize the behavior of the method and the object itself.

Benefits of object composition<ref>http://www.kuro5hin.org/story/2002/7/14/81923/3656</ref>

  • To favour composition over inheritance is a design principle that gives the design higher flexibility.
  • Initial design becomes simple by avoiding hierarchical relationship among classes via inheritance and by identifying object behaviors in separate interfaces.
  • Can easily accommodate future requirements without having to restructure large portions of code.

Drawbacks<ref>http://stackoverflow.com/questions/3979581/disadvantage-of-object-composition-over-class-inheritance</ref> <ref>http://www.kuro5hin.org/story/2002/7/14/81923/3656</ref>

  • All of the methods provided by the composed classes must be implemented in the derived class, even if they are only forwarding methods.
  • In inheritance the derived class only need to implement methods whose behavior needs to be modified..
  • Significantly less programming effort if only a handful of functions in the superclass needs to be overridden in the subclass.

Consider what should be variable in your design

The key to identifying what should be variable lies in anticipating new requirements and changes to the existing requirements. The system must be designed in such a way so that it can evolve accordingly. Designing a robust system is we must take into account unforeseen changes to avoid a major design overhaul in future. Those changes might involve class redefinition and re-implementation, client modification, and retesting.Redesign affects many parts of the software system, and unanticipated changes are invariably expensive.

Design patterns help you avoid this by ensuring that a system can change in specific ways. Each design pattern lets some aspect of system structure vary independently of other aspects, thereby making a system more robust to a particular kind of change.

Below are some common causes of redesign along with the design pattern(s) that address them:<ref>http://www.palserv.com/dp/DesignPatternsOverview.html</ref>

  • Creating an object by specifying a class explicitly: Specifying a class name when you create an object commits you to a particular implementation instead of a particular interface. This commitment can complicate future changes. To avoid it, create objects indirectly.
 Design patterns: Abstract Factory, Factory Method, Prototype.
  • Dependence on specific operations: When you specify a particular operation, you commit to one way of satisfying a request. By avoiding hard-coded requests, you make it easier to change the way a request gets satisfied both at compile-time and at run-time.
 Design patterns: Chain of Responsibility, Command.
  • Dependence on hardware and software platform: External operating system interfaces and application programming interfaces (APIs) are different on different hardware and software platforms. Software that depends on a particular platform will be harder to port to other platforms. It may even be difficult to keep it up to date on its native platform. It's important therefore to design your system to limit its platform dependencies.
 Design patterns: Abstract Factory, Bridge.
  • Dependence on object representations or implementation: Clients that know how an object is represented, stored, located, or implemented might need to be changed when the object changes. Hiding this information from clients keeps changes from cascading.
 Design patterns: Abstract Factory, Bridge, Memento, Proxy.
  • Algorithmic dependencies: Algorithms are often extended, optimized, and replaced during development and reuse. Objects that depend on an algorithm will have to change when the algorithm changes. Therefore algorithms that are likely to change should be isolated.
 Design patterns: Builder, Iterator, Strategy, Template Method, Visitor.
  • Tight coupling: Classes that are tightly coupled are hard to reuse in isolation, since they depend on each other. Tight coupling leads to monolithic systems, where you can't change or remove a class without understanding and changing many other classes. The system becomes a dense mass that's hard to learn, port, and maintain.Loose coupling increases the probability that a class can be reused by itself and that a system can be learned, ported, modified, and extended more easily. Design patterns use techniques such as abstract coupling and layering to promote loosely coupled systems.
 Design patterns: Abstract Factory, Bridge, Chain of Responsibility, Command, Facade, Mediator, Observer.
  • Extending functionality by subclassing: Customizing an object by subclassing often isn't easy. Every new class has a fixed implementation overhead (initialization, finalization, etc.). Defining a subclass also requires an in-depth understanding of the parent class. For example,
 Overriding one operation might require overriding another. An overridden operation might be required 
 to call an inherited operation. And subclassing can lead to an explosion of classes, because you might have to introduce
 many new subclasses for even a simple extension.

Object composition in general and delegation in particular provide flexible alternatives to inheritance for combining behavior. New functionality can be added to an application by composing existing objects in new ways rather than by defining new subclasses of existing classes. On the other hand, heavy use of object composition can make designs harder to understand. Many design patterns produce designs in which you can introduce customized functionality just by defining one subclass and composing its instances with existing ones.

 Design patterns: Bridge, Chain of Responsibility, Composite, Decorator, Observer, Strategy.
  • Inability to alter classes conveniently: Sometimes you have to modify a class that can't be modified conveniently. Perhaps you need the source code and don't have it (as may be the case with a commercial class library). Or maybe any change would require modifying lots of existing subclasses. Design patterns offer ways to modify classes in such circumstances.
 Design patterns: Adapter, Decorator, Visitor.

These examples reflect the flexibility that design patterns can help you build into your software. How crucial such flexibility is depends on the kind of software you're building.

Advantages:

  • Enables large scale reuse of software.
  • Helps in improving developer communication.
  • Captures expert knowledge and design trade-offs and make expertise widely available

Disadvantages:

  • Does not lead to direct code reuse and is complex in nature.
  • They are validated by experience and discussion.
  • They consume more memory because of generalized format.
  • They are optimized to store any kind of data. This can degrade performance.

See Also

1. http://pragmaticjava.blogspot.com/2008/08/program-to-interface-not-implementation.html
2. http://blog.fedecarg.com/2008/09/04/favour-object-composition-over-class-inheritance
3. http://www.amazon.com/gp/product/0596007124?ie=UTF8&tag=fatagnus-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=0596007124
4. http://www.amazon.com/gp/product/0201633612?ie=UTF8&tag=fatagnus-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=0201633612

References

<references/>