Módulo (patrón de diseño)

De Wikipedia, la enciclopedia libre

En ingeniería de software, el patrón de diseño módulo es un patrón de diseño utilizado para implementar el concepto de módulos de software definidos por el paradigma de programación modular, en un lenguaje de programación que no lo soporta, o lo soporta parcialmente.

Este patrón se puede considerar una variante del singleton o instancia única, con un propósito, más específico.

Definición[editar]

La meta del patrón para diseño de software, consiste en proporcionar las características y estructura sintáctica, definida por el paradigma de programación modular, a lenguajes de programación, que no son compatibles totalmente, o que solo lo son a medias.

Estructura[editar]

El patrón módulo expresado como UML.
El patrón módulo expresado como UML.

Este patrón requiere 2 elementos, un elemento es el elemento de definición; el cual, puede ser un grupo de código fuente, un solo objeto, o una sola clase, que aplica este patrón. Y el otro, es el elemento de implementación, el cual es un grupo de código fuente, un solo objeto, o una sola clase, que utiliza el elemento de definición.

Un elemento, puede utilizar ambos casos, definir un patrón, y aplicar otro. Es común, en este patrón, que se aplique varias veces, en una misma aplicación.

Concepto[editar]

En el proceso de desarrollo de software, existen varias metodologías o paradigmas, para organizar el código fuente en componentes, agrupándolo y separándolo en varios componentes, en ocasiones, como clases u objetos. El paradigma de programación modular es una de esas metodologías, la cual se aplica, generalmente, junto con el paradigma de programación procedimental, como si fuesen una sola técnica. Por lo tanto, a veces, se considera que no debiese ser parte de el paradigma de programación orientada a objetos.

En muchos lenguajes orientados a objetos, el concepto de módulo está implementado de forma incompleta, o sin implementar. Sin embargo, el código fuente orientado a objetos es comúnmente distribuido en varios archivos, en donde hay elementos agrupados, físicamente, como archivos, y agrupados conceptualmente como espacios de trabajo (namespaces).

Varios lenguajes de programación soportan los espacios de trabajo, agrupando lógicamente clases, por medio de un identificador, como Java y C++.

Características[editar]

Para poder considerar un objeto singleton, o cualquier agrupamiento de código relacionado, que está implementando este patrón de diseño, las siguientes características deben estar presentes, en dicho código fuente:

  • Cuando sea implementado por una clase u objeto, solo podrá existir una única instancia en todo el programa.
  • El código deberá tener acceso público, y como propósito, deberá ser considerado para acceso público, no como un objeto singleton regular, que en ciertas ocasiones, podría tener acceso privado o acceso protegido.
  • El código deberá tener acceso global, o ser miembro de una entidad con acceso global.
  • Cuando sea implementado como orientado a objetos, las clases deberán preferentemente ser consideradas, que no serán extendidas.
  • Deberá tener una función o procedimiento para inicialización, que podría ser la misma, equivalente, o complementaria, a un método constructor, de un objeto. Esta característica no es soportada por espacios de trabajo regulares.
  • Deberá tener una función o procedimiento para finalización, que podría ser la misma, equivalente, o complementaria, a un método destructor, de un objeto. Esta característica no es soportada por espacios de trabajo regulares.
  • Soporta y encapsula varios miembros relacionados, que también podrían ser singletons.
  • En caso de tener miembros, esos elementos, podrían requerir su propio código para inicialización, y su propio código para finalización, que deberá ser ejecutado, por la función de inicialización, y por la función de finalización, respectivamente, del módulo principal.
  • Varios de los elementos o miembros, serán funciones que realizan operaciones, en elementos externos a la clase u objeto, que implementa el patrón, y son proporcionados como parámetros de cada función. Esas funciones tienen el propósito de ser utilizadas como herramientas o bibliotecas.

Similitudes y diferencias con conceptos similares[editar]

Espacios de trabajo frente a módulos[editar]

Tanto los espacios de trabajo y los módulos, permiten agrupar entidades relacionadas con un identificador único, y en algunas situaciones intercambiarse. Ambos conceptos, se utilizan globalmente. Por lo tanto, su propósito principal, es el mismo.

Hay escenarios en donde un espacio de trabajo requiere que alguno o algunos de los elementos globales que lo componen, sean inicializados, y en ocasiones, se les aplique un proceso de finalización, ejecutando una función o método.

Debido a que un espacio de trabajo, solo está considerado para agrupar y contener elementos, y no realizar comportamiento alguno, el módulo tiene además el propósito para soportar tanto el proceso de inicialización, como el proceso de finalización.

Clases frente a espacios de trabajo[editar]

Las clases son utilizadas, a veces, también como Espacios de Trabajo, e incluso, combinados. En algunos lenguajes de programación que no soportan espacios de trabajo (por ejemplo: versiones anteriores de PHP, JavaScript o C), pero sí implementan clases y objetos; las clases son utilizadas por sí mismas, permitiendo acceso a sus elementos, sin tener que instanciar directamente cada clase en objetos.

En estas circunstancias, las clases están tomando la función de los espacios de trabajo y de los módulos.

A veces, para este propósito; la característica o modificador de compilación estática es aplicada a la clase misma, o alguno de los miembros de la clase.

Singleton frente a espacios de trabajo[editar]

En los lenguajes de programación en donde los Espacios de Trabajo no están soportados, o están soportados solo parcialmente; y los miembros individuales de una clase no pueden ser accesados, la solución es generar un único objeto de una clase específica, aplicando el patrón de diseño singleton.

Relaciones con otros patrones de diseño[editar]

El patrón de diseño módulo, puede ser implementado utilizando una especialización de el patrón de diseño singleton. Sin embargo, puede combinarse con otros patrones de diseño también.

Este patrón puede utilizarse, junto con los patrones decorador, objeto ligero y adaptador.

Implementaciones de el patrón de diseño módulo en diversos lenguajes de programación[editar]

La semántica y sintaxis de cada lenguaje de programación, puede causar que la implementación de este patrón varie. Cada nueva versión de cada lenguaje de programación, soportando nuevas características, puede causar que haya varias implementaciones, para el mismo lenguaje.

Los siguientes ejemplos muestran una posible implementación de este patrón, pero, podrían existir otros casos, para el mismo lenguaje, versión del lenguaje, y versión de compilador.

Note, que en algunos casos, el patrón de diseño adaptador o envoltorio, ha sido también aplicado, pero no es un requisito.

Ejemplos de implementaciones en lenguajes de programación orientados a objetos[editar]

Java[editar]

Aunque Java soporta el concepto de espacios de trabajo, una versión reducida del módulo, hay algunos escenarios en donde un desarrollado de software quisiera aplicar este patrón en este lenguaje.

Archivo para definición

package consoles;

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 Consoles.mainmodule();
    }
   
    return mainmodule.Singleton;
  }

  // ...

  public void prepare() {
    //System.out.println("console::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("console::unprepare();");
  }
  
  // ...
  
  public void printNewLine() {
    System.out.println("");
  }

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

  public void printInteger(Integer 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(Integer value) {
    // to-do: ...
  }

  public void ScanBoolean(Boolean value) {
    // to-do: ...
  }
  
  // ...
  
}

Archivo para implementación

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(String args[])
  {
    Console.PrintString("Hello World");
    Console.PrintNewLine();
      Console.ScanNewLine();
  }

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

C# (C Sharp.Net)[editar]

C#, al igual que Java, soporta el concepto de espacios de trabajo, una versión reducida de un módulo; hay algunos escenarios, en donde un desarrollador de software quiera implementar este patrón en este lenguaje.

Archivo para definición

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

namespace Consoles {

    public sealed class MainModule {

    private static MainModule Singleton = null;

    public InputStream  input  = null;
    public OutputStream output = null;
    public ErrorStream  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.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: ...
    }
    
    // ...
  
  }
}

Archivo para implementación

  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();
    }
  }

Ejemplo de implementaciones en lenguajes de programación funcional[editar]

JavaScript[editar]

Archivo para definición

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(name) {
    this.Input  = new InputStream();
    this.Output = new OutputStream();
    this.Error  = new ErrorStream();
  }
  
  // ...
  
  var printNewLine = function(name) {
    // to-do: ...
  }

  var printString = function(name) {
    // to-do: ...
  }

  var printInteger = function(name) {
    // to-do: ...
  }

  var printBoolean = function(name) {
    // to-do: ...
  }

  var ScanNewLine = function(name) {
    // to-do: ...
  }

  var ScanString = function(name) {
    // to-do: ...
  }
  
  var ScanInteger = function(name) {
    // to-do: ...
  }

  var ScanBoolean = function(name) {
    // to-do: ...
  }
  
  // ...
  
}

Archivo para implementación

function ConsoleDemo() {
  var Console  = null;

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

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

  var run = function(name) {
    // echo "Hello World";
  }

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

Ejemplo de implementaciones en lenguajes de programación procedimentales[editar]

Este patrón, puede ser visto, como una extensión Procedural en Lenguajes Orientados a Objetos. pero, también aplica en otros escenarios.

Aunque, los paradigmas de programación procedimental y programación modular, se utilizan juntos, frecuentemente existen algunos casos en donde un lenguaje de programación procedimental, no aplique completamente el concepto de módulos.

PHP (modo procedimental)[editar]

Note que este ejemplo aplica en versiones procedimentales, sin espacios de trabajo, de PHP.

Archivo para definición

<?php
  // filename: console.php

  /* void */ console_prepare() {
    // to-do: ...
  }
	
  /* void */ console_unprepare() {
    // to-do: ...
  } 

  // ...
  
  /* void */ console_printNewLine() {
    // to-do: ...
  }
  
  /* void */ console_printString(/* String */ Value) {
	 // to-do: ...
  }
  
  /* void */ console_printInteger(/* Integer */ Value) {
    // to-do: ...
  }
  
  /* void */ console_printBoolean(/* Boolean */ Value) {
    // to-do: ...
  }
  
  /* void */ console_scanNewLine() {
    // to-do: ...
  }
  
  /* void */ console_scanString(/* String */ Value) {
    // to-do: ...
  }
  
  /* void */ console_scanInteger(/* Integer */ Value) {
    // to-do: ...
  }
  
  /* void */ console_scanBoolean(/* Boolean */ Value) {
    // to-do: ...
  }
?>

Archivo para implementación

<?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 Puro"[editar]

Archivo para definición (encabezado)

  // 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);

Archivo para definición (cuerpo)

  // filename: "consoles.c"

  #include <stdio.h>
  #include <string.h>
  #include <ctype.h>
  #include <consoles.h>
  
  void consoles_prepare() {
    // to-do: ...
  }
	
  void consoles_unprepare() {
    // to-do: ...
  } 

  // ...
  
  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);
  }

Archivo para implementación

  // filename: "consoles.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;
  }

Razones para considerar un módulo como patrón de diseño, por sí mismo[editar]

El patrón de diseño módulo puede ser considerado tanto como un patrón de creación o como un patrón estructural. Permite la creación de otros elementos, tal y como lo hace un patrón de creación. Al mismo tiempo, sirve para organizar otros elementos, y agruparlos, de la misma manera que lo hace, un patrón de estructura.

Este patrón, es comúnmente utilizado con el patrón adaptador, cuando se trata de normalizar un espacio de trabajo ya existente, aunque no es requisito necesario.

Un objeto que aplica este patrón, provee la misma funcionalidad de un espacio de trabajo, pero, adicionando el proceso de inicialización, y el proceso de finalización, que no está presente en el mismo.

Proporciona las mismas características que ofrece una clase estática, o una clase con miembros estáticos; pero con una sintaxis y semántica más clara, y precisa.

Soporta diversos escenarios, en donde una clase u objeto, puede ser tratado como datos estructurados y procedimentales. Y, viceversa, migrar datos estructurados y procedimentales, y puedan tratarse como datos orientados a objetos.

Véase también[editar]

  • Patrones de diseño: resumen de los patrones de diseño de software, en general.
  • Singleton o instancia única: un patrón para restringir y administrar una sola instancia de objetos, usualmente con acceso global.
  • Adaptador: un patrón para permitir la interfaz de una clase, ser utilizada, como la interfaz de otra clase. También se le conoce como envoltura o traductor.