CSC/ECE 517 Fall 2012/ch2a 2w14 bb

From Expertiza_Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Introduction

The purpose of the wiki is to introduce and show some example about pattern fragility. The contents include definition of pattern fragility and what does the concept cover. We also give some examples of mistakes in code that spoil the benefits of design patterns.

Design Patterns

A design pattern is a general reusable solution to a commonly occurring problem within a given context in software design. A design pattern is not a finished design that can be transformed directly into source or machine code. It is a description or template for how to solve a problem that can be used in many different situations. Patterns are formalized best practices that the programmer must implement themselves in the application. Object-oriented design patterns typically show relationships and interactions between classes or objects, without specifying the final application classes or objects that are involved. Many patterns imply object-orientation or more generally mutable state, and so may not be as applicable in functional programming languages, in which data is immutable or treated as such.

Why Design Patterns

A design pattern is a pattern—a way to pursue an intent—that uses classes and their methods in an object-oriented language. Developers often start thinking about design after learning a programming language and writing code for a while. You might notice that someone else’s code seems simpler and works better than yours does, and you might wonder how that developer achieves such simplicity. Design patterns are a level up from code and typically show how to achieve a goal using a few classes. A pattern represents an idea, not a particular implementation.

Categorization of Patterns

Intent Patterns
Interfaces ADAPTER, FACADE, COMPOSITE, BRIDGE
Responsibility SINGLETON, OBSERVER, MEDIATOR, PROXY, CHAIN OF RESPONSIBILITY, FLYWEIGHT
Construction BUILDER, FACTORY METHOD, ABSTRACT FACTORY, PROTOTYPE, MEMENTO
Operations TEMPLATE METHOD, STATE, STRATEGY, COMMAND, INTERPRETER
Extensions DECORATOR, ITERATOR, VISITOR

Benefits of Design Patterns

Design patterns provide a way of encapsulating the experience of software developers in a form that can be communicated to other developers. They provide a higher level of abstraction than single classes and objects thus providing bigger building blocks in the construction of software designs.

The main benefits of design patterns are:

  • They encapsulate and codify design experience.
  • Provide a common vocabulary for software designers to use when communicating with their peers.
  • Enhance maintainability of software systems whose designs are documented with patterns.
  • Provide robustness to the design by copying or imitating proven design techniques.
  • Reuse at the design level.

At first glance some of these benefits may not seem very powerful, but upon further inspection maybe we can gain new insight into the true nature of their value. Let us just consider the fact that we have given a design pattern a common name. We can now communicate an entire design principle or concept with other software developers by just using the simple name of the pattern. In one fell swoop we have drastically reduced the effort and time needed for a developer to discuss a concept with another developer. Taken one step further we can discuss the patterns and their interactions in the system and illustrate the system architecture in few sentences. Grouping design concepts with common names facilitates communication among developers and raises the conversation to a higher level of abstraction.

Pattern Fragility

Symptoms of Rotting Design

There are four primary symptoms that tell us that our designs are rotting. They are not orthogonal, but are related to each other in ways that will become obvious.

The four symptoms are:

  • Rigidity -- the tendency for software to be difficult to change, even in simple ways.
  • Fragility -- the tendency of the software to break in many places every time it is changed.
  • Immobility -- the inability to reuse software from other projects or from parts of the same project.
  • Viscosity -- viscosity comes in two forms: viscosity of the design, and viscosity of the environment.

In following passage of this wiki page, we will discuss over 'Fragility' with some examples.

Definition of Pattern Fragility

Fragility is the tendency of the software to break in many places every time it is changed. Often the breakage occurs in areas that have no conceptual relationship with the area that was changed. Such errors fill the hearts of managers with foreboding. Every time they authorize a fix, they fear that the software will break in some unexpected way. As the fragility becomes worse, the probability of breakage increases with time, asymptotically approaching 1. Such software is impossible to maintain. Every fix makes it worse, introducing more problems than are solved. Such software causes managers and customers to suspect that the developers have lost control of their software. Distrust reigns, and credibility is lost.

Examples

Singleton -- the most overused pattern

Sometimes it's important to have only one instance for a class. For example, in a system there should be only one window manager (or only a file system). Usually singletons are used for centralized management of internal or external resources and they provide a global point of access to themselves. The primary purpose of the singleton is to guarantee that at anytime there is only one instance for a given class and provide a global reference to it.

The singleton pattern is one of the simplest design patterns: it involves only one class which is responsible to instantiate itself, to make sure it creates not more than one instance; in the same time it provides a global point of access to that instance. In this case the same instance can be used from everywhere, being impossible to invoke directly the constructor each time.

Well, the question is, is it safe to use a singleton class when it might be very tempting to do so? Obviously, the answer is negative.

Problem 1 of abusing Singleton

There is a general misconception about how Singletons should be used. Some people see the Singleton as a justification for global state, along the lines of "If there's a pattern for it, it must be good". Possibly, because it is true, that for having global state, it is better using Singletons, than just plainly global variables or class objects. Well no, it isn’t. Global state is considered harmful. For a number of reasons, that even Singleton-misuse won't make go away, simply because: Singletons are NOT intended to provide global state!

The Singleton is a creational pattern. It is used to enforce, that a class be instantiated only once. Create a singleton, and you have just made it possible for widely separated bits of your program to use that singleton as a secret channel to communicate with each other and, in the process, tightly couple themselves to each other. The horrible consequences of this coupling are why software engineering got out of the global variable business in the first place.

There is only one solution to this problem: Don’t do that. Properly applied, singletons are not global variables. Rather, they are meant to model things that occur exactly once. Yes, because it occurs only once, you can use a singleton as a unique communications conduit between bits of your program. But don’t do that. Singletons are like every other pattern and programming technique—which means you can really screw things up if you abuse them. I can only repeat: Don’t do that.

Problem 2 of abusing Singleton

Let’s first see the classical implementation of Singleton:

 public class Singleton {
     private static Singleton _instance = null;
     private Singleton() {}
     public static Singleton getInstance() {
       if (_instance == null) {
           _instance = new Singleton();
        }
       return _instance;
     }
  }

The implementation is straight forward. The constructor of Singleton is private, so it is not possible to instantiate it from outside. The only way to get an instance is to call static getInstance() method. getInstance() first checks whether an instance is already created. If not, then it creates an instance, refers it via private static member _instance and then returns that. And if already created then it returns the previously created _instance. Thus only the first call to getInstance() instantiates a Singleton object and any further call returns the same object. Also note that the object is not instantiated until getInstance() is called, i.e. we only create that when actually required. This is called lazy initialization and becomes helpful if the object is resource hungry.

This looks very simple and straight forward implementation. But we have a slight problem with this. This classical implementation is not thread safe. Lets see what may happen in the presence of two threads.

  • Thread-1 enters getInstance() for the first time and sees that _instance is null and thus the condition is true.
  • Before instantiating the object a thread switch occur.
  • Thread-2 enters getInstance() and it will see _instance null too, as the instantiation by Thread-1 is not completed yet.
  • Thread-2 instantiate new object and then return.
  • Thread-1 knows nothing about Thread-2. So when it gets its turn again, it instantiates another object and returns that. At this point we have two instances of Singleton which violates the fundamental purpose of the pattern.

How to solve this problem? Well, here I find a useful link for you to reference.

Factory

Create an interface for building an object, but let subclasses decide which class to instantiate. It allows a class to defer instantiation of subclasses. Factory pattern is one of the patterns that have being heavily abused. With as long as if --- else, go to the factory model, as long as the object involved in the creation of selective, go to the factory model. Factory pattern seems to have become the panacea to solve all. But let us look at some of the above contrast with the factory pattern, you simple code it? Wrong! The contrary, no increase in the scalability of the code at the same time, it increased the number of types of calls, an increase of the number of categories. This is not what we want to see!


One example is there is only one object to be instantiate. There was no concept of abstraction or extended classes,just plain old ClassX & ClassXFactory.

 public class A {
       public String str;
       public String getStr() {
              return str;
       }
}// End of class

public class ChildA extends A {
       public ChildA(String str) {
       System.out.println("Hello "+str);
       }
}// End of class

public class AFactory {
       public static void main(String args[]) {
              AFactory factory = new AFactory();
              factory.getA(args[0]);
       }

       public A getA(String str) {
              if(str!=NULL)
                     return new A(str);
              else
                     return null;
       }
}

When to use a Factory Pattern? The Factory patterns can be used in following cases:

1. When a class does not know which class of objects it must create.

2. A class specifies its sub-classes to specify which objects to create.

3. In programmer’s language (very raw form), you can use factory pattern where you have to create an object of any one of sub-classes depending on the data provided.

Visitor

Visitor pattern represent an operation to be performed on the elements of an object structure. In GoF's word, "It let you define a new operation changing the classes of the elements on which it operates." However, It's also the one that GoF most worried about. It's not a "necessary evil" - but is often over used and the need for it often reveals a more fundamental flaw in your design.

Following is a classic example being that you have the intersection between two shapes, but there's an even simpler case that's often overlooked: comparing the equality of two heterogeneous objects.

interface IShape
{
    double intersectWith(Triangle t);
    double intersectWith(Rectangle r);
    double intersectWith(Circle c);
}

The problem with this is that you have coupled together all of your implementations of "IShape". You've implied that whenever you wish to add a new shape to the hierarchy you will need to change all the other "Shape" implementations too.

Sometimes, this is the correct minimal design - but think it through. Does your design really mandate that you need to dispatch on two types? Are you willing to write each of the combinatorial explosion of multi-methods?

Often, by introducing another concept you can reduce the number of combinations that you're actually going to have to write:

interface IShape
{
    Area getArea();
}

class Area
{
    public double intersectWith(Area otherArea);
    ...
}

Command

The Command design pattern encapsulates commands (method calls) in objects allowing us to issue requests without knowing the requested operation or the requesting object. Command design pattern provides the options to queue commands, undo/redo actions and other manipulations.

There are two extremes that a programmer must avoid when using this pattern:

1. The command is just a link between the receiver and the actions that carry out the request 2. The command implements everything itself, without sending anything to the receiver.

We must always keep in mind the fact that the receiver is the one who knows how to perform the operations needed, the purpose of the command being to help the client to delegate its request quickly and to make sure the command ends up where it should.

One example is:

 class FileDeleteCommand
   def initialize(path)
     @path = path
   end
   def execute
     File.delete(@path)
   end
 end
 fdc = FileDeleteCommand.new('foo.dat')
 fdc.execute

there is nothing simpler than just getting on with it: File.delete('foo.dat')


A mixed example using Singleton Factory and Command

The final goal of the program

System.out.println("hello world");

Using Singleton, Factory and Command

First we define two interfaces Subject and Observer to add Observer.

public interface Subject {
    public void attach(Observer observer);
    public void detach(Observer observer);
    public void notifyObservers();
}

public interface Observer {
    public void update(Subject subject);
}

Then we define two classes HelloWorldSubject and HelloWorldObserver that implements them.

public class HelloWorldSubject implements Subject {
    
    private ArrayList<Observer> observers;
    private String str;
    
    public HelloWorldSubject() {
        super();

        observers = new ArrayList<Observer>();
    }

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void detach(Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers() {
        Iterator<Observer> iter = observers.iterator();
        
        while (iter.hasNext()) {
            Observer observer = iter.next();
            observer.update(this);
        }
    }
    
    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
        notifyObservers();
    }
}

public class HelloWorldObserver implements Observer {

    public void update(Subject subject) {
        HelloWorldSubject sub = (HelloWorldSubject)subject;
        System.out.println(sub.getStr());
    }

}

Then we add a Command.

public interface Command {
    void execute();
}

public class HelloWorldCommand implements Command {

    private HelloWorldSubject subject;
    
    public HelloWorldCommand(Subject subject) {
        super();
    
        this.subject = (HelloWorldSubject)subject;
    }
    
    public void execute() {
        subject.setStr("hello world");
    }

}

Then We add a Factory

public interface AbstractFactory {
    public Subject createSubject();
    public Observer createObserver();
    public Command createCommand(Subject subject);
}

public class HelloWorldFactory implements AbstractFactory {

    public Subject createSubject() {
        return new HelloWorldSubject();
    }

    public Observer createObserver() {
        return new HelloWorldObserver(); 
    }

    public Command createCommand(Subject subject) {
        return new HelloWorldCommand(subject);
    }
}


And a Singleton

public class FactoryMakerSingleton {
    
    private static FactoryMakerSingleton instance = null;
    private AbstractFactory factory;

    private FactoryMakerSingleton() {
        factory = new HelloWorldFactory();
    }
    
    public static synchronized FactoryMakerSingleton getInstance() {
        if (instance == null) {
            instance = new FactoryMakerSingleton();
        }
        
        return instance;
    }

    public AbstractFactory getFactory() {
        return factory;
    }
}

And finally the main class

public class AbuseDesignPatterns {

    public static void main(String[] args) {
        AbstractFactory factory = FactoryMakerSingleton.getInstance().getFactory();
        
        Subject subject = factory.createSubject();
        subject.attach(factory.createObserver());
        
        Command command = factory.createCommand(subject);
        
        command.execute();
    }

}

And the output is: Hello World

Conclusion

With design patterns becoming more and more popular, a tendency also becomes more and more obvious, which is inexperienced designers blind pursue instead of finding the best design patterns for the problems they encountered. They focused on how to use those famous patterns as more as possible. Various famous design patterns, which are unnecessary and even harmful, are spoiled in their program. In order to implement design patterns properly, programmers need to be aware of the following principles:

  • Not consider what to don until fully understand the problem and specific situation.
  • Not implement the design patterns until fully master the theory.
  • Combining theory of design patterns and practice.

Hope all of you will have perfect experiences with design patterns! :)

Reference