Archive for Juni 27th, 2008

ASP.NET: Schichtentrennung – Implementierung

Das Schicke an der Schichtentrennung und den damit verbundenen Konzepten, ist, dass die Implementierung später gegen eine andere Implementierung ausgetauscht werden kann. Genau diesen Ansatz macht man sich zu Nutze, wenn man schnell Ergebnisse erzielen möchte, indem man zunächst eine einfache Implementierung erstellt und eben erst später die echte, große, datenbankgestützte Version bereit stellt.

Das vorausgeschickt, ist die folgende Implementierung nur als ein erster Ansatz zu sehen, den Sie durch eine Version ersetzen können, der Ihren eigenen Anforderungen besser entspricht. Die hier vorgestellte Implementierung nutzt nur den Speicher, um die angelegten Kunden vorzuhalten. Technisch macht sie nix anderes, als eine Liste von Kunden im Speicher zu halten. Einfach, aber für einen Test und einen ersten Prototypen sicherlich ausreichen.

Wichtig: Diese Komponente liegt in einem eigenen Projekt mit dem Namen MemoryCustomerManager (genau so wird der Name der Assemblierung heißen).

Der Code ist ziemlich selbsterklärend – hier werden schließlich nur Operationen auf einer Liste von Kunden vorgenommen. Damit alles später zusammen funktioniert, muss diese Implementierung somit lediglich von der Basisklasse CustomerManager erben und die benötigten Methoden implementieren:

using System;
using System.Collections.Generic;
using System.Text;
using BusinessLayer;

namespace MemoryCustomerManager
{
   ///

   /// Implementation of the business layer
   ///

   public class MemoryCustomerManager : CustomerManager
   {

      private static List _customers =
         new List
();

      ///

      /// List of customers
      ///

           
      private static List CustomersList
      {
         get { return _customers; }
      }  

      ///

      /// Returns all customers
      ///

      public override List GetAllCustomers()
      {
         // Sort
         Sort(CustomersList);

         // Return the customers
         return CustomersList;
      }

      ///

      /// Returns a specific customer
      ///

      public override Customer GetCustomer(Guid id)
      {
         // Check all customers
         foreach (Customer cust in CustomersList)
         {
            // Compare the id
            if (cust.Id.Equals(id))
            {
               // Found it!
               return cust;
            }
         }

         // Found nothing
         return null;
      }

      ///

      /// Finds all customers by their names
      ///

      public override List
         FindCustomersByName(string name)
      {
         List
customers =
            new List
();
         string nameLower = name.ToLower();

         // Check every customer
         foreach (Customer cust in CustomersList)
         {
            if (cust.LastName.ToLower()
               .Equals(nameLower))
            {
               customers.Add(cust);
            }
         }

         // Sort the customers
         Sort(customers);

         // Done
         return customers;
      }

      ///

      /// Finds all customers by their email-addresses
      ///

      public override List
         FindCustomersByEMail(string email)
      {
         List
customers =
            new List
();
         string emailLower = email.ToLower();

         // Check every customer
         foreach (Customer cust in CustomersList)
         {
            if (cust.EMail.ToLower().Equals(emailLower))
            {
               customers.Add(cust);
            }
         }

         // Sort the list
         Sort(customers);

         // Return the customers
         return customers;
      }

      ///

      /// Updates a customer
      ///

      public override Customer
         UpdateCustomer(Customer customer)
      {
         // Delete an existing customer
         DeleteCustomer(customer);

         // Add the customer
         CustomersList.Add(customer);

         return customer;
      }

      ///

      /// Deletes a customer
      ///

      public override bool
         DeleteCustomer(Customer customer)
      {
         // Check, whether the customer
         // exists in the list
         Customer existing = null;
         foreach (Customer cust in CustomersList)
         {
            if (cust.Id.Equals(customer.Id))
            {
               existing = cust;
               break;
            }
         }

         // Replace the old customer
         if (null != existing)
         {
            CustomersList.Remove(existing);
            return true;
         }

         return false;
      }

      ///

      /// Sort the customers
      ///

      private void Sort(List customers)
      {
         customers.Sort(new CustomerSorter());
      }
   }
}

Innerhalb der Klasse wird Bezug auf eine Klasse CustomerSorter genommen. Diese hat die Aufgabe, die Liste der Kunden stets alphabetisch sortiert zu halten. Wir implementieren dies mit Hilfe des Interfaces IComparer. Dabei ist lediglich die Methode Compare() zu überschreiben – und diese Überschreibung ist ziemlich simpel, denn tatsächlich geschieht nix anderes, als die Nachnamen der Kunden über deren Funktionalitäten miteinander zu vergleichen und das numerische Ergebnis dieses Vergleiches zurück zu geben. Dabei kann es zu folgenden Rückgaben kommen:

  • Zahl größer als 0: Der Wert der Instanz, auf der verglichen worden ist, ist größer.
  • 0: Beide Werte sind gleich.
  • Zahl kleiner als 0: Der Wert der Instanz, mit der verglichen worden ist, ist größer.

Dementsprechend kann das natürlich auch umgedreht werden, wenn man am Ende des Tages eine absteigende Sortierung wünscht. Wenn die Nachnamen gleich sind, werden die Vornamen miteinander verglichen. Sind auch die gleich, kommen die E-Mail-Adressen an die Reihe.

Lange Rede, kurzer Sinn: Dies ist der Code der CustomerSorter-Klasse:

using System;
using System.Collections.Generic;
using System.Text;
using BusinessLayer;

namespace MemoryCustomerManager
{
   public class CustomerSorter : IComparer
   {
      ///

      /// Compares two customers
      ///

      public int Compare(Customer x, Customer y)
      {
         int result = x.LastName.CompareTo(y.LastName);
        
         // Compare the first names when neccessary
         if (result == 0)
         {
            result = x.FirstName.CompareTo(y.FirstName);
         }

         // Compare the emails when neccessary
         if (result == 0)
         {
            result = x.EMail.CompareTo(y.EMail);
         }

         return result;
      }
   }
}

Damit ist die Implementierung komplett. Im nächsten Teil unserer Serie widmen wir uns dann der Nutzung dieser ganzen Komponenten im Web-Umfeld.

Teil 1. Teil 2. Teil 3.

ASP.NET: Schichtentrennung – Fassade

Schichtentrennung lebt vom Verbergen der Implementierung und davon, möglichst wenig an Informationen über das Erzeugen von Instanzen oder Abhängigkeiten nach draußen gelangen zu lassen. Das Grundprinzip sollte stets sein, so wenig wie möglich fest miteinander zu koppeln.

Aus diesem Grund bedienen wir uns einer Fassadenklasse, die das Instanzieren und Benutzen der Implementierung unserer Basisklasse verbirgt. Somit haben wir eine dedizierte Abgrenzung zur Frontendschicht geschaffen, was es uns im weiteren Verlauf des Lebenszyklus eines Projektes erleichtern würde, auch weitreichendere Änderungen zu implementieren (und sei es, die komplette Basisklasse gegen irgend eine andere Implementierung auszutauschen). Also, Fassade davor, und schon kann man auch mal was ändern, ohne das es weh tun muss.

So sieht die Fassadenklasse aus:

using System;
using System.Collections.Generic;
using System.Text;

using de.ksamaschke.Tools;


namespace BusinessLayer
{
   ///

   /// API for handling customers
   ///

   public class Customers
   {

      private static CustomerManager _manager;

      ///

      /// Reference to the
      /// CustomerManager-implementation
      ///

           
      private static CustomerManager Manager
      {
         get
         {
            // Get the manager
            if (null == _manager)
            {
               try
               {
                  _manager = ManagerLoader
                     .Load();
               }
               catch
               {
                  throw;
               }
            }

            return _manager;
         }
      }

      ///

      /// Returns a list of all customers
      ///

      public static List GetAllCustomers()
      {
         return Manager.GetAllCustomers();
      }

      ///

      /// Returns a specific customer
      ///

      public static Customer GetCustomer(Guid id)
      {
         return Manager.GetCustomer(id);
      }

      ///

      /// Returns a list of customers
      /// identified by their names
      ///

      public static List
         FindCustomersByName(string name)
      {
         return Manager.FindCustomersByName(name);
      }

      ///

      /// Returns a list of customers identified
      /// by their email-addresses
      ///

      public static List
         FindCustomersByEMail(string email)
      {
         return Manager.FindCustomersByEMail(email);
      }

      ///

      /// Updates a customer
      ///

      public static Customer
         UpdateCustomer(Customer customer)
      {
         return Manager.UpdateCustomer(customer);
      }

      ///

      /// Deletes a customer
      ///

      public static bool
         DeleteCustomer(Customer customer)
      {
         return Manager.DeleteCustomer(customer);
      }
   }
}

Diese Fassade ist absichtlich sehr übersichtlich gehalten. Im Wesentlichen stellt sie nur die gleichen Funktionalitäten wie die intern verwendete CustomerManager-Klasse dar. Die konkret verwendete Instanz wird über die Eigenschaft Manager abgerufen, wo sie in Form eines Singletons gehalten wird – somit gibt es nur eine Instanz und die Instanz muss nicht bei jedem Zugriff neu erzeugt werden.

Im nächsten Teil widmen wir uns der konkreten Implementierung einer CustomerManager-Ableitung.

Hier geht es zu Teil 1 und Teil 2.