V tomto článku se budeme zabývat návrhovým vzorem zvaným „abstract factory“ (abstraktní továrna). Vzor je použit pro flexibilní výběr typu databázového serveru, se kterým aplikace pracuje. Při návrhu systémů bývá téměř pravidlem, že se jednotlivé návrhové vzory doplňují, a ani v tomto příkladu tomu není jinak. Abstraktní továrnu zde doplňuje známý a často používaný vzor „singleton“ (jedináček).

Řešené zadání

Zadání pro ukázkový příklad zní: „Vytvořit databázovou aplikaci, která bude moci pro ukládání dat používat různé databázové servery.“ Základní implementace bude provedena pro servery MS SQL a Oracle, nicméně při požadavku na přenesení na jiný databázový stroj má být toto co nejsnazší a bez nutnosti rekompilace nebo dokonce přepisování aplikace.

V této ukázce se také nebudeme zabývat řešením střední a prezentační vrstvy. Klient navrženého řešení bude jednoduchá WinForm aplikace, na které bude předveden způsob použití výsledného navrženého řešení.

Návrh řešení

Rozdělení aplikace na vrstvy

Mnoho soudobých databázových aplikací používá vícevrstvý návrh. Asi nejznámější vícevrstvou architekturou je architektura třívrstvá, ve které jsou odděleny vrstvy prezentační, vrstva aplikační logiky (někdy nazývaná jen střední vrstva) a datová vrstva. Výhody plynoucí z rozdělení aplikací na více vrstev je několik, hlavní pro použití v našem návrhu je možnost výměny implementace některé vrstvy bez ovlivnění zbývajících částí aplikace.

Příklad možného rozvrstvení aplikace je uveden na následujícím obrázku:

Abstract Factory - příklad na možné rozvrstvení aplikace

Aplikace nemusí vždy striktně dodržovat princip rozdělení na tři vrstvy, v některých případech lze každou vrstvu ještě dále rozčlenit na další vrstvy, případně na oddělené sady komponent. V rámci datové vrstvy aplikace bývá zvykem vyčlenit takzvanou vrstvu logiky přístupu k datům (Data Access Logic, DAL). Třídy v této vrstvě se zkráceně nazývají DALC (Data Access Logic Components). Tuto vrstvu potřebujeme implementovat tak, aby bylo možné změnou konfigurace aplikace určit, jaký typ databázového serveru bude aplikace používat.

Základní popis vzoru Abstract Factory

Návrhový vzor abstract factory je takzvaně tvořivý objektový vzor. Z tohoto zařazení vyplývá, že je určen pro vytváření instancí a že pro změnu procesu tvorby se používá změna instancí továrny.

Hlavním důvodem použití abstract factory pro naše řešení je ten, že vzor umožňuje konfigurovat aplikaci pro použití sady tříd, které jsou spolu svázány nějakou společnou vlastností (jsou nějakým způsobem příbuzné). V našem případě je touto sadou tříd již zmíněná sada DAL komponent a společnou vlastností, která je předurčuje ke společnému použití, je typ databázového serveru. Abstract factory navíc ve svém důsledku přímo podporuje snadnou výměnu této sady tříd pouhou výměnou instance použité továrny.

Součásti vzoru jsou:

Abstraktní továrna
Popisuje rozhraní pro vytváření produktů.
Konkrétní továrny
Implementují rozhraní a vytvářejí konkrétní produkty.
Abstraktní produkty
Popis rozhraní produktů vytvářených abstraktní továrnou.
Konkrétní produkty
Konkrétní implementace těchto produktů, tyto implementace vytváří příslušná konkrétní továrna.
Klient
Třída používající produkty továrny.

V našem případě je abstraktní továrnou třída DatabaseFactory, konkrétními továrnami poté třídy MsSqlFactory a OracleFactory.

Abstraktními produkty jsou zde rozhraní komponent logiky pro přístup k datům – pro příklad zde používáme rozhraní pro přístup k objednávkám IOrderDALC a pro přístup k produktům IProductDALC. Tyto jsou implementovány komponentami MsSqlOrderDALC a MsSqlProductDALC, případně OracleOrderDALC a OracleProductDALC.

Struktura je znázorněna na následujícím třídním modelu:

Abstract Factory - třídní model řešení
Abstract Factory – třídní model řešení (plná velikost, cca 10 kB)

Další informace o tomto vzoru je možné nalézt primárně on-line například na Design Patterns: Abstract Factory.

Způsob použití

Jakmile klient (obvykle třída střední vrstvy) potřebuje načítat nebo ukládat data do databáze, musí k tomu získat instanci DAL komponenty. Komponentu získává voláním příslušné metody továrny. Z toho je zřejmé, že pro naši implementaci je nutné zajistit načtení konfiguračních údajů určujících použitý typ konkrétní továrny a následné vytvoření instance této konkrétní továrny.

Konfiguraci a vytvoření instance továrny již samotný vzor neřeší. V příkladu proto k tomu využijeme návrhový vzor singleton, který nám umožní odstínit klienta od vytvoření instance továrny (za výběr nakonfigurované instance by neměl být zodpovědný klient). V konfiguraci aplikace uvedeme název typu použité třídy a pomocí techniky reflexe z tohoto typu vytvoříme instanci továrny.

Vytvoření instance továrny

Pro čtení konfigurace máme navrženu třídu Configuration, která implementuje rozhraní IConfigurationSectionHandler (public class Configuration : IConfigurationSectionHandler). Třída Configuration načte název typu třídy konkrétní továrny a connection string…

private string _type;
private string _connectionString;
public object Create(object parent, object configContext, System.Xml.XmlNode section)
{
  _type = section.Attributes[„type“].Value;
  _connectionString = section.Attributes[„connectionString“].Value;
  if (_type == null || _type.Length == 0)
    throw new ConfigurationException(„Není nakonfigurován typ továrny.“);
  if (_connectionString == null || _connectionString.Length == 0)
    throw new ConfigurationException(„Není nakonfigurován připojovací řetězec.“);
  return this;
}

…a zpřístupní je pomocí svých vlastností:

public string Type
{
  get
  {
    if (_type != null)
      return _type;
    else
      return string.Empty;
  }
}
public string ConnectionString
{
  get
  {
    if (_connectionString != null)
      return _connectionString;
    else
      return string.Empty;
  }
}

Vytvoření instance je poté zapouzdřeno v metodě GetFactory(), přičemž ke čtení konfigurace a vytváření instance dochází díky použití vzoru singleton pouze při prvním volání této metody. Implementace jedináčka v abstraktní továrně:

private static volatile DatabaseFactory _instance;
private static object _lock = new object();
public static DatabaseFactory GetFactory()
{
  if (_instance == null)
  {
    lock (_lock)
    {
      if (_instance == null)
      {
        Configuration configuration = GetConfiguration();
        try
        {
          _instance = Activator.CreateInstance(Type.GetType(configuration.Type), true) as DatabaseFactory;
        }
        catch (Exception exc)
        {
          throw new ApplicationException(„Chyba při vytváření instance továrny.“, exc);
        }
      }
    }
  }
  return _instance;
}

Pro vytváření instance z nakonfigurovaného typu je využita třída Activator a její metoda CreateInstance. Mimochodem, z hlediska vícevláknového programování je vytvoření sdílené instance kritickou sekcí, musíme se tedy postarat o zajištění synchronizace vláken. V příkladu je použit princip „double-checked locking“, který je blíže popisován společně s implementací jedináčka například v článku Implementing Singleton in C#.

Kompletní příklad řešení je vám k dispozici.

Konfigurace aplikace

Konfigurační sekce pro DAL vrstvu se skládá z jediného elementu:

<DAL
  type=“Samples.AbstractFactory.MsSqlDAL.MsSqlFactory, Samples.AbstractFactory.MsSqlDAL“
  connectionString=“Persist Security Info=False;Integrated Security=SSPI;database=northwind;server=mySQLServer“
/>

Atributem type volíme třídu implementující konkrétní továrnu (zde Samples.AbstractFactory.MsSqlDAL.MsSqlFactory) a atributem connectionString určíme připojovací řetězec k databázi, který budou DAL komponenty využívat. Tvar připojovacího řetězce musí odpovídat databázovému provideru, který implementace DAL komponent využívá.

Pro použití s DB serverem Oracle postačí změnit atribut type na hodnotu Samples.AbstractFactory.OracleDAL.OracleFactory a při příštím spuštění již bude aplikace používat tuto implementaci. Podporu dalších databázových serverů lze dodat implementací DatabaseFactory a komponent IProductDALC a IOrderDALC pro tento další typ serveru.

Doporučená literatura

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět