Ik heb momenteel wat problemen om tot een goed design van mijn applicatie te komen. Het gaat om het volgende: De applicatie moet prijzen berekenen voor producten, zodate er een offerte uitgebracht kan worden. Op dit moment (maar die zou in de toekomst kunnen veranderen) zijn de producten op te delen in twee groepen, productGroepA en productGroepB. Iedere productgroep heeft een n-aantal producten onder zich. De twee productgroepen hebben twee dingen in common (ben nederlandse term ff kwijt
)
1. Ze moeten beiden een prijs gaan berekenen
2. Ze hebben een class (params met hun getters en setters) waarin alle input en output parameters staan (input zijn ongeveer 10 parameters, output ongeveer 30, vandaar de aparte class)
De daadwerkelijke berekening van de prijzen is per groep totaal verschillend. Om het nog lastiger te maken, is op dit moment nog niet bekend hoe de berekening van productGroepB er uit gaat zien.
De berekening voor productGroepA bestaat uit een 7-tal subberekening, die voor alle producten in die groep hetzelfde zijn, op 1 na. Deze laatste berekening is ook weer verschillend per type layout van de offerte.
Nu ben ik zelf tot het volgende design gekomen, maar ik weet gewoon dat het niet klopt.. er zit o.a. nog veel te veel dubbele code in. Zal na de code een alternatief wat ik bedacht had bespreken, maar ook dat is een fout design volgens mij.
Abstract class for ieder product (geen interface vanwege de factory method)
- Messenger is een interface voor de classes die alle input/output parameters in zich hebben.
- De factory method geeft de Messenger door aan de constructor van de producten, dat klopt volgens mij ook niet.. hoe kan ik daar beter mee om gaan?
Abstract class voor ieder product van productGroepA:
(ProductGroepAMessenger is dus een type Messenger)
Een product uit productGroepA:
- Hoewel bij elke calculate method hetzelfde staat, is dit in werkelijkheid verschillend voor iedere method.
Als alternatief had ik overwogen om de code die voor elk product van productGroepA hetzelfde is (calculate1 t/m 6) in de abstract class ProductGroepA te plaatsen, maar ook dat is een slecht design.
Beide oplossingen gaan echter in tegen het idee van "favor composition over inherentance" en "prefer interfaces to abstract classes".. hoe kan ik mijn design aanpassen om het wel goed (of in ieder geval beter) te maken?
Voor de duidelijkheid: Ik heb de code aangepast om tot een simpele en duidelijke benaming te komen. Mochten er toch onduidelijkheden zijn, schroom niet te vragen!
Sorry voor deze lap tekst, maar hoop dat jullie mij hier wat mee kunnen helpen!
1. Ze moeten beiden een prijs gaan berekenen
2. Ze hebben een class (params met hun getters en setters) waarin alle input en output parameters staan (input zijn ongeveer 10 parameters, output ongeveer 30, vandaar de aparte class)
De daadwerkelijke berekening van de prijzen is per groep totaal verschillend. Om het nog lastiger te maken, is op dit moment nog niet bekend hoe de berekening van productGroepB er uit gaat zien.
De berekening voor productGroepA bestaat uit een 7-tal subberekening, die voor alle producten in die groep hetzelfde zijn, op 1 na. Deze laatste berekening is ook weer verschillend per type layout van de offerte.
Nu ben ik zelf tot het volgende design gekomen, maar ik weet gewoon dat het niet klopt.. er zit o.a. nog veel te veel dubbele code in. Zal na de code een alternatief wat ik bedacht had bespreken, maar ook dat is een fout design volgens mij.
Abstract class for ieder product (geen interface vanwege de factory method)
- Messenger is een interface voor de classes die alle input/output parameters in zich hebben.
- De factory method geeft de Messenger door aan de constructor van de producten, dat klopt volgens mij ook niet.. hoe kan ik daar beter mee om gaan?
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| public abstract class Product {
protected Logger log = null;
public abstract void calculate(Messenger messenger)
throws QEException;
public abstract String getName();
public abstract Messenger getParameters();
public static Product getInstance(int productID, Messenger params)
throws QEException {
switch (productID) {
case PRODUCT1:
return new Product1(params);
case PRODUCT2:
return new Product2(params);
case PRODUCT3:
return new Product3(params);
(etc. etc. )
default:
throw new QEException("QE9999", "Invalid productID");
}
}
} |
Abstract class voor ieder product van productGroepA:
(ProductGroepAMessenger is dus een type Messenger)
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| public abstract class ProductGroepA extends Product {
protected ProductGroepAMessenger productGroepAMessenger = null;
protected abstract void calculate1() throws QEException;
protected abstract void calculate2() throws QEException;
protected abstract void calculate3() throws QEException;
protected abstract void calculate4() throws QEException;
protected abstract void calculate5() throws QEException;
protected abstract void calculate6() throws QEException;
protected abstract void calculate7() throws QEException;
public Messenger getParameters() {
return productGroepAMessenger;
}
} |
Een product uit productGroepA:
- Hoewel bij elke calculate method hetzelfde staat, is dit in werkelijkheid verschillend voor iedere method.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
| public class Product1 extends ProductGroepA {
private static final String PRODUCTNAME = "Product1";
public Product1 (Messenger params) {
super(params);
log = Logger.getLogger(Product1.class.getName());
}
public void calculate() throws QEException {
log.info("Calculating 1");
calculate1();
log.info("Calculating 21");
calculate2();
log.info("Calculating 3");
calculate3();
log.info("Calculating 4");
calculate4();
log.info("Calculating 5");
calculate5();
log.info("Calculating 6");
calculate6();
log.info("Calculating 7");
calculate7();
}
protected void calculate1() throws QEException {
// prepare for calculation
// calculate -> gebeurt in een andere class
// process results
}
protected void calculate2() throws QEException {
// prepare for calculation
// calculate -> gebeurt in een andere class
// process results
}
protected void calculate3() throws QEException {
// prepare for calculation
// calculate -> gebeurt in een andere class
// process results
}
protected void calculate4() throws QEException {
// prepare for calculation
// calculate -> gebeurt in een andere class
// process results
}
protected void calculate5() throws QEException {
// prepare for calculation
// calculate -> gebeurt in een andere class
// process results
}
protected void calculate6() throws QEException {
// prepare for calculation
// calculate -> gebeurt in een andere class
// process results
}
protected void calculate7() throws QEException {
switch (layoutID) {
case LAYOUT1:
// do stuff
case LAYOUT2:
// do stuff
(etc. etc.)
}
}
public String getName() {
return PRODUCTNAME;
}
} |
Als alternatief had ik overwogen om de code die voor elk product van productGroepA hetzelfde is (calculate1 t/m 6) in de abstract class ProductGroepA te plaatsen, maar ook dat is een slecht design.
Beide oplossingen gaan echter in tegen het idee van "favor composition over inherentance" en "prefer interfaces to abstract classes".. hoe kan ik mijn design aanpassen om het wel goed (of in ieder geval beter) te maken?
Voor de duidelijkheid: Ik heb de code aangepast om tot een simpele en duidelijke benaming te komen. Mochten er toch onduidelijkheden zijn, schroom niet te vragen!
Sorry voor deze lap tekst, maar hoop dat jullie mij hier wat mee kunnen helpen!