Wzorzec kompozytu
Część sekcji Programowanie obiektowe ścieżki C# w Coddy. Lekcja 61 z 70.
Wzorzec Composite to wzorzec strukturalny, który pozwala łączyć obiekty w struktury drzewiaste oraz traktować pojedyncze obiekty i grupy obiektów jednolicie. Doskonale sprawdza się przy przedstawianiu hierarchii całość-część, gdy chcesz pracować zarówno z pojedynczymi elementami, jak i kolekcjami za pośrednictwem tego samego interfejsu.
Wzorzec ma trzy kluczowe elementy: interfejs komponentu definiujący wspólne operacje, klasy liści reprezentujące pojedyncze obiekty oraz klasy złożone, które zawierają elementy podrzędne i implementują ten sam interfejs:
public interface IFileSystemItem
{
string Name { get; }
int GetSize();
}
public class File : IFileSystemItem
{
public string Name { get; }
private int _size;
public File(string name, int size)
{
Name = name;
_size = size;
}
public int GetSize() => _size;
}
public class Folder : IFileSystemItem
{
public string Name { get; }
private List<IFileSystemItem> _items = new List<IFileSystemItem>();
public Folder(string name) => Name = name;
public void Add(IFileSystemItem item) => _items.Add(item);
public int GetSize() => _items.Sum(item => item.GetSize());
}Piękno tego wzorca polega na tym, że klienci nie muszą wiedzieć, czy pracują z pojedynczym plikiem, czy z całą hierarchią folderów:
var root = new Folder("root");
root.Add(new File("doc.txt", 100));
var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));
root.Add(subFolder);
Console.WriteLine(root.GetSize()); // 650Wzorzec Kompozyt jest idealny w przypadku systemów plików, drzew komponentów interfejsu użytkownika, schematów organizacyjnych lub dowolnej struktury, w której kontenery i ich zawartość powinny być traktowane w ten sam sposób. Upraszcza kod klienta, eliminując potrzebę rozróżniania prostych i złożonych elementów.
Wyzwanie
ŁatwyZbudujmy system schematu organizacyjnego z użyciem wzorca Composite. Utworzysz strukturę, w której zarówno poszczególnych pracowników, jak i działy (zawierające pracowników lub inne działy) można traktować jednakowo, co pozwoli obliczyć łączny koszt wynagrodzeń na dowolnym poziomie hierarchii.
Rozmieścisz kod w trzech plikach:
OrgComponent.cs: Zdefiniuj interfejsIOrgComponentw przestrzeni nazwOrganizationz dwoma elementami: właściwościąName(string) i metodąGetSalary(), która zwraca liczbę całkowitą. Ten interfejs określa wspólny kontrakt, którego będą przestrzegać zarówno poszczególni pracownicy, jak i działy.OrgClasses.cs: Utwórz dwie klasy w tej samej przestrzeni nazw:Employee— klasę liścia reprezentującą pojedynczego pracownika. Przyjmuje ona imię i wynagrodzenie w konstruktorze oraz implementuje interfejs, zwracając własne wynagrodzenie.Department— klasę złożoną reprezentującą grupę. Przyjmuje ona nazwę w konstruktorze i przechowuje listę elementówIOrgComponent. Dodaj metodęAdd(IOrgComponent component), aby dodawać członków. MetodaGetSalary()powinna zwracać sumę wynagrodzeń wszystkich członków działu.
Program.cs: Zbuduj strukturę organizacji na podstawie danych wejściowych i oblicz łączne wynagrodzenie. Utworzysz działy i pracowników, odpowiednio umieścisz ich w hierarchii i pokażesz, jak wzorzec Composite pozwala traktować całą hierarchię jednakowo.
Otrzymasz następujące dane wejściowe:
- Liczbę komponentów do utworzenia
- Dla każdego komponentu: typ (
employeelubdepartment), następnie nazwę, a w przypadku pracowników także ich wynagrodzenie. W przypadku działów otrzymasz również nazwę działu nadrzędnego (lubroot, jeśli jest to dział najwyższego poziomu).
Format danych wejściowych dla każdego komponentu:
employee
{name}
{salary}
{parent_department}
department
{name}
{parent_department}Po zbudowaniu struktury wyświetl łączne wynagrodzenie działu głównego w formacie {root_name} Total Salary: {amount}.
Na przykład, jeśli dane wejściowe są następujące:
5
department
Engineering
root
employee
Alice
5000
Engineering
employee
Bob
4500
Engineering
department
QA
Engineering
employee
Charlie
4000
QAWynik powinien być następujący:
Engineering Total Salary: 13500Dział Engineering zawiera Alice (5000), Boba (4500) oraz dział QA. Dział QA zawiera Charliego (4000). Po wywołaniu metody GetSalary() dla działu Engineering metoda rekurencyjnie oblicza łączną kwotę na podstawie wszystkich zagnieżdżonych komponentów — pokazując, jak wzorzec Composite pozwala pracować ze złożonymi strukturami drzewiastymi za pomocą prostego, jednolitego interfejsu!
Spróbuj swoich sił
using System;
using System.Collections.Generic;
using Organization;
class Program
{
public static void Main(string[] args)
{
int n = Convert.ToInt32(Console.ReadLine());
// TODO: Utwórz słownik do przechowywania działów według nazwy
// Ułatwi to znalezienie działów nadrzędnych podczas dodawania komponentów
Dictionary<string, Department> departments = new Dictionary<string, Department>();
// TODO: Śledź dział główny
Department root = null;
for (int i = 0; i < n; i++)
{
string type = Console.ReadLine();
if (type == "department")
{
string name = Console.ReadLine();
string parent = Console.ReadLine();
// TODO: Utwórz dział
// TODO: Dodaj go do słownika departments
// TODO: Jeśli parent ma wartość "root", jest to dział główny
// TODO: W przeciwnym razie znajdź dział nadrzędny i dodaj do niego ten dział
}
else if (type == "employee")
{
string name = Console.ReadLine();
int salary = Convert.ToInt32(Console.ReadLine());
string parent = Console.ReadLine();
// TODO: Utwórz pracownika
// TODO: Znajdź dział nadrzędny i dodaj do niego pracownika
}
}
// TODO: Wyświetl łączną kwotę wynagrodzeń w dziale głównym
// Format: "{root_name} Total Salary: {amount}"
Console.WriteLine($"{root.Name} Total Salary: {root.GetSalary()}");
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrznePrzestrzenie nazw i dyrektywyWprowadzenie do klas i obiektówSłowo kluczowe „this”Metody i parametryPola a właściwościKonstruktoryInicjalizatory obiektówPodsumowanie — prosty kalkulator4Dziedziczenie
Podstawowa składnia dziedziczenia (:)Słowo kluczowe 'base'Słowa kluczowe virtual i overrideKlasy sealedBazowa klasa 'object'Powtórzenie – hierarchia pracowników7Zaawansowane funkcje
Przeciążanie operatorówIndeksery (this[])Nadpisywanie ToString()Metody rozszerzającePodsumowanie – własna lista2Właściwości i składowe statyczne
Właściwości automatyczneWłaściwości tylko do odczytu i zapisuPola i metody statyczneKlasy statyczneSkładowe z ciałem wyrażeniowym5Polimorfizm i interfejsy
Polimorfizm czasu kompilacji a czasu wykonaniaInterfejs a klasa abstrakcyjnaWiele interfejsówJawne implementowanie interfejsówRzutowanie w górę i w dółPowtórka — kalkulator kształtów8Zaawansowane koncepcje OOP
Kompozycja zamiast dziedziczeniaTypy generyczne (klasy i metody)Delegaty i zdarzeniaAtrybuty i refleksjaIDisposable i instrukcja usingPodstawy wstrzykiwania zależności11Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytu3Architektura klas
Dane instancji a dane statyczneSłowa kluczowe 'readonly' i 'const'Pola zapasowePowtórzenie — menedżer kont bankowych6Hermetyzacja
Modyfikatory dostępuWłaściwości w hermetyzacjiImplementacja ukrywania danychWzorce niezmiennościPowtórka — dane studentów9Argumenty zmiennej liczby
Słowo kluczowe „params”Parametry opcjonalneArgumenty nazwanePrzeciążanie metodPoćwicz samodzielnie: Kompilator C# online