Sari la conținutul principal

Factory

În multe situații, dorim să creăm obiecte/componente software în funcție de contextul aplicației. Pentru a putea comuta între o implementare sau alta, utilizăm un obiect fabrică (factory), care ne permite să obținem o implementare sau alta în funcție de configurarea aplicației.

Considerând implementarea de mai jos pentru două tipuri de medii de stocare, unul in-memory și altul pe un fișier, putem crea un factory configurat cu un enum pentru a folosi o implementare sau alta.

IStorage.cs
// Declaram o interfata care sa fie folosita de factory.
public interface IStorage
{
public void SaveValue(string key, string value);
public string? GetValue(string key);
public void AddValue(string key, string value);
}
AbstractStorage.cs
/* Clasa abstracta poate sau nu sa implementeze o interfata.
* Aici folosim clasa astracta pentru ca o metoda poate fi implementata din alte metode
* si pentru a reduce codul implementam acea metoda.
*/
public abstract class AbstractStorage : IStorage
{
// Metodele care nu se implementeaza sunt declarate cu "abstract".
public abstract void SaveValue(string key, string value);
public abstract string? GetValue(string key);

/* Clasa abstracta poate avea metode implementate, daca se implementeaza
* o interfata trebuie fie ca medele sa fie declarate ca fiind abstracte fie
* se fie implementate.
*/
public void AddValue(string key, string value)
{
var oldValue = GetValue(key);

SaveValue(key, oldValue != null ? $"{oldValue} {value}" : value);
}
}
FileStorage.cs
// Daca folosim cuvantul cheie "sealed", aceasta clasa nu mai poate fi derivata mai departe.
public sealed class FileStorage : AbstractStorage
{
private const string FilePath = "./Storage.txt";

public FileStorage()
{
if (!File.Exists(FilePath))
{
using var _ = File.Create(FilePath);
}
}

// Trebuie folosit "override" pe metodele abstracte din clasa abstracta
public override void SaveValue(string key, string value)
{
var lines = File.ReadAllLines(FilePath);
using var file = new StreamWriter(FilePath, new FileStreamOptions
{
Mode = FileMode.Truncate,
Access = FileAccess.Write
});

file.WriteLine($"{key} {value}");

foreach (var line in lines)
{
var lineKey = line[..line.IndexOf(" ", StringComparison.InvariantCulture)];

if (lineKey != key)
{
file.WriteLine(line);
}
}
}

public override string? GetValue(string key)
{
using var reader = new StreamReader(FilePath, new FileStreamOptions
{
Mode = FileMode.OpenOrCreate
});

while (!reader.EndOfStream)
{
var line = reader.ReadLine();

var lineKey = line?[..line.IndexOf(" ", StringComparison.InvariantCulture)];

if (lineKey == key)
{
return line?[(line.IndexOf(" ", StringComparison.InvariantCulture) + 1)..];
}
}

return default;
}
}
InMemoryStorage.cs
public class InMemoryStorage : AbstractStorage
{
private readonly Dictionary<string, string> _cache = new();

public override void SaveValue(string key, string value) => _cache[key] = value;

public override string? GetValue(string key) => _cache.TryGetValue(key, out var value) ? value : default;
}

Exercițiu - Factory

Folosind implementările de mai sus, creați un factory care să aibă o metodă ce produce o implementare sau alta și o returnează ca interfață pe care o implementează. Modificați codul de mai jos astfel încât să folosească acel factory.

Program.cs
class Program
{
static void Main(string[] args)
{
IStorage? storage;

do
{
Console.Write("Input storage type: ");
var line = Console.ReadLine();

if (line == "inmemory")
{
storage = new InMemoryStorage();
break;
}

if (line == "file")
{
storage = new FileStorage();
break;
}
} while (true);

storage.AddValue("test1", "1");
storage.AddValue("test1", "2");
storage.AddValue("test1", "3");
storage.AddValue("test2", "4");
storage.AddValue("test2", "5");
Console.WriteLine("test1: {0}", storage.GetValue("test1"));
Console.WriteLine("test2: {0}", storage.GetValue("test2"));
}
}