2015-08-01 18 views
6

Co zrobić, jeśli klasy o tym samym interfejsie mają podobny, ale inny podpis metody?Co zrobić, jeśli klasy o tym samym interfejsie mają podobny, ale inny podpis metody?

Załóżmy, że mam projekt do obliczenia różnych kosztów (aby ostatecznie uzyskać całkowity koszt).

W moim programie jest kilka klas kalkulatora, a mianowicie ACostCalculator, BCostCalculator i tak dalej. Kiedy wywoływana jest metoda calculate() w celu obliczenia kosztu, kontener kosztowy jest również przekazywany do tych kalkulatorów kosztów. W dobrym scenariuszu mogę utworzyć interfejs CostCalculator dla każdego kalkulatora kosztów.

Jednak obliczenia dla różnych kosztów wymagały różnych zasobów. W moim bieżącym programie wygląda to tak:

//getResource() are costly method while several costs need this. So do it outside calculate() method. 
ResourceA resourceA = getResourceA(); 
ResourceB resourceB = getResourceB(); 

CostContainer costContainer = new CostContainer(); 
CostCalculator aCostCalculator = new ACostCalculator(); 
... 
CostCalculator eCostCalculator = new ECostCalculator(); 

aCostCalculator.calculate(costContainer); 
bCostCalculator.calculate(costContainer) 
cCostCalculator.calculate(costContainer, resourceA); 
dCostCalculator.calculate(costContainer, resourceA); 
eCostCalculator.calculate(costContainer, resourceA, resourceB); 

Jeśli podpis jest dokładnie taki sam, mogę wykonać pętlę wygodnie, aby zrobić to za jednym razem. Jednak ponieważ są one podobne, ale różne, nie mogę nawet stworzyć dobrego interfejsu.

Nie jestem pewien, czy istnieją na to dobre sposoby. Co mogę pomyśleć, to uogólniając wszystkie calculate() metodę do

calculate(CostContainer costContainer, List<Object> resources); 

Jakieś pomysły? Dzięki za odpowiedź.

+0

... lub varargs zamiast listy. –

Odpowiedz

5

Jeśli zasoby pozostaną niezmienione przez cały cykl życia kalkulatorów: przekaż zasoby do konstruktora kalkulatorów.

ResourceA resourceA = getResourceA(); 
ResourceB resourceB = getResourceB(); 

CostContainer costContainer = new CostContainer(); 

CostCalculator aCostCalculator = new ACostCalculator(); 
CostCalculator bCostCalculator = new BCostCalculator(); 
CostCalculator cCostCalculator = new CCostCalculator(resourceA); 
CostCalculator dCostCalculator = new DCostCalculator(resourceA); 
CostCalculator eCostCalculator = new ECostCalculator(resourceA, resourceB); 

aCostCalculator.calculate(costContainer); 
bCostCalculator.calculate(costContainer); 
cCostCalculator.calculate(costContainer); 
dCostCalculator.calculate(costContainer); 
eCostCalculator.calculate(costContainer); 
+1

Jest to jedyne rozwiązanie bezpieczne dla rodzaju. I nawet jeśli argumenty nie są statyczne w całym cyklu życia kalkulatorów, można zaimplementować podobny mechanizm za pomocą klasy opakowania. –

+0

Cóż, wygląda idealnie i czysto, dostosowując się do mojego przypadku użycia. –

+0

Jest już całkiem piękna i prosta. Ale zobaczmy, czy ktoś może dać kolejną genialną odpowiedź: –

0

Można użyć variadic arguments:

public interface CostCalculatorInterface { 
    public void calculate(CostContainer container, Object... resources); 
} 

(lub zastąpić Object z innym nadklasą ResourceA i ResourceB).

W klasach realizujących interfejs resources będzie Object[] więc można odnosić się do nich jak resources[0], resources[1] i tak dalej.

+2

To nie jest bezpieczne dla typu, ale ... –

+0

jeśli przekażesz niepoprawną liczbę zasobów, wyjątek może zostać odrzucony. – ka4eli

+0

Dzięki za asnwering. Bezpieczeństwo typu jest jednym z problemów, a także czytelności. Inny programista może potrzebować sporo wysiłku, aby dowiedzieć się, jakie są "zasoby [0]". –

-1

To jest coś, co można faktycznie korzysta z rodzajowych:

Resource resourceA = new ResourceA(); 
    Resource resourceB = new ResourceB(); 
    CostContainer costContainer = new CostContainer(); 
    CostCalculator<Resource> costCalculatorA = new ACostCalculator(); 
    costCalculatorA.calculate(costContainer,resourceA,resourceB); 
    CostCalculator<Resource> costCalculatorB = new BCostCalculator(); 
    costCalculatorB.calculate(costContainer,resourceA); 


interface Resource { 
    //Your code 
} 

class ResourceA implements Resource { 
    //Your code 
} 

class ResourceB implements Resource { 
    //Your code 
} 

class CostContainer { 
    //Your code 
} 

interface CostCalculator<T extends Resource> { 
    void calculate(CostContainer costContainer, T... resources); 
} 

class ACostCalculator implements CostCalculator<Resource>{ 

    @Override 
    public void calculate(CostContainer costContainer, Resource... resources) { 
     System.out.println("Test"); 
    } 
} 

class BCostCalculator implements CostCalculator<Resource>{ 

    @Override 
    public void calculate(CostContainer costContainer, Resource... resources) { 
     System.out.println("Test2"); 
    } 
} 
+0

Dzięki za odpowiedź. Istnieją jednak podobne wady, jak odpowiedź Glorfindela. –

2

Problem z różnych podpisów dla wspólnego interfejsu brzmi to jak problem, że Adapter design pattern (object adapter variant) rozwiązuje:

Adapter pattern (GoF)

W zależności od sytuacji używałbyś tylko adapterów do nieodpowiednich kalkulatorów. Istnieją tak naprawdę tylko dwa typy adapterów, Type1 dla podpisu (costContainer, resourceA) i Type2 dla podpisu (costContainer, resourceA, resourceB). Korzystanie z przykładem:

Adapter pattern applied to your example

Zalety karty jest to, że jest to znany wzorzec projektowy (opublikowana w 1995 roku przez GOF), pozwala na ewentualne calculate(...) metody, które mają różne podpisy. Adaptery mogą być aktualizowane dynamicznie, jeśli wystąpią zmiany kontekstu (np. Zmiana zasobów).

Wadami są oczywiście dodatkowe zajęcia, niedostrzeganie itp. Jest to bardziej skomplikowane niż wybrana odpowiedź, ale jest bardziej elastyczne, szczególnie jeśli nie można zmodyfikować interfejsu API adaptesów.

+0

Dzięki za odpowiedź. Odpowiedź od lesmana nie działa, jeśli cykl życia kalkulatora zasobów i kosztów nie jest zgodny, np. Kalkulator kosztów to singleton. Czy wzór adaptera może być zastosowany we wszystkich sytuacjach? Przypuszczam, że tak (zakładając, że kalkulator kosztów jest bezpieczny dla wątków)? –

+0

@ MichaelW.Patterns są zawsze ogólnymi rozwiązaniami na poziomie problemu, który mają rozwiązać. Zastosowanie go do kodu jest jedynym sposobem, aby wiedzieć na pewno. Kod klienta naprawdę widzi tylko Adaptery. Dopóki są tworzone i zarządzane poprawnie, nie powinno mieć znaczenia, czy adaptees są singletons. Możesz dodać np. 'SetResourceA()', jeśli chcesz dynamicznie zmieniać kontekst adapterów. Wzór zapewnia tylko standardowy interfejs. Musisz zająć się resztą (brzydkich) szczegółów. – Fuhrmanator

+0

Mam to dzięki. Pozwól, że wypróbuję to później. –