Jak zarządzać pojedynczą odpowiedzialnością, gdy odpowiedzialność jest dzielona?

10

Mam dwie podstawowe klasy Operationi Trigger. Każda z nich ma wiele podklas, które specjalizują się w określonych rodzajach operacji lub wyzwalaczy. A Triggermoże wyzwolić określony Operation. Chociaż Operationmoże być wyzwalany przez określony Trigger.

Muszę napisać kod, który odwzorowuje dane Operationdane Trigger(lub odwrotnie), ale nie jestem pewien, gdzie je umieścić.

W takim przypadku kod nie należy wyraźnie do jednej lub drugiej klasy. Jeśli chodzi o zasadę pojedynczej odpowiedzialności, nie jestem pewien, gdzie powinien być kod.

Widzę trzy opcje, które wszystko by działały. Podczas gdy 1 i 2 wydają się być tylko wyborem semantyki, 3 reprezentuje zupełnie inne podejście.

  1. Na spuście, np bool Triggers(Operation o).
  2. Na operacji, np bool TriggeredBy(Trigger t).
  3. W zupełnie nowej klasie, która zarządza mapowaniem, np bool MappingExists(Trigger t, Operation o).

Jak zdecydować, gdzie umieścić wspólny kod odwzorowania w odniesieniu do zasady pojedynczej odpowiedzialności?

Jak zarządzać pojedynczą odpowiedzialnością, gdy odpowiedzialność jest dzielona?


Edytuj 1.

Rzeczywisty kod wygląda tak. Wszystkie właściwości są albo string, Guid, collection<string>, lub enum. Zasadniczo są to po prostu małe fragmenty danych.

wprowadź opis zdjęcia tutaj

Edytuj 2.

Przyczyna zwrotu typu bool. Kolejna klasa będzie zużywać kolekcję Triggeri kolekcję Operation. Musi wiedzieć, gdzie istnieje mapowanie między a Trigger, a Operation. Wykorzysta te informacje do utworzenia raportu.

James Wood
źródło
Dlaczego typ bool?
Tulains Córdova
@ user61852, aby zwrócić wynik do kodu wywołującego
James Wood
1
Co robi kod wywołujący z wartością logiczną? W zależności od odpowiedzi na to pytanie mogę mieć rozwiązanie.
Tulains Córdova
@ user61852, zobacz moje zmiany.
James Wood
1
Więc nie ma to nic wspólnego z faktycznym wykonaniem wyzwalacza operacji?
Tulains Córdova

Odpowiedzi:

4

Pomyślałbym o tym w ten sposób: w jaki sposób określa się, która Operacja powoduje uruchomienie wyzwalacza. Musi to być algorytm, który może zmieniać się w czasie lub ewoluować w wiele algorytmów. Umieszczenie go w klasach Trigger lub Operation oznacza, że ​​klasy te będą w stanie obsłużyć takie scenariusze w przyszłości. Zauważ, że nie uważam tego za tak proste jak mapowanie, ponieważ może być więcej.

Moim wyborem byłoby utworzenie klasy za pomocą odpowiednich metod, takich jak GetOperationForTrigger (Trigger t). Pozwala to kodowi przekształcić się w zestaw takich klas, których wybór może zależeć w czasie wykonywania lub innych zmiennych (np. Wzorzec strategii).

Zauważ, że głównym założeniem w tym sposobie myślenia jest pisanie minimalnego kodu (tj. Obecnie trzech klas), ale unikanie poważnego refaktoryzacji, jeśli funkcjonalność będzie musiała zostać rozszerzona w przyszłości, nie zakładając, że zawsze będzie dokładnie jeden sposób określić, który wyzwalacz powoduje, która operacja.

Mam nadzieję że to pomoże. Chociaż odpowiedź jest podobna do user61852, rozumowanie jest inne. W rezultacie implementacja będzie się różnić (tj. Mieć jawne metody zamiast zastępowania równe, więc liczba metod może ewoluować w czasie w zależności od potrzeb).

Omer Iqbal
źródło
5

Byłem tam, zrobiłem to.

Opcja nr 3.

Nie wiem, jakiego języka będziesz używać, ale użyję pseudokodu, który jest bardzo podobny do języka Java. Jeśli Twoim językiem jest C #, prawdopodobnie masz podobne interfejsy i struktury.

Posiadaj klasę lub interfejs mapowania:

public interface Mapping {
    public void setObject1(Object o);
    public void setObject2(Object o);
    public Object getObjecto1();
    public Object getObjecto2();
}
  • Przesłoń equals()metodę, Mappingaby Mappingmożna było zapytać, czy zawierają one dane mapowanie.
  • Specjalizowane obiekty również powinny mieć odpowiednie equals()metody.
  • Zaimplementuj także interfejs Comparable, aby móc sortować raporty.

Możesz po prostu umieścić mapowania w kolekcji

List<Mapping> list = new ArrayList<Mapping>();
Hat hat = new Hat();
Bag bag = new Bag();
list.add(new Mapping(hat,bag));

Później możesz zapytać:

// let's say you have a variable named x which is of type Mapping

if ( list.contains(x) ){
    // do some thing
}
Tulains Córdova
źródło
0
  1. Podziel kod na mniejsze części.

Obecnie masz klasę A znającą klasę B i klasę B znającą klasę A. To dużo się dzieje.

Z definicji A wykonuje co najmniej własną operację ORAZ sprawdza, czy należy uruchomić B. Odwrotna sytuacja jest prawdą w przypadku B. Jakakolwiek pierwsza klasa nazywa się, powinna być w stanie spojrzeć na wynik i sprawdzić, czy należy uruchomić inne rzeczy.

Spróbuj przerwać to połączenie, dzieląc swoje klasy na mniejsze elementy. Lubię umieszczać komentarz na górze każdej klasy, wyjaśniając zwykłym angielskim, co robi. Jeśli potrzebujesz użyć takich słów jak AND lub ciągnie się ono przez zdanie lub dwa, musisz rozważyć jego podział. Zasadniczo wszystko po „i” powinno należeć do własnej klasy

Sprawdź także, czy możesz zdefiniować interfejs obejmujący funkcje Trigger i Operation. Jeśli nie możesz, to kolejna oznaka, że ​​twoja klasa staje się zbyt duża. To także przerwie połączenie między wami.

Tom Squires
źródło
1
Szczerze mówiąc, nie jestem pewien, czy mogę dalej łamać kod. Zaktualizowałem moje pytanie o podpisy klas, abyście mogli je zobaczyć, ale w zasadzie są to dość lekkie obiekty danych przechowujące po kilka właściwości. Jeśli chodzi o sprzężenie, tak, to jest nieco problematyczne, ponieważ faktycznie a Triggerbyłoby połączone z a Operation. Ale tak wyglądają dane ze świata rzeczywistego. Są sprzężone, ponieważ istnieje mapowanie, np. Muszą wiedzieć o sobie, aby mieć znaczenie.
James Wood