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
123456789101112131415161718192021
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).
12345678910
namespace
Builder
{
public
interface
IComputerBuilder
{
void
BuildCpu();
void
BuildRamMemory();
void
BuildHardDriveType();
Computer GetComputer();
}
}
Dla ułatwienia tego przykładu utworzyłem klasę Assembly
1234567891011121314151617181920212223242526namespace
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.
123456789101112131415161718192021222324252627282930
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;
}
}
}
123456789101112131415161718192021222324252627public
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;
}
}
123456789101112131415161718192021222324252627public
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.
12345678910111213
namespace
Builder.Director
{
public
class
Manufacturer
{
public
void
BuildComputer(IComputerBuilder computerBuilder)
{
computerBuilder.BuildCpu();
computerBuilder.BuildHardDriveType();
computerBuilder.BuildRamMemory();
computerBuilder.GetComputer();
}
}
}
- Gotowy działający program
1234567891011121314151617181920212223242526
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());
}
}