[php] Object uitbreiden, op basis van een jaar

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 12-09 14:37
Hoe kan ik het beste een prijs berekenen, waarvoor de berekening af en toe veranderd. Misschien zou je zoiets doen:

PHP:
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
class printPrice {
    
    protected $numberOfPrints;
    
    public function __construct($numberOfPrints) {
        
        $this->numberOfPrints = $numberOfPrints;
        
    }

    public function getPrice() 
    {        
        return $this->getSetupPrice() + $this->getPaperPrice() + $this->getInkPrice();        
    }

    private function getSetupPrice()
    {
        return 125;
    }
    
    private function getPaperPrice()
    {
        return $this->numberOfPrints * 0.09;        
    }

    private function getInkPrice()
    {
        return $this->numberOfPrints * 0.03;
    }
}


Maar volgend jaar veranderen de tarieven; nou in plaats van hardcoded bedragen kun je die nog in een database zetten... maar misschien veranderd de berekening ook wel. Je kunt natuurlijk (bijv.) 1 januari 2016 de nieuwe versie gaan gebruiken, maar wat als je nog berekeningen voor 2015 wil doen?

Oplossingen

Zelf kan ik nog niet veel concreets vinden op Google, maar misschien zoek ik ook verkeerd :+ Is er een 'gangbare' oplossing voor dit probleem?

Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Sla gewoon een effectivedate + prijs op in je DB?

code:
1
2
3
4
5
6
7
8
Effectivedate   Product Price
1970-01-01      XYZ     0.03
2001-06-15      XYZ     0.04
2014-12-19      XYZ     0.05
1970-01-01      QRS     1.10
2010-03-03      QRS     1.20
2015-04-16      QRS     1.50
2015-10-01      QRS     2.00


Prijs voor je product op halen doe je dan, ongeveer, uit de losse pols, zo:
SQL:
1
2
3
4
5
select top 1 *
from productprice
where productcode = 'XYZ'
    and effectivedate < getdate()
order by effectivedate desc

[ Voor 99% gewijzigd door RobIII op 17-04-2015 15:54 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 12-09 14:37
RobIII schreef op vrijdag 17 april 2015 @ 15:49:
Sla gewoon een effectivedate + prijs op in je DB?

code:
1
2
3
4
5
6
7
Effectivedate   Product Price
1970-01-01      XYZ     0.03
2001-06-15      XYZ     0.04
2014-12-19      XYZ     0.05
1970-01-01      QRS     1.10
2010-03-03      QRS     1.20
2015-04-16      QRS     1.50
Oké dat werkt als de berekening niet veranderd. Alleen wat als die setupkosten nou vervallen en de prijs voor inkt en papier omhoog gaat? Dan veranderd de prijs voor die laatste 2 in de database, maar de getSetupPrice moet uit getPrice. Hoe kun je dan nog zorgen dat je voor 'orders' (oid.) 2015 en 2016 kunt blijven berekenen?

Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Maak een IPriceCalculator interface met een getPrice() method. Vervolgens bouw je een FooPriceCalculator die IPriceCalculator implementeert. Als je volgend jaar een nieuwe versie maakt die een andere berekeningsmethode hanteert dan bouw je een BarPriceCalculator die ook IPriceCalculator implementeert. Dan is 't enkel nog een kwestie van de juiste XXXPriceCalculator gebruiken voor de juiste periode. Dat is een kwestie van een factory die afhankelijk van de datum de juiste instantie van een XXXPriceCalculator teruggeeft. Die factory geef je dus, bijvoorbeeld, een CreatePriceCalculatorForDate(DateTime date) methode die een IPriceCalculator returned. Of die factory dan hardcoded datums gebruikt of daar ook een SQL tabelletje voor gebruikt is dan een tweede. Ook zo'n sql tabelletje is simpel:

code:
1
2
3
4
Effectivedate   CalculatorType
1970-01-01      Foo
2013-12-19      Bar
2015-04-16      Baz


code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//PSEUDOCODE
class PriceCalculatorFactory {
    public IPriceCalculator CreatePriceCalculatorForDate(DateTime date) {
      var type = "select top 1 calculatortype from pricecalculatortable where effectivedate < {date} order by effectivedate desc";
      switch (type.tolower()) {
        case 'foo':
          return new FooCalculator();
        case 'bar':
          return new BarCalculator();
        case 'baz':
          return new BazCalculator();
        default:
          throw new Exception('Unknown pricecalculatortype: ' + type);
      }
    }
}

[ Voor 46% gewijzigd door RobIII op 17-04-2015 16:07 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • frickY
  • Registratie: Juli 2001
  • Laatst online: 11-09 13:55
Toverwoord; Decorator-pattern

edit:
In combinatie met bovenstaand :Y)

[ Voor 23% gewijzigd door frickY op 17-04-2015 16:03 ]


Acties:
  • 0 Henk 'm!

  • kwaakvaak_v2
  • Registratie: Juni 2009
  • Laatst online: 02-06 12:29
TheNephilim schreef op vrijdag 17 april 2015 @ 15:55:
[...]


Oké dat werkt als de berekening niet veranderd. Alleen wat als die setupkosten nou vervallen en de prijs voor inkt en papier omhoog gaat? Dan veranderd de prijs voor die laatste 2 in de database, maar de getSetupPrice moet uit getPrice. Hoe kun je dan nog zorgen dat je voor 'orders' (oid.) 2015 en 2016 kunt blijven berekenen?
En als setup kosten nu ook gewoon een product met vaste waarde is en dus ook 0 (nul, niet null) is? Dan moet voor een simpele cost per afdruk prijs nog wel te doen zijn.

Driving a cadillac in a fool's parade.


Acties:
  • 0 Henk 'm!

  • Pin0
  • Registratie: November 2002
  • Niet online
Wij slaan in onze webwinkel bij de orders een copy op van de producten. Voor de overzichten, facturen en andere berekeningen gebruiken we de waarden uit het copy object. We kunnen dan altijd de prijs in de database aanpassen zonder dat de order history wordt beïnvloed.

Mijn Lego Mocs - LEGO idea: The Motorcycle Garage


Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 12-09 14:37
Bedankt voor de uitgebreide reactie RobIII! ^^

Misschien is het een wat grote lap code, maar ik wil toch graag weten of ik het goed begrepen heb:

PHP:
1
2
3
4
5
<?php
interface PriceCalculatorInterface
{
    public function getPrice();
}


De interface met getPrice method. Ik vroeg me af of de constructor hier ook nog thuishoort? In dit geval heb ik alleen de numberOfPrints, maar wellicht komt er (bijv.) een variabele bij volgend jaar.




PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class PriceCalculatorFactory {

    public static function create($year, $numberOfPrints)
    {
        switch ($year) {

            case 2016:
                return new PriceCalculator2016($numberOfPrints);
                break;

            case 2015:
                return new PriceCalculator2015($numberOfPrints);
                break;

            default:
                throw new Exception('Unknown price calculator!');
        }
    }
}


De factory, waar ik op basis van een jaar een bepaalde PriceCalculatorXXX teruggeef. Misschien kan dat netter op basis van een class_exists check, maar het idee is denk ik duidelijk. Eventueel kan dat ook op basis van datum uit database zoals je aangaf inderdaad.




PHP:
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
<?php
class PriceCalculator2015 implements PriceCalculatorInterface {

    protected $numberOfPrints;

    public function __construct($numberOfPrints)
    {
        $this->numberOfPrints = $numberOfPrints;
    }

    public function getPrice()
    {
        return $this->getSetupPrice() + $this->getPaperPrice() + $this->getInkPrice();
    }

    private function getSetupPrice()
    {
        return 125;
    }

    private function getPaperPrice()
    {
        return $this->numberOfPrints * 0.09;
    }

    private function getInkPrice()
    {
        return $this->numberOfPrints * 0.03;
    }
}


De prijs berekenen voor 2015.




PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class PriceCalculator2016 implements PriceCalculatorInterface {

    protected $numberOfPrints;

    public function __construct($numberOfPrints)
    {
        $this->numberOfPrints = $numberOfPrints;
    }

    public function getPrice()
    {
        return $this->numberOfPrints * 0.15;
    }
}


De prijs bereken voor 2016, maar dan anders. Gewoon een vaste prijs per printje in dit geval.




Verder
kwaakvaak_v2 schreef op vrijdag 17 april 2015 @ 16:23:
[...]

En als setup kosten nu ook gewoon een product met vaste waarde is en dus ook 0 (nul, niet null) is? Dan moet voor een simpele cost per afdruk prijs nog wel te doen zijn.
Pin0 schreef op vrijdag 17 april 2015 @ 16:29:
Wij slaan in onze webwinkel bij de orders een copy op van de producten. Voor de overzichten, facturen en andere berekeningen gebruiken we de waarden uit het copy object. We kunnen dan altijd de prijs in de database aanpassen zonder dat de order history wordt beïnvloed.
Het ging me vooral even om de 'structuur' van het geheel. Details over hoe ik uiteindelijk tot een prijs kom doen er nog even niet toe.

Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Pin0 schreef op vrijdag 17 april 2015 @ 16:29:
Wij slaan in onze webwinkel bij de orders een copy op van de producten. Voor de overzichten, facturen en andere berekeningen gebruiken we de waarden uit het copy object. We kunnen dan altijd de prijs in de database aanpassen zonder dat de order history wordt beïnvloed.
Dat moet je sowieso altijd doen. Maar als je wil kunnen variëren tussen berekeningswijzen (en dus niet alleen maar bedragen) dan zul je dus prijzen van die datums moeten hebben omdat je mogelijk een 'PriceCalculator' gebruikt die niet alle benodigde values in je "copy object" kan vinden (CalcA gebruikt bedragen A, B en C om een totaal te berekenen, CalcB gebruikt A, B en Z bijvoorbeeld; in je copy object zal logischerwijs enkel A, B en C aanwezig zijn (of je slaat bij elke factuur je complete prijzenstructuur op :X )).
TheNephilim schreef op vrijdag 17 april 2015 @ 17:23:
Misschien is het een wat grote lap code, maar ik wil toch graag weten of ik het goed begrepen heb:
Ik zie niet waarom $numberOfPrints in de constructor van de calculators meegegeven wordt (en dus ook aan de factory meegegeven moet worden). Waarom is dat geen argument voor de getPrice() method? This makes much more sense:

PHP:
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
<?php
interface PriceCalculatorInterface
{
    public function getPrice($numberOfPrints);
}

class PriceCalculatorFactory {

    public static function create($year)
    {
        switch ($year) {

            case 2016:
                return new PriceCalculator2016();

            case 2015:
                return new PriceCalculator2015();

            default:
                throw new Exception('Unknown price calculator!');
        }
    }
}

class PriceCalculator2015 implements PriceCalculatorInterface {

    public function getPrice($numberOfPrints)
    {
        return $this->getSetupPrice()
            + $this->getPaperPrice($numberOfPrints)
            + $this->getInkPrice($numberOfPrints);
    }

    private function getSetupPrice()
    {
        return 125;
    }

    private function getPaperPrice($numberOfPrints)
    {
        return $numberOfPrints * 0.09;
    }

    private function getInkPrice($numberOfPrints)
    {
        return $numberOfPrints * 0.03;
    }
}

class PriceCalculator2016 implements PriceCalculatorInterface {

    public function getPrice($numberOfPrints)
    {
        return $numberOfPrints * 0.15;
    }
}


offtopic:
BTW: break;s na een return zijn niet nodig ;)


Overigens is dit nog steeds een sterk vereendvoudigd voorbeeld; ik zou bijvoorbeeld

PHP:
1
2
3
4
interface PriceCalculatorInterface
{
    public function getPrice($numberOfPrints);
}

niet heel gauw doen; ik zou getPrice() eerder een $orderline argument o.i.d. meegeven; nu zit je vast aan een enkele "int" (voor zover type-safety in PHP uberhaupt een ding is...) namelijk $numberOfPrints waar getPrice() het maar mee moet doen. Als je getPrice() een object (zoals orderline, order of foobar) meegeeft ben je véél flexibeler later in je calculators als PriceCalculator2015 z'n prijs baseert op aantallen en PriceCalculator2017 op andere gegevens die onderdeel zijn van de order(regel) zoals papierkleur, full-color vs. b/w druk etc.

Wat voor object je precies meegeeft (en of "getPrice()" uberhaupt wel zo'n goede keuze is) is afhankelijk van wat je nu hebt / wat je structuur is etc. maar je kunt je zoiets voorstellen:

PHP:
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
class PriceCalculator2015 implements PriceCalculatorInterface {

    public function getPrice($orderline)
    {
        return $this->getSetupPrice()
            + $this->getPaperPrice($orderline->numberOfPrints)
            + $this->getInkPrice($orderline->numberOfPrints);
    }

    private function getSetupPrice()
    {
        return 125;
    }

    private function getPaperPrice($numberOfPrints)
    {
        return $numberOfPrints * 0.09;
    }

    private function getInkPrice($numberOfPrints)
    {
        return $numberOfPrints * 0.03;
    }
}

class PriceCalculator2016 implements PriceCalculatorInterface {

    public function getPrice($orderline)
    {
        return $orderline->numberOfPrints * 0.15;
    }
}


//New class! YAY!
class PriceCalculator2017 implements PriceCalculatorInterface {

    public function getPrice($orderline)
    {
        return $this->getSetupPrice()
            + $this->getPaperPrice($orderline->numberOfPrints, $orderline->paperColor)
            + $this->getInkPrice($orderline->numberOfPrints, $orderline->printType);
    }

    private function getSetupPrice()
    {
        return 50;
    }

    private function getPaperPrice($numberOfPrints, $paperColor)
    {
        switch ($paperColor) {
            case 'white':
                return $numberOfPrints * 0.09;
            case 'pink':
                return $numberOfPrints * 0.10;
            case 'yellow':
                return $numberOfPrints * 0.15;
            default:
                throw new Exception('Unknown papercolor!');
        }
    }

    private function getInkPrice($numberOfPrints, $printType)
    {
        switch ($printType) {
            case 'b/w':
                return $numberOfPrints * 0.03;
            case '2-tone':
                return $numberOfPrints * 0.08;
            case 'full-color':
                return $numberOfPrints * 0.15;
            default:
                throw new Exception('Unknown printtype!');
        }
    }
}


Als je dan in 2018 een nieuwe property paperWeight toevoegt aan een orderline (order, foobar) object dan blijven je oude calculators gewoon werken en kan je 2018 calculator gebruik maken van dit nieuwe gegeven om een prijs op te baseren.

Tot slot: Het is in de meeste talen gebruikelijk een interface te prefixen met I<something>; ik zou dus geen PriceCalculatorInterface doen maar IPriceCalculator. Laat maar, in PHP lijkt (op 't eerste gezicht na een korte google opdracht althans) een Interface-suffix gangbaar(der) te zijn dan een I-prefix.

[ Voor 93% gewijzigd door RobIII op 17-04-2015 19:28 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • Wmm
  • Registratie: Maart 2002
  • Laatst online: 11-09 14:30

Wmm

frickY schreef op vrijdag 17 april 2015 @ 16:02:
Toverwoord; Decorator-pattern

edit:
In combinatie met bovenstaand :Y)
Juist het Strategy pattern lijkt me.

Acties:
  • 0 Henk 'm!

  • TheNephilim
  • Registratie: September 2005
  • Laatst online: 12-09 14:37
RobIII schreef op vrijdag 17 april 2015 @ 18:52:

[...]

Ik zie niet waarom $numberOfPrints in de constructor van de calculators meegegeven wordt (en dus ook aan de factory meegegeven moet worden). Waarom is dat geen argument voor de getPrice() method? This makes much more sense:

[...]

Klopt, dat is een goed punt!
offtopic:
BTW: break;s na een return zijn niet nodig ;)
Oh, niet bij stil gestaan, maar inderdaad :+.
Overigens is dit nog steeds een sterk vereendvoudigd voorbeeld; ik zou bijvoorbeeld

PHP:
1
2
3
4
interface PriceCalculatorInterface
{
    public function getPrice($numberOfPrints);
}

niet heel gauw doen; ik zou getPrice() eerder een $orderline argument o.i.d. meegeven; nu zit je vast aan een enkele "int" (voor zover type-safety in PHP uberhaupt een ding is...) namelijk $numberOfPrints waar getPrice() het maar mee moet doen. Als je getPrice() een object (zoals orderline, order of foobar) meegeeft ben je véél flexibeler later in je calculators als PriceCalculator2015 z'n prijs baseert op aantallen en PriceCalculator2017 op andere gegevens die onderdeel zijn van de order(regel) zoals papierkleur, full-color vs. b/w druk etc.

Wat voor object je precies meegeeft (en of "getPrice()" uberhaupt wel zo'n goede keuze is) is afhankelijk van wat je nu hebt / wat je structuur is etc. maar je kunt je zoiets voorstellen:

[...]

Als je dan in 2018 een nieuwe property paperWeight toevoegt aan een orderline (order, foobar) object dan blijven je oude calculators gewoon werken en kan je 2018 calculator gebruik maken van dit nieuwe gegeven om een prijs op te baseren.
Dat is nog iets om over na te denken inderdaad. Een soort van $arguments, waarin je later nog dingen aan toe kan voegen of in ieder geval veranderen. Flexibeler dan vaste argumenten die direct te method in gaan in ieder geval.
Tot slot: Het is in de meeste talen gebruikelijk een interface te prefixen met I<something>; ik zou dus geen PriceCalculatorInterface doen maar IPriceCalculator. Laat maar, in PHP lijkt (op 't eerste gezicht na een korte google opdracht althans) een Interface-suffix gangbaar(der) te zijn dan een I-prefix.
Ja ik zag xxxFactory en wilde inderdaad meteen al xxxInterface doen. Na even zoeken zag ik vaak I prefixes, maar ook Interface suffixes. Zowel Factory als Interface als suffix leek me net zo duidelijk.

Bedankt voor de uitgebreide hulp! _/-\o_
Pagina: 1