W tej serii będę opisywał najpopularniejsze wzorce projektowe. Nie odkryję tu Ameryki, jednak posty te będą powstawać głównie dla utrwalenia wiedzy i możliwości szybkiego jej przejrzenia, a jeżeli komuś się to przyda to tym lepiej.
Na pierwszy ogień idzie jeden z podstawowych wzorców konstrukcyjnych a więc tytułowy „Builder”
Zalety: Za pomocą tego wzorca oddzielamy proces tworzenia obiektu od jego implementacji (za przykład mogą tu posłużyć wszelkiej maści konwertery potrafiące dać wiele formatów wynikowych).
Składowe:
- Builder – jest interfejsem abstrakcyjnym służącym do tworzenia obiektów.
- ConcreteBuilder
- Tworzy i łączy poszczególne składniki ze sobą.
- Generuje i śledzi poszczególne wygenerowane składniki.
- Udostępnia interfejs do pobrania gotowego obiektu.
- Director – tworzy obiekt za pomocą interfejsu klasy „Builder”.
- Product
- To on reprezentuje nasz wygenerowany złożony obiekt
- Zawiera klasy definiujące składowe obiektu, oraz interfejsy do ich łączenia
Na diagramie przedstawia się to następująco:
Zastosowanie:
- Wzorzec ten powinien być zastosowany wówczas, gdy algorytm tworzący obiekt powinien być nie zależny od samego procesu tworzenia finalnego obiektu.
- Jeżeli za pomocą jednego procesu konstrukcji obiektu chcemy wygenerować różne reprezentacje danego obiektu.
Czas na odrobinę kodu
- Tworzymy model naszego pożądanego obiektu (Product), który będzie tworzony przy użyciu Director’a implementującego interfejs Buildera
namespace Builder.Product { public class Computer { private readonly string _computerOwner; public Cpu cpu { get; set; } public RamMemory ramMemory { get; set; } public HardDriveType hardDriveType { get; set; } public Computer(string computerOwner) { _computerOwner = computerOwner; } public override string ToString() { return string.Format("Owner: {0}\nCPU: {1}\nMemory: {2} GB\nHard Drive: {3}\n", _computerOwner, cpu, (int)ramMemory, hardDriveType); } } }
- Kolej na nasz abstrakcyjny interfejs (Builder), który pomoże stworzyć nasz pożądany obiekt (Product).
namespace Builder { public interface IComputerBuilder { void BuildCpu(); void BuildRamMemory(); void BuildHardDriveType(); Computer GetComputer(); } }
Dla ułatwienia tego przykładu utworzyłem klasę Assembly
namespace Builder.Model { public class Assembly { public enum Cpu { i3, i5, i7 }; public enum RamMemory { One = 1, Two = 2, Four = 4, Eight = 8 } public enum HardDriveType { HDD, SSD, HYBRID } }
- Czas na Concrete Builder, który implementuje nasz interfejs (Builder). W moim wypadku są tą trzy klasy.
namespace ConcreteBuilder { public class CheapComputerBuilder : IComputerBuilder { Computer _computer; public CheapComputerBuilder(string ownerName) { _computer = new Computer(ownerName); } public void BuildCpu() { _computer.cpu = Cpu.i3; } public void BuildHardDriveType() { _computer.hardDriveType = HardDriveType.HDD; } public void BuildRamMemory() { _computer.ramMemory = RamMemory.Two; } public Computer GetComputer() { return _computer; } } }
public class ExpensiveComputerBuilder : IComputerBuilder { Computer _computer; public ExpensiveComputerBuilder(string computerOwner) { _computer = new Computer(computerOwner); } public void BuildCpu() { _computer.cpu = Cpu.i7; } public void BuildHardDriveType() { _computer.hardDriveType = HardDriveType.SSD; } public void BuildRamMemory() { _computer.ramMemory = RamMemory.Eight; } public Computer GetComputer() { return _computer; } }
public class NormalPriceComputerBuilder : IComputerBuilder { Computer _computer; public NormalPriceComputerBuilder(string computerOwner) { _computer = new Computer(computerOwner); } public void BuildCpu() { _computer.cpu = Cpu.i5; } public void BuildHardDriveType() { _computer.hardDriveType = HardDriveType.HYBRID; } public void BuildRamMemory() { _computer.ramMemory = RamMemory.Four; } public Computer GetComputer() { return _computer; } }
- Director, odpowiada za prawidłową sekwencję budowania naszego obiektu.
namespace Builder.Director { public class Manufacturer { public void BuildComputer(IComputerBuilder computerBuilder) { computerBuilder.BuildCpu(); computerBuilder.BuildHardDriveType(); computerBuilder.BuildRamMemory(); computerBuilder.GetComputer(); } } }
- Gotowy działający program
class Program { static void Main(string[] args) { Manufacturer newManufacturer = new Manufacturer(); // Lets have the Builder class ready IComputerBuilder computerBuilder = null; // Create a new cheap computer computerBuilder = new CheapComputerBuilder("Dave"); newManufacturer.BuildComputer(computerBuilder); Console.WriteLine("A new Computer built:\n\n{0}", computerBuilder.GetComputer().ToString()); // Create a new normal price computer computerBuilder = new NormalPriceComputerBuilder("Tom"); newManufacturer.BuildComputer(computerBuilder); Console.WriteLine("A new Computer built:\n\n{0}", computerBuilder.GetComputer().ToString()); // Create a new expensive computer computerBuilder = new ExpensiveComputerBuilder("Joe"); newManufacturer.BuildComputer(computerBuilder); var computer = computerBuilder.GetComputer(); Console.WriteLine("A new Computer built:\n\n{0}", computerBuilder.GetComputer().ToString()); } }