CSC/ECE 517 Fall 2009/wiki2 13 StaticDynamic: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
Line 95: Line 95:
=== Where dynamically typed language is more concise ===
=== Where dynamically typed language is more concise ===
====Lazy Initialization Pattern====
====Lazy Initialization Pattern====
Here is an example of Lazy Initialization Pattern for a Fruit class in C++:
Here is an example of Lazy Initialization Pattern for a Pizza class in C++:


<pre>
<pre>
Line 104: Line 104:
using namespace std;
using namespace std;
   
   
class Fruit {
class Pizza {
     private:
     private:
         static map<string,Fruit*> types;
         static map<string,Pizza*> types;
         string type;
         string type;


         // note: constructor private forcing one to use static getFruit()
         // note: constructor private forcing one to use static getPizza()
         Fruit(const string& t) : type( t ) {}
         Pizza(const string& t) : type( t ) {}


     public:
     public:
         static Fruit* getFruit(const string& type);
         static Pizza* getPizza(const string& type);
         static void printCurrentTypes();
         static void printCurrentTypes();
};
};


//declaration needed for using any static member variable
//static member variable
map<string,Fruit*> Fruit::types;         
map<string,Pizza*> Pizza::types;         
   
   
/*
/*
  * Lazy Factory method, gets the Fruit instance associated with a
  * Lazy Factory method, gets the instance associated with a
  * certain type. Instantiates new ones as needed.
  * certain type. Instantiates new ones as needed.
  * precondition: type. Any string that describes a fruit type, e.g. "apple"
  * precondition: type. Any string that describes a type
  * postcondition: The Fruit instance associated with that type.
  * postcondition: The instance associated with that type.
  */
  */
Fruit* Fruit::getFruit(const string& type) {
Pizza* Pizza::getPizza(const string& type) {
     Fruit *& f = types[type];  //try to find a pre-existing instance
     Pizza *& f = types[type];  //find a pre-existing instance
   
   
     if (!f) {
     if (!f) {
         // couldn't find one, so make a new instance
         // couldn't find one, then make a new one
         f = new Fruit(type); // lazy initialization part
         f = new Pizza(type); // lazy initialization
         types.insert(pair<string,Fruit*>(type, f)); // insert new instance into map
         types.insert(pair<string,Pizza*>(type, f)); // insert new instance into map
     }
     }
     return f;
     return f;
}
}


/*
* For example purposes to see pattern in action
*/
void Fruit::printCurrentTypes() {
    if (types.size() > 0) {
        cout << "Number of instances made = " << types.size() << endl;
        for (map<string,Fruit*>::iterator iter = types.begin(); iter != types.end(); ++iter) {
            cout << (*iter).first << endl;
        }
        cout << endl;
    }
}
int main(void) {
    Fruit::getFruit("Banana");
    Fruit::printCurrentTypes();
    Fruit::getFruit("Apple");
    Fruit::printCurrentTypes();
    // returns pre-existing instance from first
    // time Fruit with "Banana" was created
    Fruit::getFruit("Banana");
    Fruit::printCurrentTypes();
    return 0;
}
/*
OUTPUT:
Number of instances made = 1
Banana
Number of instances made = 2
Apple
Banana
Number of instances made = 2
Apple
Banana
*/
</pre>
</pre>


Line 185: Line 142:


<pre>
<pre>
class Fruit:
class Pizza:
     def __init__(self, type):
     def __init__(self, type):
         self.type = type
         self.type = type
      
      
class Fruits:
class Pizzas:
     def __init__(self):
     def __init__(self):
         self.types = {}
         self.types = {}
      
      
     def get_fruit(self, type):
     def get_pizza(self, type):
         if type not in self.types:
         if type not in self.types:
             self.types[type] = Fruit(type)
             self.types[type] = Pizza(type)
          
          
         return self.types[type]
         return self.types[type]
if __name__ == '__main__':
    fruits = Fruits()
    print fruits.get_fruit('Apple')
    print fruits.get_fruit('Lime')





Revision as of 20:18, 12 October 2009

Design Patterns from a Static/Dynamic Point of View

Ruby provides more concise realizations of certain design patterns than Java does, especially when lots of declaration are involved for Java, Ruby needs none. There could be design patterns that dynamically typed language is able to realize better than a statically typed language. In some instances a different pattern may better suit dynamically typed language than a static typed one.

Introduction

Design Pattern

A design pattern is a description or template for solving a particular problem under different circumstances. It's a general reusable solution to a commonly occurring problem in software design. Object-oriented design pattern is not a finished product but rather an idea can be realized into code. It specifies the relationships and interactions between classes or objects, and leave the job of specifying the final application classes or objects to the implementor.

[1]

Name Description
Creational Patterns
Abstract Factory Creates an instance of several families of classes
Builder Separates object construction from its representation
Factory Method Creates an instance of several derived classes
Prototype A fully initialized instance to be copied or cloned
Singleton A class of which only a single instance can exist
Structural Patterns
Adapter Match interfaces of different classes
Bridge Separates an object’s interface from its implementation
Composite A tree structure of simple and composite objects
Decorator Add responsibilities to objects dynamically
Facade A single class that represents an entire subsystem
Flyweight A fine-grained instance used for efficient sharing
Proxy An object representing another object
Behavioral Patterns
Chain of Resp. A way of passing a request between a chain of objects
Command Encapsulate a command request as an object
Interpreter A way to include language elements in a program
Iterator Sequentially access the elements of a collection
Mediator Defines simplified communication between classes
Memento Capture and restore an object's internal state
Observer A way of notifying change to a number of classes
State Alter an object's behavior when its state changes
Strategy Encapsulates an algorithm inside a class
Template Method Defer the exact steps of an algorithm to a subclass
Visitor Defines a new operation to a class without change

Static/Dynamic Type Languages

Types associates either with values or with objects such as variables. Any value simply consists of a sequence of bits in a computer, which are indistinguishable without knowing its boundary and structure. It's like taking out all the white spaces out of an article. Type informs users (program or programmer) of the information (bits) how those bit collections should be treated.

Major functions provided by type systems include:

  • Safety - Use of types may allow a compiler to detect meaningless or probably invalid code. For example, we can identify an expression 100/"Hello, World" as invalid because the rules of arithmetic do not specify how to divide an integer by a string. Take caution though strong typing offers more safety, but generally does not guarantee complete safety.
  • Optimization - Static type-checking may provide useful compile-time information. For example, if a type requires that a value must align in memory at a multiple of 4 bytes, the compiler may be able to use more efficient machine instructions.
  • Document - In more expressive type systems, type can explicitly illustrate the intent of the programmer. For instance, timestamps may be represented as integers—but if a programmer declares a function as returning a timestamp type rather than merely an integer type, this documents part of the purpose of the function.
  • Abstraction (or modularity) - Types allow programmers to think about programs at a higher level than the bit or byte, not bothering with low-level implementation. It gives programmers a concept, a scope, not byte or bit, of what he deals with. Type allows programmers to express the interface between two subsystems. This helps localize the definitions required for interoperability, and prevents inconsistencies among those subsystems when they communicate.

Static Type Languages

A programming language is said to use static typing when type checking is performed during compile-time as opposed to run-time.

C++, C#, Java

Dynamic Type Languages

A programming language is said to be dynamically typed, or just 'dynamic', when the majority of its type checking is performed at run-time as opposed to at compile-time.

Ruby, Python

Example

[2] Many software designers view patterns as a form of language-independent design. Pattern-Oriented Software Architectures: A System of Patterns, edited by Frank Buschmann (John Wiley & Sons, 1996), for instance, divides patterns into three main groups -- architectural patterns, design patterns, and idioms. Only the idioms (defined as "low-level patterns specific to a program language") are language dependent -- the other patterns (and the implicit pattern language) rise above the level of programming language, much as the unified modeling language (UML) provides a common way to express designs.

This idea of language independence is seductive. It is also misleading. While patterns are language independent, language choice limits the patterns that are possible, easily supported, and useful. Language also affects how applications are structured.

Where dynamically typed language is more concise

Lazy Initialization Pattern

Here is an example of Lazy Initialization Pattern for a Pizza class in C++:

#include <iostream>
#include <string>
#include <map>
 
using namespace std;
 
class Pizza {
    private:
        static map<string,Pizza*> types;
        string type;

        // note: constructor private forcing one to use static getPizza()
        Pizza(const string& t) : type( t ) {}

    public:
        static Pizza* getPizza(const string& type);
        static void printCurrentTypes();
};

//static member variable
map<string,Pizza*> Pizza::types;        
 
/*
 * Lazy Factory method, gets the instance associated with a
 * certain type. Instantiates new ones as needed.
 * precondition: type. Any string that describes a type
 * postcondition: The instance associated with that type.
 */
Pizza* Pizza::getPizza(const string& type) {
    Pizza *& f = types[type];   //find a pre-existing instance
 
    if (!f) {
        // couldn't find one, then make a new one
        f = new Pizza(type); // lazy initialization
        types.insert(pair<string,Pizza*>(type, f)); // insert new instance into map
    }
    return f;
}

Here is the same pattern realized in Python:

class Pizza:
    def __init__(self, type):
        self.type = type
    
class Pizzas:
    def __init__(self):
        self.types = {}
    
    def get_pizza(self, type):
        if type not in self.types:
            self.types[type] = Pizza(type)
        
        return self.types[type]


Where dynamically typed language is able to realize better than a statically typed language

To illustrate, we'll examine a pattern often used with Objective-C, but not usually used (or used differently) with C++. Both languages are object-oriented extensions of C. The main difference between them is that C++ has compile-time binding and fairly strong typing while Objective-C uses the Smalltalk object model of dynamic binding and weak typing. We'll be examining facade, commonly found in Objective-C programming. While simple, facade illustrates some of the key Objective-C programming techniques

The Facade Pattern

Lets examine a commonly used architectural pattern that describes a way to structure applications to take advantage of this flexibility.

Previously with structured programming, saving an application's state was simple. Programs were divided into data and functions, and saving consisted of calling the function that wrote the data to some storage area. In object-oriented applications, things become a little more difficult. To the extent that you practice information hiding, object serialization is the natural approach to take.

Unfortunately, if m is the number of object references and n is the number of objects, serialization is O(mlog(n)). This is far too slow for serialization to be the saving mechanism in many applications.

But an Objective-C program that uses the four previous patterns will almost certainly use lots of facades as well. Notice here[3]that Facades look a lot like cut points in the object graph. This leads to the natural Objective-C solution to the speed problem for object serialization: Serialize each facade to a separate Serializer (make each facade responsible for serializing the subsystem it abstracts).

This can get complicated. If an object outside a subsystem bypasses a facade (and messages an object in a subsystem directly), then extra care must be taken during serialization (to avoid serializing objects to more than one location). And, deserializing (opening) becomes trickier as well -- objects that bypass a facade will need to find objects within the subsystem. In practice, this comes down to making the facades used in serialization Singletons and making certain that all connections to objects in the subsystem are mediated by the facade (so that, during deserialization, the connection can be restored).

Conclusion

There are various levels of design recognition for generating repeatable solutions to commonly occurring programming problems in software design. Design patterns in this case have been met with criticism in whether it negates the agile product development process. From our example above we have shown that when implementing patterns across various languages of varying type systems a design pattern may not always resolve common problems efficiently. Thus, the idea of a language independent pattern recognition solution must be evaluated thoroughly when in considering from a static and dynamic point of view.

References

All sources used to create this wiki are linked in the above text.