Jump to content

Module pattern

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Wenerme (talk | contribs) at 16:53, 15 June 2014 (fix code.). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

In software engineering, the module pattern is a design pattern used to implement the concept of software modules, defined by modular programming, in a programming language that does not or only partially supports it.

There are several ways to implement this pattern, depending on the host programming language, such as the singleton design pattern, object-oriented static members in a class, and procedural global functions.

Definition

The module software design pattern provides the features and syntactic structure defined by the modular programming paradigm to programming languages that do not support it or support it only partially.

Structure

The object module pattern expressed in UML.
The object module pattern expressed in UML.

Concept

In software development, there are several approaches to organizing source code into components which accomplish a particular function or contain everything necessary to accomplish a particular task. Modular programming is one of those approaches and is usually applied with procedural programming.

The concept of a "module" is not fully supported or not supported at all in many common programming languages.

Features

In order to consider that a Singleton or any group of related code implements this pattern, the following features must be applied:

  • A portion of the code must have global or public access and be meant to be used as global or public code. Additional private or protected code can be used, but it must be designed and executed by the main public code.
  • A module must have an initializer function, which may be the same as, equivalent to, or complementary to an object constructor method. This feature is not supported by regular namespaces.
  • A module must have a finalizer function, may be the same as, equivalent to, or complementary to an object destructor method. This feature is not supported by regular namespaces.
  • In case of supporting members, those elements may require initialization code that is executed by the initializer function of the module.
  • In case of supporting members, those elements may require finalization code that is executed by the finalizer function of the module.
  • Most members are functions that perform operations on elements external to the class, provided as parameters. Those functions are meant to be frequently used as "utilities", "tools" or "libraries".

Implementations

The semantics and syntax of each programming language may cause the implementation of this pattern to differ. New features introduced in newer versions of a language specification may also alter the possible or preferred implementation in a given language.

The following examples show possible implementations of this pattern in various languages. However, other implementations may exist.

Examples in object-oriented programming languages

Java

Although Java support the notion of a namespace, a reduced version of a module, there are some scenarios where a software developer may want to apply the design pattern instead of using namespaces.

The following example uses the singleton pattern.

Definition
package consoles;

import java.io.InputStream;
import java.io.PrintStream;

public final class MainModule {

  private static MainModule singleton = null;

  public InputStream input = null;
  public PrintStream output = null;
  public PrintStream error = null;

  public MainModule() {
    // does nothing on purpose !!!
  }

  // ...

  public static MainModule getSingleton() {
    if (MainModule.singleton == null) {
       MainModule.singleton = new MainModule();
    }
 
    return MainModule.singleton;
  }

  // ...

  public void prepare() {
    //System.out.println("consoles::prepare();");

    this.input = new InputStream();
    this.output = new PrintStream();
    this.error = new PrintStream();
  }
  
  public void unprepare() {
    this.output = null;
    this.input = null;
    this.error = null;
  
    //System.out.println("consoles::unprepare();");
  }
  
  // ...
  
  public void printNewLine() {
    System.out.println();
  }

  public void printString(String value) {
    System.out.print(value);
  }

  public void printInteger(int value) {
    System.out.print(value);
  }

  public void printBoolean(boolean value) {
    System.out.print(value);
  }
  
  public void scanNewLine() {
    // to-do: ...
  }
  
  public void scanString(String value) {
    // to-do: ...
  }

  public void scanInteger(int value) {
    // to-do: ...
  }

  public void scanBoolean(boolean value) {
    // to-do: ...
  }
  
  // ...
  
}
Implementation
import consoles;

class ConsoleDemo {
  public static MainModule console = null;

  public static void prepare() {
    console = MainModule.getSingleton();

    console.prepare();
  }

  public static void unprepare() {
    console.unprepare();
  }

  public static void execute(String[] args) {
    console.printString("Hello World");
    console.printNewLine();
    console.scanNewLine();
  }

  public static void main(String[] args) {
    prepare();
    execute(args);
    unprepare();
  }
}

C# (C Sharp .Net)

C#, like Java, support the notion of a namespace; however, there are some scenarios in which a software developer may want to apply this pattern.

The following example uses the singleton pattern.

Definition
using System;
using System.IO;
using System.Text;

namespace Consoles {

  public InputStream  input  = null;
  public OutputStream output = null;
  public ErrorStream  error  = null;

  public sealed class MainModule {
    private static MainModule Singleton = null;

    // ...

    public MainModule () {
      // does nothing on purpose !!!
    }

    // ...

    public MainModule getSingleton() {
      if (MainModule.Singleton == null)
      {
         MainModule.Singleton = new MainModule();
      }

      return MainModule.Singleton;
    }

    // ...

    public void prepare() {
      //System.WriteLine("console::prepare();");

      this.input  = new InputStream();
      this.output = new OutputStream();
      this.error  = new ErrorStream();
    }
    
    public void unprepare() {
      this.output = null;
      this.input  = null;
      this.error  = null;
    
      //System.WriteLine("console::unprepare();");
    }
    
    // ...
  
    public void printNewLine() {
      System.Console.WriteLine("");
    }
  
    public void printString(String Value) {
	  System.Console.Write(Value);
    }
  
    public void printInteger(Integer Value) {
      System.Console.Write(Value);
    }
  
    public void printBoolean(Boolean Value) {
      System.Console.Write(Value);
    }
    
    public void ScanNewLine() {
      // to-do: ...
    }
    
    public void ScanString(String Value) {
      // to-do: ...
    }
  
    public void ScanInteger(Integer Value) {
      // to-do: ...
    }
  
    public void ScanBoolean(Boolean Value) {
      // to-do: ...
    }
    
    // ...
  
  }
}
Implementation
  class ConsoleDemo {
    public static Consoles.MainModule Console = null;
   
    public static void prepare()
    {
      Console = Consoles.MainModule.getSingleton();
   
      Console.prepare();
    }
   
    public static void unprepare()
    {
      Console.unprepare();
    }
   
    public static void execute()
    {
      Console.PrintString("Hello World");
      Console.PrintNewLine();
        Console.ScanNewLine();
    }
   
    public static void main()
    {
      prepare();
      execute(args);
      unprepare();
    }
  }

Examples in prototype-based programming languages

JavaScript

Definition
function ConsoleClass() {
  var Input  = null;
  var Output = null;
  var Error  = null;

  // ...
  
  this.prepare = function() {
    this.Input  = new InputStream();
    this.Output = new OutputStream();
    this.Error  = new ErrorStream();
  }

  this.unprepare = function() {
    this.Input  = null;
    this.Output = null;
    this.Error  = null;
  }
  
  // ...
  
  var printNewLine = function() {
    // code that prints a new line
  }

  var printString = function(params) {
    // code that prints parameters
  }

  var printInteger = function(params) {
    // code that prints parameters
  }

  var printBoolean = function(params) {
    // code that prints parameters
  }

  var ScanNewLine = function() {
    // code that looks for a newline
  }

  var ScanString = function(params) {
    // code that inputs data into parameters
  }
  
  var ScanInteger = function(params) {
    // code that inputs data into parameters
  }

  var ScanBoolean = function(params) {
    // code that inputs data into parameters
  }
  
  // ...
  
}
Implementation
function ConsoleDemo() {
  var Console  = null;

  var prepare = function() {
    Console  = new ConsoleClass();

    Console.prepare();
  }
  
  var unprepare = function() {
    Console.unprepare();  
  }

  var run = function() {
    Console.printString("Hello World");
    Console.printNewLine();
  }

  var main = function() {
    this.prepare();
    this.run();
    this.unprepare();
  }  
}

Examples in procedural programming languages

This pattern may be seen as a procedural extension to object-oriented languages.

Although the procedural and modular programming paradigms are often used together, there are cases where a procedural programming language may not fully support modules, hence requiring a design pattern implementation.

PHP (procedural)

This example applies to procedural PHP without namespaces (introduced in version 5.3.0). It is recommended that each member of a module is given a prefix related to the filename or module name in order to avoid identifier collisions.

Definition
<?php
  // filename: console.php

  /* void */ console_prepare() {
    // code that prepares a "console"
  }
	
  /* void */ console_unprepare() {
    // code that unprepares a "console"
  } 

  // ...
  
  /* void */ console_printNewLine() {
    // code that ouputs a new line
  }
  
  /* void */ console_printString(/* String */ Value) {
    // code that prints parameters
  }
  
  /* void */ console_printInteger(/* Integer */ Value) {
    // code that prints parameters
  }
  
  /* void */ console_printBoolean(/* Boolean */ Value) {
    // code that prints parameters
  }
  
  /* void */ console_scanNewLine() {
    // code that looks for a new line
  }
  
  /* void */ console_scanString(/* String */ Value) {
    // code that stores data into parameters
  }
  
  /* void */ console_scanInteger(/* Integer */ Value) {
    // code that stores data into parameters
  }
  
  /* void */ console_scanBoolean(/* Boolean */ Value) {
    // code that stores data into parameters
  }
?>
Implementation
<?php
    // filename: consoledemo.php

    require_once("console.php");

    /* void */ consoledemo_prepare()
    {
      console_prepare();
    }
   
    /* void */ consoledemo_unprepare()
    {
      console_unprepare();
    }
   
    /* void */ consoledemo_execute()
    {
      console_printString("Hello World");
      console_printNewLine();
        console_scanNewLine();
    }
   
    /* void */ consoledemo_main()
    {
      consoledemo_prepare();
      consoledemo_execute();
      consoledemo_unprepare();
    }
?>

C

Note that this example applies to procedural C without namespaces. It is recommended that each member of a module is given a prefix related to the filename or module name in order to avoid identifier collisions.

Definition header module
  // filename: "consoles.h"

  #include <stdio.h>
  #include <string.h>
  #include <ctype.h>
  
  void consoles_prepare();	
  void consoles_unprepare();

  // ...
  
  void consoles_printNewLine();
  
  void consoles_printString(char* Value);  
  void consoles_printInteger(int Value);  
  void consoles_printBoolean(bool Value);
  
  void consoles_scanNewLine(); 
  
  void consoles_scanString(char* Value);  
  void consoles_scanInteger(int* Value);  
  void consoles_scanBoolean(bool* Value);
Definition body module
  // filename: "consoles.c"

  #include <stdio.h>
  #include <string.h>
  #include <ctype.h>
  #include <consoles.h>
  
  void consoles_prepare() {
    // code that prepares console
  }
	
  void consoles_unprepare() {
    // code that unprepares console
  } 

  // ...
  
  void consoles_printNewLine() {
    printf("\n");
  }
  
  void consoles_printString(char* Value) {
	printf("%s", Value);
  }
  
  void consoles_printInteger(int Value) {
    printf("%d", &Value);
  }
  
  void consoles_printBoolean(bool Value) {
    if (Value)
	{
	  printf("true");
	}
	else
	{
	  printf("false");
	}
  }
  
  void consoles_scanNewLine() {
    getch();
  }
  
  void consoles_scanString(char* Value) {
    scanf("%s", Value);
  }
  
  void consoles_scanInteger(int* Value) {
    scanf("%d", Value);
  }
  
  void consoles_scanBoolean(bool* Value) {
    char temp[512];
    scanf("%s", temp);
	
	*Value = (strcmp(Temp, "true") == 0);
  }
Implementation
  // filename: "consoledemo.c"

  #include <stdio.h>
  #include <string.h>
  #include <ctype.h>
  #include <consoles.h>

  void consoledemo_prepare()
  {
    consoles_prepare();
  }
   
  void consoledemo_unprepare()
  {
    consoles_unprepare();
  }
   
  int consoledemo_execute()
  {
    consoles_printString("Hello World");
    consoles_printNewLine();
      consoles_scanNewLine();
	  
	 return 0;
  }
   
  int main()
  {
    ErrorCode Result = 0;
  
    consoledemo_prepare();
    ErrorCode = consoledemo_execute();
    consoledemo_unprepare();
	
	return ErrorCode;
  }

Procedural Pascal

Note that this example applies to procedural non-modular Pascal. Many Pascal dialects already have namespace support, called "unit (s)". Some dialects also support initialization and finalization.

If namespaces are not supported, it is recommended to give all member names a prefix related to the filename or module name in order to prevent identifier collisions.

Definition
  unit consoles;
  (* filename: "consoles.pas" *)

  uses crt;

  procedure prepare();
  begin
    (* code that prepares console *)
  end;
	
  procedure unprepare();
  begin
    (* code that unprepares console *)
  end; 

  // ...
  
  procedure printNewLine();
  begin
    WriteLn();
  end;
  
  procedure printString(Value: string);
  begin
	Write(Value);
  end;
  
  procedure printInteger(Value: integer);
  begin
	Write(Value);
  end;
  
  procedure printBoolean(Value: boolean);
  begin
    if (Value) then
	begin
  	  Write('true');
	end else
	begin
  	  Write('false');
	end;
  end;
  
  procedure scanNewLine();
  begin
    SeekEoLn();
  end;
  
  procedure scanString(Value: string);
  begin
    ReadLn(Value);
  end;
  
  procedure scanInteger(Value: Integer);
  begin
    ReadLn(Value);
  end;
  
  procedure scanBoolean(Value: Boolean);
    var temp: string;
  begin
    ReadLn(temp);
	
    if (Temp = 'true') then
    begin
      Value := true;
    end else
    begin
      Value := false;
    end;
  end;
Implementation
  program consoledemo;
  // filename: "consoles.pas"

  uses consoles;

  procedure prepare();
  begin
    consoles.prepare();
  end;
   
  procedure unprepare();
  begin
    consoles.unprepare();
  end;

  function execute(): Integer;
  begin
    consoles.printString('Hello World');
    consoles.printNewLine();
      consoles.scanNewLine();
	  
    execute := 0;
  end;
   
  begin
    prepare();
    execute();
    unprepare();
  end.

Comparisons to other concepts

Namespaces

Both namespaces and modules allow to group several related entities by a single identifier, and in some situations, used interchangeably. Those entities can be globally accessed. The main purpose of both concepts its be the same.

There are scenarios where a namespace requires that the global elements that compose it are initialized and finalized by a function or method call.

In many programming languages, namespaces are not directly intended to support an initialization process nor a finalization process, and are therefore not equivalent to modules. That limitation can be worked around in two ways. In namespaces that support global functions, a function for initialization and a function for finalization are coded directly, and called directly in the main program code.

Classes and namespaces

Classes are used sometimes used as or with namespaces. In programming languages that don't support namespaces (e.g. PHP < 5.3.0, JavaScript) but do support classes and objects, classes are often used to substitute for namespaces. These classes are usually not instantiated and consist exclusively of static members.

Singleton classes and namespaces

In object-oriented programming languages where namespaces are not supported or partially supported, the singleton pattern may be used instead of static members within a non-instantiable class.

Relationship with other design patterns

The module pattern can be implemented using a specialization of the singleton pattern. However, other design patterns may applied and combined, in the same class as well.

This pattern can be used as a decorator, a flyweight, or an adapter.

Module as a design pattern

The Module pattern can be considered a creational pattern and a structural pattern. It allows to manage the creation of other elements and organize other elements, and group them as the structural pattern does.

An object that applies this pattern can perform the functionality of a namespace, providing the initialization and finalization process of a static class or a class with static members with cleaner, more concise syntax and semantics.

Supports specific cases where a class or object can be considered structured, procedural data. And, vice-versa, migrate structured, procedural data, and considered as object-oriented.

See also