Modul programtervezési minta

A szoftverfejlesztésben a modul minta egy olyan programtervezési minta, amit a moduláris programozásban definiált szoftver modulok létrehozására használnak egy olyan programozási nyelven, ami nem rendelkezik teljes (beépített) támogatással a modul koncepció megvalósítására.

A minta több különböző módon is implementálható, a konkrét programozási nyelvtől függően, mint például az egyke minta, az objektumorientált nyelvek osztályaiban a statikus tagok vagy a procedurális nyelvek globális függvényei. A Python nyelvben a modul minta egy beépített tulajdonság, minden .py kiterjesztésű fájl automatikusan egy-egy modul is.

Definíció és struktúra

A modul programtervezési minta a moduláris programozás paradigmája szerint definiált modul fogalmának megfelelő tulajdonságokat és szintaktikai struktúrát valósítja meg olyan programozási nyelveknél, amik nem rendelkeznek teljes (beépített) támogatással a modul koncepció megvalósítására.

Egy objektum modul minta UML ábrája.
Egy objektum modul minta UML ábrája.

Koncepció

A szoftverfejlesztésben, a forráskód komponensekbe szervezhető, amik egy adott funkcionalitást valósítanak meg, vagy tartalmaznak mindent ami egy adott (bővebb) feladat elvégzéséhez szükséges. A moduláris programozás ezen megközelítések egyike, ahol a modul egy ilyen forráskód komponensként fogható fel.

Sok elterjedt programozási nyelven, a „modul“ koncepciója nem teljes körűen támogatott beépített konstrukcióként.

Tulajdonságok

Ahhoz, hogy azt mondhassuk, hogy egy egyke osztály vagy bármilyen összetartozó kód-csoport implementálja ezt a mintát, a következő tulajdonságokkal kell rendelkeznie:

  • A kód egy részének globális vagy publikus elérésűnek kell lennie, és ennek megfelelően külső (globális/publikus) használatra tervezettnek. A további saját (private) vagy védett (protected) kódot a „fő“ (nyilvános) kód hívja (hajtja végre).
  • A modulnak kell hogy legyen egy inicializáló metódusa, amely megfeleltethető (hasonlít) egy osztály konstruktor metódusához. Ez a képesség általánosságban nincs meg a névtereknél (namespace).
  • A modulnak kell hogy legyen egy lezáró metódusa (finalizer), amely megfeleltethető (hasonlít) egy osztály destruktor metódusához. Ez a képesség sincs meg általában a névtereknél.
  • A modul egyes elemei is megkövetelhetnek további inicializációs/lezáró kódokat, amiket a modul inicializáló/lezáró metódusai hajtanak végre.
  • A legtöbb modul-elem valamilyen függvény, amelyek modulon kívüli elemeken hajtanak végre műveleteket, amely külső elemek függvény-argumentumként kerülnek átadásra. Az ilyen függvények általában „segédkódok“ (utility), „kódeszközök“ (tool) vagy „könyvtárak“ (library).

Implementációk

Az egyes programozási nyelvek szintaxisa és a szemantikája befolyásolja a minta implementációját.

Objektumorientált programozási nyelvek

Java

Habár a Java támogatja a névterek (namespace) fogalmát (ami a modul egy egyszerűsített változataként is felfogható), bizonyos esetekben előnyét élvezhetjük annak, ha a modul mintát használjuk, az egyszerű névterek helyett.

A következő példa az egyke mintát használja a modul minta megvalósítására.

Modul definíció
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() {
    // Szándékosan nem csinál semmit !!!
  }

  // ...

  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: ...
  }
  
  // ...
  
}
Használat
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)

A C#, csakúgy mint a Java, támogatja a névterek használatát, de adott esetben a modul minta alkalmazása továbbra is hasznos lehet.

A következő példa az egyke mintát használja a modul minta megvalósítására.

Modul definíció
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 () {
      // Szándékosan nem csinál semmit !!!
    }

    // ...

    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: ...
    }
    
    // ...
  
  }
}
Használat
  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();
    }
  }

Prototípus alapú programozási nyelvek

JavaScript

A JavaScript széleskörűen használt a WEB alapú technológiákban. Az objektumokat nem konstruktorok segítségével, hanem a prototípus klónozásával állítja elő.

Modul definíció
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
  }
  
  // ...
  
}
Használat
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();
  }  
}

Procedurális programozási nyelvek

A modul minta, az objektumorientált nyelvek procedurális kiegészítéseként is tekinthető.

Habár a procedurális és moduláris programozási paradigmák gyakran használatosak együtt, vannak esetek, ahol egy procedurális programnyelv nem teljes mértékben támogatja a modulokat, ami miatt a modul minta implementálása szükséges.

PHP (procedurálisan)

Az alábbiakban egy a procedurális, névtér használat lehetősége előtti (5.3.0 verziót megelőző) PHP nyelvre alkalmazott példa. Ajánlott, hogy egy modul minden eleme (függvénye), el legyen látva egy a modul-fájl vagy a modul nevéhez kapcsolódó előtaggal, az esetleges azonosító ütközések elkerülése érdekében.

Modul definíció
<?php
  // filename: console.php

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

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

    require_once("console.php");

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

C

Az alábbi példa a procedurális, névterek nélküli C nyelvre vonatkozik. Ajánlott, hogy egy modul minden eleme (függvénye), el legyen látva egy a modul-fájl vagy a modul nevéhez kapcsolódó előtaggal, az esetleges azonosító ütközések elkerülése érdekében.

Modul definíció header
  // 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);
Modul definíció törzs
  // 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);
  }
Használat
  // 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;
  }

Procedurális Pascal

Az alábbi példa a procedurális, nem moduláris Pascal nyelvre vonatkozik. Több Pascal dialektus támogatja a „unit“-oknak nevezett névterek használatát. Néhány dialektus a unitok inicializációs és lezáró (finalizer) kódjainak megadását is támogatja.

Ha a névterek használata nem támogatott, ajánlott, hogy egy modul minden eleme (függvénye), el legyen látva egy a modul-fájl vagy a modul nevéhez kapcsolódó előtaggal, az esetleges azonosító ütközések elkerülése érdekében.

Modul definíció
  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;
Használat
  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.

Összehasonlítás egyéb koncepciókkal

Néveterek (namespace)

Mind a névtér, mind a modul lehetővé teszi egymással összefüggő kód-entitások (pl. függvények) csoportosítását egyetlen azonosító „alá“ (modulnév / névtér), és bizonyos szituációkban felcserélhető módon használhatóak. A csoportosított entitások globálisan elérhetőek. Mindkét koncepció fő célja azonos.

Néhány esetben egy névtér megkövetelheti a részét képező globális elemek, egy metódus hívással történő inicializálását vagy lezárását.

Sok programozási nyelvben, a névterek közvetlenül nem támogatnak inicializációs vagy lezáró eljárásokat, és emiatt ezek nem tekinthetőek egyenlőnek a modul fogalmával. Ez a korlátozás oly módon oldható fel, hogy a globális függvényeket támogató névterekben, közvetlenül lekódolásra kerül egy inicializáló és egy lezáró (finalizer) függvény, amelyek direkt módon meghívásra kerülnek a főprogram kódjában.

Osztályok (classes) és a névterek

Az osztályok néha a névterekkel együtt, vagy azokat helyettesítve használatosak. Azokban a programozási nyelvekben (mint például a JavaScript) amik nem támogatják a névtereket, de támogatják az osztályokat és objektumokat, az osztálydefiníciók gyakran használatosak a névterek helyettesítésére. Az ilyen osztályokból rendszerint nem készülnek példányok és kizárólag statikus (osztályszintű) tag-metódusokból állnak, és az osztály neve használható névtérként (összefogva az osztály tag-metódusait).

Egyke (singleton) osztályok és a néveterek

Azokban az objektumorientált nyelvekben, ahol a névterek nem támogatottak, az egyke minta is használható a névterek helyettesítésére, a „nem példányosított osztály, statikus tag-metódusokkal“ megoldás helyett (lásd fentebb).

Kapcsolat más tervezési mintákkal

A modul minta implementálható az egyke minta specializált változataként. Más tervezési minták is alkalmazhatóak és kombinálhatóak, ugyanabban az osztályban. A modul minta használható, mint díszítő minta, vagy pehelysúlyú minta vagy akár illesztő mintaként.

A modul mint tervezési minta

A modul mintára tekinthetünk létrehozási mintaként és szerkezeti mintaként is. Egyrészt menedzseli más elemek létrehozását és szervezését, másrészt csoportba (struktúrába) foglalja az elemeket, ahogyan a szerkezeti minták teszik.

A minta alkalmazása támogatja azokat a specifikus eseteket, amikor egy osztályra, strukturált, procedurális kódként tekintünk, csakúgy, mint ennek a fordítottját, ahol strukturált, procedurális kódra tekintünk objektumorientáltan, egy osztály metódusaiként.

Fordítás

Ez a szócikk részben vagy egészben a Module pattern című angol Wikipédia-szócikk fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.