Guarda! Senza mani! - Ottava parte
a cura di Sabrina Cosolo e Diego Cattaruzza (requisiti: conoscenza intermedia di Sql Server e di .Net)

Iniziamo facendo un po' di disastri
Prima di costruire le classi mancanti per avere il necessario a creare un database, facciamo un po' di rivoluzioni, in questo modo vediamo cosa accade nel mondo reale quando, mentre si sta sviluppando una applicazione, si trova una brillante soluzione ad un problema e la si vuole applicare a tutto ciò che si ha già scritto.

nota: il codice esposto in questo articolo è lo stretto indispensabile a comprendere l'articolo stesso, essendo questo molto lungo, questa volta. Il codice completo è ovviamente fornito come al solito (vedi link a fondo pagina).

Mentre implementavamo la seconda collection del progetto, ci siamo resi conto che c'è un test ripetitivo che merita di essere trasformato in un metodo, pertanto abbiamo generato, nella libreria TAndT.Base, la nuova classe StringHelper, anche questa statica, in cui abbiamo messo il metodo:

namespace TAndT.Base
{
  public static class StringHelper
  {
    public static bool IsNullOrTrimEmpty(string pStringValue)
    {
      return (pStringValue == null || pStringValue.Trim().Length == 0);
    }
  }
}
Public Class StringHelper

  Public Shared Function IsNullOrTrimEmpty(ByVal stringValue As String) As Boolean
    Return stringValue Is Nothing OrElse stringValue.Trim.Length = 0
  End Function

End Class

Il metodo implementato è IsNullOrTrimEmpty, il quale verifica che una stringa non sia nulla né vuota.
Nella nostra logica, una stringa è vuota quando non contiene nulla oppure quando contiene solo spazi.
Questa è una regola di business, applicabile ovunque serve, diversa da quella implementata dalla classe di sistema String dove, giustamente, una stringa con degli spazi non è considerata vuota.

Generato il metodo, andiamo a sostituire in tutte le classi già implementate i controlli:

      if( miastringa != null && miastringa.Trim().Length > 0 )
    If miastringa IsNot Nothing AndAlso miastringa.Trim().Length > 0 Then

con

      if( !StringHelper.IsNullOrTrimEmpty(miastringa) )
    If Not StringHelper.IsNullOrTrimEmpty(miastringa) Then

ed i controlli:

      if( miastringa == null || miastringa.Trim().Length == 0 )
    If miastringa Is Nothing OrElse miastringa.Trim().Length = 0 Then

con

      if( StringHelper.IsNullOrTrimEmpty(miastringa) )
    If StringHelper.IsNullOrTrimEmpty(miastringa) Then

Dove non presente, è opportuno inserire la direttiva:

using TAndT.Base;
Imports TAndT.Base

Ecco dove cercare:

Classi Membri
IoHelper CheckEmptyPath
  CreateAppConfigFileName
  CreateUsrConfigFileName
TAndTSetting IsValid
TAndTSettings SetFromDataTable
DbFileGrowth IsValid
DbFileMaxSize IsValid
DbFileSize IsValid
UsingSqlServerConfig CnStringMaster
  CnStringMsdb
  IsValid

Applicando questa modifica e scorrendo le nostre classi collection ed entità, ci siamo accorti che, nell'implementazione della generazione della DataTable all'interno della collezione TAndTSettings, abbiamo utilizzato i nomi delle proprietà della classe TAndTSetting per i nomi dei campi, e queste stringhe appaiono anche all'interno dell'entità come parametri dell'evento OnPropertyChanged della classe.
Per coerenza, quindi, abbiamo deciso di mettere le costanti con i nomi dei campi all'interno della classe Entity (TAndTSetting) rendendoli pubblici, in modo che possano essere usati sia dall'implementazione di OnPropertyChanged che nella generazione delle DataTable nella collezione.

        public const string FLD_NAME = "Name";
        public const string FLD_VALUE = "Value";
    Public Const FLD_NAME As String = "Name"
    Public Const FLD_VALUE As String = "Value"

Queste due costanti sono ora nella classe TAndTSetting; sono conseguentemente cambiate le proprietà Name e Value.
Abbiamo aggiunto, già che c'eravamo, una costante di formato, FMP_Tostring, modificando poi il metodo ToString.

        private const string FMP_ToString = "{0} = {1}";

        // ...
        public string Name
         {
             // ...
             set
             {
                 mName = value;
                 OnPropertyChanged(new PropertyChangedEventArgs(FLD_NAME));
             }
         }

        // ...
        public string Value
         {
             // ...
             set
             {
                 mValue = value;
                 OnPropertyChanged(new PropertyChangedEventArgs(FLD_VALUE));
             }
         }

        public override string ToString()
        {
            try
            {
                return (string.Format(FMP_ToString, this.Name, this.Value));
            }
            // ...
        }
    Public Const FMP_ToString As String = "{0} = {1}"

     Public Property Name() As String
       ' ...
       Set(ByVal value As String)
         mName = value
         OnPropertyChanged(New PropertyChangedEventArgs(FLD_NAME))
       End Set
     End Property

     Private mValue As String
     Public Property Value() As String
       ' ...
       Set(ByVal value As String)
         mValue = value
         OnPropertyChanged(New PropertyChangedEventArgs(FLD_VALUE))
       End Set
    End Property

    Public Overrides Function ToString() As String
      Try
        Return String.Format(FMP_ToString, Me.Name, Me.Value)
         ' ...
      End Try
    End Function

Di conseguenza, nella classe TAndTSettings variano i metodi GetDataTable e SetFromDataTable:

    public DataTable GetDataTable(string pTableName)
    {
      try
      {
        System.Data.DataTable dt = new System.Data.DataTable();
        dt.TableName = pTableName;
        dt.Columns.Add(TAndTSetting.FLD_NAME, typeof(string));
        dt.Columns.Add(TAndTSetting.FLD_VALUE, typeof(string));
        dt.PrimaryKey = new System.Data.DataColumn[] { dt.Columns[TAndTSetting.FLD_NAME] };

        foreach (TAndTSetting stt in this)
        {
          System.Data.DataRow dr = dt.NewRow();
          dr[TAndTSetting.FLD_NAME] = stt.Name;
          dr[TAndTSetting.FLD_VALUE] = stt.Value;
          dt.Rows.Add(dr);
        }
        return (dt);
      }
      // ...
    }

    public void SetFromDataTable(System.Data.DataTable pDt, bool pClearCollection)
    {
      try
      {
        // ...
        for (int i = 0; i < pDt.Rows.Count; i++)
        {
          System.Data.DataRow drV = pDt.Rows[i];
          string name = FieldValue<string>.Value(drV[TAndTSetting.FLD_NAME]);
          string value = FieldValue<string>.Value(drV[TAndTSetting.FLD_VALUE], string.Empty);
          if (StringHelper.IsNullOrTrimEmpty(name))
          {
            SetSetting(name, value);
          }
        }
      }
      // ...
    }
    Public Function GetDataTable(ByVal tableName As String) As DataTable
      Try
        Dim dt As New DataTable
        dt.TableName = tableName
        dt.Columns.Add(TAndTSetting.FLD_NAME, Type.GetType("System.String"))
        dt.Columns.Add(TAndTSetting.FLD_VALUE, Type.GetType("System.String"))
        dt.PrimaryKey = New DataColumn() {dt.Columns(TAndTSetting.FLD_NAME)}

        For Each stt As TAndTSetting In Me
          Dim dr As DataRow = dt.NewRow
          dr(TAndTSetting.FLD_NAME) = stt.Name
          dr(TAndTSetting.FLD_VALUE) = stt.Value
          dt.Rows.Add(dr)
        Next

        ' ...
    End Function

    Public Sub SetFromDataTable(ByVal dt As DataTable, ByVal clearCollection As Boolean)
        ' ...
        For i As Integer = 0 To dt.Rows.Count - 1
          Dim drV As DataRow = dt.Rows(i)
          Dim name As String = FieldValue(Of String).Value(drV(TAndTSetting.FLD_NAME))
          Dim value As String = _
                              FieldValue(Of String).Value(drV(TAndTSetting.FLD_VALUE), String.Empty)
          If StringHelper.IsNullOrTrimEmpty(name) Then
            SetSetting(name, value)
          End If
        Next

        ' ...
    End Sub

Come potete notare, le costanti sono usate con il nome proprio nella classe Entity, mentre nella classe collection sono referenziate usando il nome della classe Entity che le contiene.
Analogamente, cambia il metodo ConfiguraDataGrid nella FrmConfig, e bisogna aggiungere la direttiva del caso:

using TAndT.Base.Entities;

 // ...

    private void ConfiguraDataGrid()
     {
 // ...

         DataGridViewColumn col = this.dgApp.Columns[TAndTSetting.FLD_NAME];

 // ...

         col = this.dgApp.Columns[TAndTSetting.FLD_NAME];

 // ...

         col = this.dgUser.Columns[TAndTSetting.FLD_NAME];

 // ...

         col = this.dgUser.Columns[TAndTSetting.FLD_VALUE];

 // ...

     }
Imports TAndT.Base.Entities
 
  ' ...
 
  Private Sub ConfiguraDataGrid()
 
  ' ...
 
      Dim col As DataGridViewColumn = dgApp.Columns(TAndTSetting.FLD_NAME)
 
  ' ...
 
      col = dgApp.Columns(TAndTSetting.FLD_VALUE)
 
  ' ...
 
      col = dgUser.Columns(TAndTSetting.FLD_NAME)
 
  ' ...
 
      col = dgUser.Columns(TAndTSetting.FLD_VALUE)
 
  ' ...
 
  End Sub

Continuando con le messe a punto, abbiamo deciso che il posto corretto ove inserire il metodo VerificaSeIlFormEAperto implementato su FrmMain per il test della nostra prima form non è quello giusto: infatti, se implementassimo un'altra applicazione di sicuro potremmo volerlo applicare, pertanto abbiamo modificato la libreria TAndT.UI, aggiungendo una cartella di nome Forms, dentro la quale abbiamo creato una nuova classe statica FormHelper, cui abbiamo aggiunto la direttiva al namespace System.Windows.Forms e vi abbiamo trasferito il metodo che si trovava sulla FrmMain, modificato nel modo seguente:

 // ...
using System.Windows.Forms;

namespace TAndT.UI.Forms
{
  public static class FormHelper
  {
 // ...
    public static Form VediSeIlFormEAperto(string pName, Form pMdi)
    {
      try
      {
        Form retForm = null;
        foreach (Form child in pMdi.MdiChildren)
        {
          if (child.Name == pName)
          {
            retForm = child;
            break;
          }
        }
        return (retForm);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  }
}
Imports System.Windows.Forms

Namespace Forms

  Public Class FormHelper

  ' ...
    Public Shared Function VediSeIlFormEAperto(ByVal name As String, ByVal mdi As Form) As Form
      Try
        Dim retForm As Form = Nothing
        For Each child As Form In mdi.MdiChildren
          If child.Name = name Then
            retForm = child
            Exit For
          End If
        Next
        Return retForm

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                           + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try
    End Function

  End Class

End Namespace

Come potete vedere, abbiamo passato la form MDI come parametro al metodo, che restituisce il form, se è aperto, oppure nulla.

Per praticità d'uso e per non far divenire l'MDI una form con 12000 righe di codice, abbiamo anche deciso che le funzioni di apertura delle form verranno spostate all'interno delle form stesse. Pertanto, il codice che prima si trovava all'interno del metodo configuraToolStripMenuItem_Click, costituisce il seguente metodo:

    public static void OpenMe(Icon pIcon, Form pMDI)
    {
      try
      {
        FrmConfig frm = (FrmConfig)FormHelper.VediSeIlFormEAperto("FrmConfig", pMDI);
        if (frm == null)
        {
          frm = new FrmConfig();
          frm.Icon = pIcon;
          frm.MdiParent = pMDI;
          frm.Show();
        }
        else
        {
          frm.WindowState = FormWindowState.Normal;
          frm.BringToFront();
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Public Shared Sub OpenMe(ByVal icon As Icon, ByVal mdi As Form)
    Dim frm As FrmConfig = DirectCast(FormHelper.VediSeIlFormEAperto("FrmConfig", mdi), FrmConfig)
    If frm Is Nothing Then
      frm = New FrmConfig
      frm.Icon = icon
      frm.MdiParent = mdi
      frm.Show()
    Else
      frm.WindowState = FormWindowState.Normal
      frm.BringToFront()
    End If
  End Sub

Essendo statico, questo metodo non necessita dell'istanza della classe form di cui è membro - nota per coloro cui sembrerebbe una contraddizione una classe che istanzi se stessa e si definisca come la nuova istanza.
Questo metodo è stato inserito all'interno della classe FrmConfig, cui abbiamo aggiunto la direttiva

using TAndT.UI.Forms;
Imports TAndT.UI.Forms

permettendoci di ridurre il codice della FrmMain a questo:

    private void configuraToolStripMenuItem_Click(object sender, EventArgs e)
    {
      FrmConfig.OpenMe(this.Icon, this);
    }
  Private Sub ConfiguraToolStripMenuItem_Click(ByVal sender As System.Object, _
                                               ByVal e As System.EventArgs) _
                                               Handles ConfiguraToolStripMenuItem.Click
    FrmConfig.OpenMe(Me.Icon, Me)
  End Sub

Da ora in avanti, tutte le form che implementeremo avranno al loro interno il metodo OpenMe per la propria gestione.

Per il momento abbiamo fatto una sufficiente quantità di confusione, pertanto passiamo a fare cose più serie.

La classe FileSpecification
Dopo aver implementato le tre mini classi contenenti le dimensioni, implementiamo il contenitore di queste classi, ovvero la classe che ci permetterà di dare a SQL Server la specifica per ognuno dei file che comporranno un database, la classe FileSpecification.
E' una classe che contiene dati, una classe che potremmo voler salvare su disco, quindi risponde alle caratteristiche peculiari dell'interfaccia IEntity, però, essendo una classe specifica per SQL Server, la metteremo nella libreria TAndT.Data.
Aggiungiamo quindi alla libreria TAndT.Data, nella cartella Entities, la classe FileSpecification.
Fatto salvo per i diversi campi, questa classe non è diversa dalla classe TAndTSetting, pertanto in questo articolo inseriremo solo le cose peculiari, tralasciando i metodi già visti e spiegati nella Parte 2.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Xml.Serialization;
using TAndT.Base;
using TAndT.Base.Entities;
using TAndT.Base.Xml;

namespace TAndT.Data.Entities
{

  [Serializable, XmlRoot(Namespace = "http://www.visual-basic.it")]
  public class FileSpecification : IEntity
  {

    public const string FLD_FileName = "FileName";
    public const string FLD_Growth = "Growth";
    public const string FLD_MaxSize = "MaxSize";
    public const string FLD_Name = "Name";
    public const string FLD_Size = "Size";
 
    private const string FMP_ToSqlFileName = "FILENAME '{0}', ";
    private const string FMP_ToSqlGrowth = "GROWTH = {0} )";
    private const string FMP_ToSqlMaxSize = "MAXSIZE = {0}, ";
    private const string FMP_ToSqlName = "( NAME = {0}, ";
    private const string FMP_ToSqlSize = "SIZE = {0}, ";
    private const string FMP_ToStringFileName = "Path: {0}";
    private const string FMP_ToStringGrowth = "Valore di crescita automatica: {0}";
    private const string FMP_ToStringMaxSize = "Dimensione massima ammessa: {0}";
    private const string FMP_ToStringName = "Specifica file {0}";
    private const string FMP_ToStringSize = "Dimensione iniziale: {0}";
Imports System.ComponentModel
Imports System.Text
Imports System.Xml.Serialization
Imports TAndT.Base
Imports TAndT.Base.Entities
Imports TAndT.Base.Xml

Namespace Entities
  <Serializable(), XmlRoot(Namespace:="http://www.visual-basic.it")> _
  Public Class FileSpecification
    Implements IEntity

    Public Const FLD_FileName As String = "FileName"
    Public Const FLD_Growth As String = "Growth"
    Public Const FLD_MaxSize As String = "MaxSize"
    Public Const FLD_Name As String = "Name"
    Public Const FLD_Size As String = "Size"

    Private Const FMP_ToSqlFileName As String = "FILENAME '{0}', "
    Private Const FMP_ToSqlGrowth As String = "GROWTH = {0} )"
    Private Const FMP_ToSqlMaxSize As String = "MAXSIZE = {0}, "
    Private Const FMP_ToSqlName As String = "( NAME = {0}, "
    Private Const FMP_ToSqlSize As String = "SIZE = {0}, "
    Private Const FMP_ToStringFileName As String = "Path: {0}"
    Private Const FMP_ToStringGrowth As String = "Valore di crescita automatica: {0}"
    Private Const FMP_ToStringMaxSize As String = "Dimensione massima ammessa: {0}"
    Private Const FMP_ToStringName As String = "Specifica file {0}"
    Private Const FMP_ToStringSize As String = "Dimensione iniziale: {0}"

La struttura base della classe, e le costanti. Abbiamo indicato l'attributo Serializable, e l'attributo XmlRoot con il namespace per rendere univoca la serializzazione della classe.
La classe implementa l'interfaccia IEntity, quindi ci saranno proprietà e metodi ad essa legati.

Oltre alla solita variabile per le eccezioni con il nome della classe, abbiamo predisposto una serie di costanti con i nomi delle proprietà, con prefisso FLD e una serie di Format pattern (FMP) che ci forniscono il necessario per formattare l'output del metodo ToString personalizzato e del metodo ToSqlString per la generazione del comando SQL di cui questa classe è parte.

    private string mFileName;
    private DbFileGrowth mGrowth;
    private DbFileMaxSize mMaxSize;
    private string mName;
    private DbFileSize mSize;
    Private mFileName As String
    Private mGrowth As DbFileGrowth
    Private mMaxSize As DbFileMaxSize
    Private mName As String
    Private mSize As DbFileSize

I campi della classe, che rappresentano i parametri di una FileSpecification per il comando create database, quindi nome logico, nome del file, dimensione iniziale, dimensione massima e crescita automatica.
Omettiamo di mostrare le proprietà corrispondenti, passando direttamente ai costruttori:

    public FileSpecification(string pName, string pFileName)
    {
      this.Name = pName;
      this.FileName = pFileName;
      InitFileSize();
    }

    public FileSpecification(string pName, string pFileName, DbFileSize pSize,
                             DbFileMaxSize pMaxSize, DbFileGrowth pGrowth)
    {
      this.Name = pName;
      this.FileName = pFileName;
      this.Size = pSize;
      this.MaxSize = pMaxSize;
      this.Growth = pGrowth;
    }

    private FileSpecification()
    {
      this.Name = string.Empty;
      this.FileName = string.Empty;
      InitFileSize();
    }
    Private Sub New()
      Me.Name = String.Empty
      Me.FileName = String.Empty
      InitFileSize()
    End Sub

    Public Sub New(ByVal name As String, ByVal fileName As String)
      Me.Name = name
      Me.FileName = fileName
      InitFileSize()
    End Sub

    Public Sub New(ByVal name As String, ByVal fileName As String, ByVal size As DbFileSize, _
                   ByVal maxSize As DbFileMaxSize, ByVal growth As DbFileGrowth)
      Me.Name = name
      Me.FileName = fileName
      Me.Size = size
      Me.MaxSize = maxSize
      Me.Growth = growth
    End Sub

I costruttori della classe sono un po' particolari, infatti ce ne sono tre, il costruttore di base senza parametro, richiesto dal fatto che la classe è serializzabile, inizializza i campi della classe con i valori di default ed è privato. Ricordiamo che le dimensioni di base sono state definite nelle classi di ciascuna delle dimensioni oppure, ma potrebbero essere inserite sul file di configurazione applicazione.
Il secondo ed il terzo costruttore invece sono parametrici e pubblici. In questo modo, obblighiamo chi utilizza la classe a specificare almeno i parametri necessari e sufficienti a costruire una classe valida.

    public int CompareTo(object obj)
    {
      int ret = -1;
      try
      {
        if (obj is FileSpecification)
        {
          FileSpecification val = (FileSpecification)obj;
          int comparator = CompareHelper.StringComparer(this.Name, val.Name);
          if (comparator == 0)
          {
            comparator = CompareHelper.StringComparer(this.FileName, val.FileName);
            if (comparator == 0)
            {
              comparator = this.Size.CompareTo(val.Size);
              if (comparator == 0)
              {
                comparator = this.MaxSize.CompareTo(val.MaxSize);
                if (comparator == 0)
                {
                  comparator = this.Growth.CompareTo(val.Growth);
                }
              }
            }
          }
          ret = comparator;
        }
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
      return (ret);
    }
    Public Function CompareTo(ByVal obj As Object) As Integer _
                                           Implements System.IComparable.CompareTo
      Dim ret As Integer = -1
      Try
        If TypeOf obj Is FileSpecification Then
          Dim val As FileSpecification = DirectCast(obj, FileSpecification)
          Dim comparator As Integer = CompareHelper.StringComparer(Me.Name, val.Name)
          If comparator = 0 Then
            comparator = CompareHelper.StringComparer(Me.FileName, val.FileName)
            If comparator = 0 Then
              comparator = Me.Size.CompareTo(val.Size)
              If comparator = 0 Then
                comparator = Me.MaxSize.CompareTo(val.MaxSize)
                If comparator = 0 Then
                  comparator = Me.Growth.CompareTo(val.Growth)
                End If
              End If
            End If
          End If
          ret = comparator
        End If
      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try
      Return ret
    End Function

il metodo di comparazione compara tutti i componenti della nostra classe FileSpecification, pertanto due FileSpecification sono uguali quando tutti i loro componenti sono uguali.

    public bool IsValid()
    {
      try
      {
        return (!StringHelper.IsNullOrTrimEmpty(this.Name) &&
          !StringHelper.IsNullOrTrimEmpty(this.FileName) &&
          Size.IsValid() && MaxSize.IsValid() && Growth.IsValid());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
    Public Function IsValid() As Boolean Implements IEntity.IsValid
      Try
        Return Not StringHelper.IsNullOrTrimEmpty(Me.Name) AndAlso _
               Not StringHelper.IsNullOrTrimEmpty(Me.FileName) AndAlso _
               Size.IsValid AndAlso MaxSize.IsValid AndAlso Growth.IsValid

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try
    End Function

Il metodo di validazione utilizza il metodo helper che abbiamo creato all'inizio di questo articolo per la validazione dei campi stringa, e usa le funzioni di validazione specifiche per le classi dimensione discusse nell'articolo precedente.

    public override string ToString()
    {
      try
      {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat(FMP_ToStringName, this.Name);
        sb.AppendLine();
        sb.AppendFormat(FMP_ToStringFileName, this.FileName);
        sb.AppendLine();
        sb.AppendFormat(FMP_ToStringSize, this.Size);
        sb.AppendLine();
        sb.AppendFormat(FMP_ToStringMaxSize, this.MaxSize);
        sb.AppendLine();
        sb.AppendFormat(FMP_ToStringGrowth, this.Growth);
        return (sb.ToString());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
      }
    }
    Public Overrides Function ToString() As String
      Try
        Dim sb As New StringBuilder
        sb.AppendFormat(FMP_ToStringName, Me.Name)
        sb.AppendLine()
        sb.AppendFormat(FMP_ToStringFileName, Me.FileName)
        sb.AppendLine()
        sb.AppendFormat(FMP_ToStringSize, Me.Size)
        sb.AppendLine()
        sb.AppendFormat(FMP_ToStringMaxSize, Me.MaxSize)
        sb.AppendLine()
        sb.AppendFormat(FMP_ToStringGrowth, Me.Growth)
        Return sb.ToString

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                  + System.Reflection.MethodBase.GetCurrentMethod.Name _
                  + ": " + ex.Message, ex)
      End Try
    End Function

L'override del metodo ToString, che ci permette di visualizzare in modo presentabile il contenuto della classe.

    public string ToSqlString()
    {
      try
      {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat(FMP_ToSqlName, this.Name);
        sb.AppendLine();
        sb.AppendFormat(FMP_ToSqlFileName, this.FileName);
        sb.AppendLine();
        sb.AppendFormat(FMP_ToSqlSize, this.Size);
        sb.AppendLine();
        sb.AppendFormat(FMP_ToSqlMaxSize, this.MaxSize);
        sb.AppendLine();
        sb.AppendFormat(FMP_ToSqlGrowth, this.Growth);
        return (sb.ToString());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
      }
    }
    Public Function ToSqlString()
      Try
        Dim sb As New StringBuilder
        sb.AppendFormat(FMP_ToSqlName, Me.Name)
        sb.AppendLine()
        sb.AppendFormat(FMP_ToSqlFileName, Me.FileName)
        sb.AppendLine()
        sb.AppendFormat(FMP_ToSqlSize, Me.Size)
        sb.AppendLine()
        sb.AppendFormat(FMP_ToSqlMaxSize, Me.MaxSize)
        sb.AppendLine()
        sb.AppendFormat(FMP_ToSqlGrowth, Me.Growth)
        Return sb.ToString()

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try

    End Function

il metodo che genera la stringa SQL per la specifica file, da utilizzare per la composizione del Create Database.

La classe FileSpecifications
Aggiungiamo quindi alla libreria TAndT.Data una cartella Collections, nella quale creiamo la classe FileSpecifications.
Questa collezione non presenta nulla di nuovo rispetto alle altre già presentate, pertanto ne mostrerò solo una minima parte, ovvero ciò che c'è di peculiare:

    public bool IsValid()
    {
      try
      {
        bool countValid = false;
        bool elementValids = false;
        if (this.Count > 0)
        {
          countValid = true;
          int validItems = 0;
          foreach (FileSpecification item in this)
          {
            if (item.IsValid()) validItems++;
          }
          if (this.Count == validItems)
          {
            elementValids = true;
          }
        }
        return (countValid && elementValids);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
    Public Function IsValid() As Boolean
      Try
        Dim countValid As Boolean = False
        Dim elementValids As Boolean = False
        If Me.Count > 0 Then
          countValid = True
          Dim validItems = 0
          For Each item As FileSpecification In Me
            If item.IsValid Then validItems += 1
          Next
          If Me.Count = validItems Then
            elementValids = True
          End If
        End If
        Return countValid AndAlso elementValids

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try

    End Function

La validazione della collezione, che controlla che ci sia almeno un elemento e che ogni elemento che contiene sia valido.

    public string ToSqlString()
    {
      try
      {
        StringBuilder sb = new StringBuilder();
        bool primo = true;
        foreach (FileSpecification item in this)
        {
          if (!primo)
          {
            sb.AppendLine(", ");
          }
          sb.AppendLine(item.ToSqlString());
          primo = false;
        }
        return (sb.ToString());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
    Public Function ToSqlString() As String
      Try
        Dim sb As New StringBuilder
        Dim primo As Boolean = True
        For Each item As FileSpecification In Me
          If Not primo Then
            sb.AppendLine(", ")
          End If
          sb.AppendLine(item.ToSqlString)
          primo = False
        Next
        Return sb.ToString

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try

    End Function

La generazione della stringa SQL per la collezione, ottenuta concatenando le stringhe SQL degli elementi, separate da virgole.

La classe DataFileGroup
Il filegroup di un database è un gruppo logico composto da uno o più file che contengono una parte delle tabelle (o tutte) di un database, siano esse tabelle di sistema o tabelle utente. Ogni database deve averne almeno uno, il cui nome è PRIMARY.
Anche questo oggetto, come la specifica di file, è un entità, che contiene al suo interno altri oggetti e, così come la FileSpecification conteneva gli oggetti DbFileSize, DBFileMaxSize, DbFileGrowth, così il DataFileGroup contiene anche una collection di tipo FileSpecification.
Se vi sembra di star giocando con le Matrioske oppure con le scatole cinesi, ebbene: benvenuti in OOP!

Anche questa classe, da creare nella cartella TAndT.Data.Entities, è simile a quelle già presentate, pertanto ci limiteremo a spiegare le differenze:

    private FileSpecifications mFileSpecs;
    private bool mIsPrimary;
    private string mName;
    Private mFileSpecs As FileSpecifications
    Private mIsPrimary As Boolean
    Private mName As String

I campi della nostra classe: la lista dei file contenuti nel filegroup, un flag per indicarci che questo è il PRIMARY filegroup, il nome del filegroup. Ciascuno di questi campi è esposto con una proprietà pubblica uguale a tutte le property delle entità già spiegate, quindi le omettiamo.

    private DataFileGroup()
    {
      this.mName = string.Empty;
      this.mFileSpecs = new FileSpecifications();
    }

    public DataFileGroup(string pName)
    {
      this.mName = pName;
      this.mFileSpecs = new FileSpecifications();
    }
    Private Sub New()
      Me.mName = String.Empty
      Me.mFileSpecs = New FileSpecifications
    End Sub

    Public Sub New(ByVal name As String)
      Me.mName = name
      Me.mFileSpecs = New FileSpecifications
    End Sub

I costruttori; anche in questo caso ne abbiamo uno privato per la serializzazione e l'altro pubblico e parametrico perché è necessario che il filegroup abbia un nome.

    public int CompareTo(object obj)
    {
      int ret = -1;
      try
      {
        if (obj is DataFileGroup)
        {
          DataFileGroup val = (DataFileGroup)obj;
          int comparator = CompareHelper.StringComparer(this.Name, val.Name);
          if (comparator == 0)
          {
            comparator = this.FileSpecs.CompareTo(val.FileSpecs);
            if (comparator == 0)
            {
              comparator = this.IsPrimary.CompareTo(val.IsPrimary);
            }
          }
          ret = comparator;
        }
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
      return (ret);
    }
    Public Function CompareTo(ByVal obj As Object) As Integer _
                                           Implements System.IComparable.CompareTo
      Dim ret As Integer = -1
      Try
        If TypeOf obj Is DataFileGroup Then
          Dim val As DataFileGroup = DirectCast(obj, DataFileGroup)
          Dim comparator As Integer = CompareHelper.StringComparer(Me.Name, val.Name)
          If comparator = 0 Then
            comparator = Me.FileSpecs.CompareTo(val.FileSpecs)
            If comparator = 0 Then
              comparator = Me.IsPrimary.CompareTo(val.IsPrimary)
            End If
          End If
          ret = comparator
        End If
      Catch ex As Exception
        Throw New ApplicationException("\'a0" + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try
      Return ret
    End Function

il metodo di comparazione, che (come gli altri analoghi) compara fra loro tutti gli elementi dell'entità.

    public bool IsValid()
    {
      try
      {
        return (!StringHelper.IsNullOrTrimEmpty(this.Name) && FileSpecs.IsValid());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
    Public Function IsValid() As Boolean Implements IEntity.IsValid
      Try
        Return Not StringHelper.IsNullOrTrimEmpty(Me.Name) AndAlso FileSpecs.IsValid

      Catch ex As Exception
        Throw New ApplicationException("\'a0" + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try
    End Function

il metodo di validazione, che verifica che ci sia il nome del filegroup e che la collezione di FileSpecification sia a sua volta valida.

    public string ToSqlString()
    {
      try
      {
        if (!this.FileSpecs.IsValid())
        {
          throw new InvalidFileSpecificationsException(
            "La specifica dei file del filegroup non è valida, " +
            "impossibile generare la stringa SQL per il filegroup.");
        }
        StringBuilder sb = new StringBuilder();
        if (this.IsPrimary)
        {
          sb.AppendLine(TXT_ToSqlStringPrimaryFileGroup);
        }
        else
        {
          sb.AppendFormat(FMP_ToSqlStringFilegroupName, this.Name);
          sb.AppendLine();
        }
        sb.Append(this.FileSpecs.ToSqlString());
        return (sb.ToString());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
      }
    }
    Public Function ToSqlString()
      Try
        If Not Me.FileSpecs.IsValid Then
          Throw New InvalidFileSpecificationsException( _
                                     "La specifica dei file del filegroup non è valida, " & _
                                     "impossibile generare la stringa SQL per il filegroup.")
        End If
        Dim sb As New StringBuilder
        If Me.IsPrimary Then
          sb.AppendLine(TXT_ToSqlStringPrimaryFileGroup)
        Else
          sb.AppendFormat(FMP_ToSqlStringFilegroupName, Me.Name)
          sb.AppendLine()
        End If
        sb.Append(Me.FileSpecs.ToString)
        Return sb.ToString()

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try

    End Function

il metodo di generazione della stringa SQL che controlla ed eventualmente genera una eccezione se la classe non è valida, oppure genera la stringa SQL necessaria per il Create Database.

    public override string ToString()
    {
      try
      {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat(FMP_ToStringName, this.Name);
        sb.AppendLine();
        sb.AppendLine(TXT_ToStringFileList);
        sb.AppendLine(this.FileSpecs.ToString());
        return (sb.ToString());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
      }
    }
    Public Overrides Function ToString() As String
      Try
        Dim sb As New StringBuilder
        sb.AppendFormat(FMP_ToStringName, Me.Name)
        sb.AppendLine()
        sb.AppendLine(TXT_ToStringFileList)
        sb.AppendLine(Me.FileSpecs.ToString)
        Return sb.ToString

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                  + System.Reflection.MethodBase.GetCurrentMethod.Name _
                  + ": " + ex.Message, ex)
      End Try
    End Function

il metodo ToString, che genera una stringa in formato adatto alla visualizzazione. Ovviamente, tutte le costanti per le stringhe usate nella classe, sono definite all'interno della classe stessa oppure, se si necessita di gestione multilingua, possono essere riportate nelle risorse del progetto.

La classe DataFileGroups
In un database è possibile definire più di un filegroup, pertanto abbiamo bisogno di una collezione di DataFileGroup, quindi, creiamo una nuova collezione, simile a quella usata per i filegroup.
Anche questa collezione non aggiunge nulla alle precedenti già spiegate, pertanto ci limiteremo a mostrarvi un metodo, il metodo ToSqlString, l'unico peculiare, il quale fa uso di una eccezione personalizzata, pertanto creiamo prima quest'ultima, aggiungendo la classe InvalidFileSpecificationsException alle Entities della TAndT.Data:

namespace TAndT.Data.Entities
{
  public class InvalidFileSpecificationsException : System.ApplicationException
Namespace Entities

  Public Class InvalidFileSpecificationsException
    Inherits System.ApplicationException

Ed ecco il metodo:

    public string ToSqlString()
    {
      try
      {
        StringBuilder sb = new StringBuilder();
        bool primo = true;
        foreach (DataFileGroup item in this)
        {
          if (!primo)
          {
            sb.AppendLine(", ");
          }
          sb.AppendLine(item.ToSqlString());
          primo = false;
        }
        return (sb.ToString());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
    Public Function ToSqlString() As String
      Try
        Dim sb As New StringBuilder
        Dim primo As Boolean = True
        For Each item As DataFileGroup In Me
          If Not primo Then
            sb.AppendLine(", ")
          End If
          sb.AppendLine(item.ToSqlString)
          primo = False
        Next
        Return sb.ToString

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try

    End Function

Come possiamo vedere, anche se peculiare, il metodo non fa nulla di nuovo, se non concatenare il risultato della ToSqlString degli elementi della collezione.

La classe DatabaseGenerator
Nonostante sia la classe più interessante del gruppo di classi necessarie a generare un Database su SQL Server, in realtà non ha nulla di particolarmente interessante al suo interno: infatti, anch'essa è un'Entity (cioè una classe che creiamo in TAndT.Data.Entities) che contiene al suo interno gli oggetti finora costruiti, un altra Matrioska. In altre parole, abbiamo distribuito le cose interessanti negli oggetti già definiti finora.

Questa classe è la più interessante perché contiene i dati necessari alla generazione di un database ed i metodi per farlo.
Per questa puntata, definiremo il necessario a far funzionare l'entità, nella prossima puntata, finalmente, genereremo il database.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Xml.Serialization;
using TAndT.Data.Collections;
using TAndT.Base;
using TAndT.Base.Entities;
using TAndT.Base.Xml;

namespace TAndT.Data.Entities
{
  [Serializable, XmlRoot(Namespace = "http://www.visual-basic.it")]
  public class DatabaseGenerator : IEntity
  {

    private const string FLD_Collation = "Collation";
    private const string FLD_DataFiles = "DataFiles";
    private const string FLD_LogFileSpec = "LogFileSpec";
    private const string FLD_DatabaseName = "DatabaseName";
Imports System.ComponentModel
Imports System.Text
Imports System.Xml.Serialization
Imports TAndT.Base
Imports TAndT.Base.Entities
Imports TAndT.Base.Xml
Imports TAndT.Data.Collections

Namespace Entities
  <Serializable(), XmlRoot(Namespace:="http://www.visual-basic.it")> _
  Public Class DatabaseGenerator
    Implements IEntity

    Public Const FLD_Collation As String = "Collation"
    Public Const FLD_DataFiles As String = "DataFiles"
    Public Const FLD_LogFileSpec As String = "LogFileSpec"
    Public Const FLD_DatabaseName As String = "DatabaseName"

La struttura di base e le costanti con i nomi dei campi della classe.

    string mCollation;
    DataFileGroups mDataFiles;
    FileSpecifications mLogFileSpec;
    string mDatabaseName;
    Private mCollation As String
    Private mDataFiles As DataFileGroups
    Private mLogFileSpec As FileSpecifications
    Private mDatabaseName As String

I campi della classe, che, come possiamo vedere, contiene le collezioni precedentemente definite, dando origine ad un'altra matrioska OOP. Ovviamente ogni campo è esposto tramite la sua property (che omettiamo).

    private DatabaseGenerator()
    {
      DatabaseGeneratorInit();
    }

    public DatabaseGenerator(string pDatabaseName)
    {
      DatabaseGeneratorInit();
      this.DatabaseName = pDatabaseName;
    }
    Private Sub New()
      DatabaseGeneratorInit()
    End Sub

    Public Sub New(ByVal databaseName As String)
      DatabaseGeneratorInit()
      Me.DatabaseName = databaseName
    End Sub

I costruttori: quello privato per la serializzazione, quello pubblico per l'uso, cui abbiamo messo un solo parametro, il nome del database, perché per generare un database, se ben ricordate l'articolo precedente, ci basta quello, in realtà.
Entrambi usano un metodo di inizializzazione:

    private void DatabaseGeneratorInit()
    {
      this.mCollation = string.Empty;
      this.mDataFiles = new DataFileGroups();
      this.mLogFileSpec = new FileSpecifications();
      this.mDatabaseName = string.Empty;
    }
    Private Sub DatabaseGeneratorInit()
      Me.mCollation = String.Empty
      Me.mDataFiles = New DataFileGroups()
      Me.mLogFileSpec = New FileSpecifications()
      Me.mDatabaseName = String.Empty
    End Sub

il metodo di inizializzazione dei campi usato dai due costruttori.

    public int CompareTo(object obj)
    {
      int ret = -1;
      try
      {
        if (obj is DatabaseGenerator)
        {
          DatabaseGenerator val = (DatabaseGenerator)obj;
          int comparator = CompareHelper.StringComparer(this.DatabaseName, val.DatabaseName);
          if (comparator == 0)
          {
            comparator = this.DataFiles.CompareTo(val.DataFiles);
            if (comparator == 0)
            {
              comparator = this.LogFileSpec.CompareTo(val.LogFileSpec);
              if (comparator == 0)
              {
                comparator = CompareHelper.StringComparer(this.Collation, val.Collation);
              }
            }
          }
          ret = comparator;
        }
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
      return (ret);
    }
    Public Function CompareTo(ByVal obj As Object) As Integer _
                                           Implements System.IComparable.CompareTo
      Dim ret As Integer = -1
      Try
        If TypeOf obj Is DatabaseGenerator Then
          Dim val As DatabaseGenerator = DirectCast(obj, DatabaseGenerator)
          Dim comparator As Integer = CompareHelper.StringComparer(Me.DatabaseName, val.DatabaseName)
          If comparator = 0 Then
            comparator = Me.DataFiles.CompareTo(val.DataFiles)
            If comparator = 0 Then
              comparator = Me.LogFileSpec.CompareTo(val.LogFileSpec)
              If comparator = 0 Then
                comparator = CompareHelper.StringComparer(Me.Collation, val.Collation)
              End If
            End If
          End If
          ret = comparator
        End If
      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try
      Return ret
    End Function

Il metodo di comparazione, che utilizza i campi della classe per stabilire se due DatabaseGenerator sono uguali.

    public bool IsValid()
    {
      try
      {
        return (!StringHelper.IsNullOrTrimEmpty(this.DatabaseName));
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
    Public Function IsValid() As Boolean Implements IEntity.IsValid
      Try
        Return Not StringHelper.IsNullOrTrimEmpty(Me.DatabaseName)

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                       + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try
    End Function

Il metodo di validazione; anche in questo caso, basta che ci sia il nome database, il resto è opzionale.

    public override string ToString()
    {
      try
      {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine("Dati generazione database:");
        sb.AppendFormat("Nome: {0}", this.DatabaseName);
        sb.AppendLine();
        sb.AppendFormat("Collation: {0}", this.Collation);
        sb.AppendLine(this.DataFiles.ToString());
        sb.AppendLine(this.LogFileSpec.ToString());
        return (sb.ToString());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
      }
    }
    Public Overrides Function ToString() As String
      Try
        Dim sb As New StringBuilder
        sb.AppendLine("Dati generazione database:")
        sb.AppendFormat("Nome: {0}", Me.DatabaseName)
        sb.AppendLine()
        sb.AppendFormat("Collation: {0}", Me.Collation)
        sb.AppendLine(Me.DataFiles.ToString())
        sb.AppendLine(Me.LogFileSpec.ToString())
        Return sb.ToString

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                  + System.Reflection.MethodBase.GetCurrentMethod.Name _
                  + ": " + ex.Message, ex)
      End Try
    End Function

Il metodo ToString, che compone in modo adatto alla visualizzazione i contenuti dei campi della classe.

    public string ToSqlString()
    {
      try
      {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine("CREATE DATABASE ");
        sb.Append("\t");
        sb.AppendLine(this.DatabaseName);
        if (this.DataFiles.IsValid())
        {
          sb.AppendLine("\tON");
          sb.AppendLine(this.DataFiles.ToSqlString());
        }
        if (this.LogFileSpec.IsValid())
        {
          sb.AppendLine("\tLOG ON");
          sb.AppendLine(this.LogFileSpec.ToSqlString());
        }
        if (!StringHelper.IsNullOrTrimEmpty(this.Collation))
        {
          sb.AppendLine("\t");
          sb.AppendFormat("COLLATE {0}", this.Collation);
          sb.AppendLine();
        }
        return (sb.ToString());
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
    Public Function ToSqlString() As String
      Dim tab As Char = Convert.ToChar(9)
      Try
        Dim sb As New StringBuilder
        sb.AppendLine("CREATE DATABASE ")
        sb.Append(tab)
        sb.AppendLine(Me.DatabaseName)
        If Me.DataFiles.IsValid Then
          sb.AppendLine(Me.DataFiles.ToSqlString())
        End If
        If Me.LogFileSpec.IsValid Then
          sb.Append(tab)
          sb.AppendLine("LOG ON")
          sb.AppendLine(Me.LogFileSpec.ToSqlString())
        End If
        If Not StringHelper.IsNullOrTrimEmpty(Me.Collation) Then
          sb.AppendLine(tab)
          sb.AppendFormat("COLLATE {0}", Me.Collation)
          sb.AppendLine()
        End If
        Return sb.ToString()

      Catch ex As Exception
        Throw New ApplicationException(" " + mClassName + "." _
                                           + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
      End Try

    End Function

Il metodo ToSqlString, l'unico interessante, che compone la nostra stringa SQL di Create Database, utilizzando i campi del DatabaseGenerator e le funzioni di validazione per decidere se includere oppure no un elemento della classe. In questo modo, la stringa viene composta dinamicamente con i dati che chi ha deciso di generare il database è in grado di fornire.

Arrivederci alla prossima puntata
Per questa puntata ci fermiamo qui. Questa è una puntata lunga e noiosa, lo ammettiamo, ma nella prossima vedremo l'uso pratico delle classi finora sviluppate e ci sarà probabilmente un po' più di movimento e divertimento per le cose nuove.

Se vogliamo parlare di Filosofia del lavoro del programmatore, possiamo considerare come vi siano momenti stimolanti, quando si deve definire un nuovo tipo di oggetto e le sue peculiarità, e momenti necessari ma noiosi, quando bisogna implementare molti oggetti correlati fra loro ma tutti di tipi noti, come le Matrioske di questo articolo.

Come al solito, il codice fin qui prodotto è scaricabile dall'Area Download.
(nota: il codice è 'sempre' aggiornato, con eventuali correzioni al codice precedente, non citate nell'articolo).
Potete scrivere Feedback (commenti, critiche, suggerimenti, correzioni) sul blog di Sabrina.