Come dividere le responsabilità della mia entità

6

Sto progettando BC per i codici promozionali. Funzionano così: L'amministratore può creare il codice promozionale specificando

  1. Dettagli (come codice e descrizione)
  2. Benefit (interfaccia per ValueObjects, ad esempio MoneyDiscountBenefit)
  3. Limitazioni d'uso - raccolta di AbstractUsageRestriction, ciascuna limitare l'uso del codice promozionale, ad esempio:

    • PromoCode può essere utilizzato solo dall'utente sigle
    • Il codice promozionale può essere utilizzato su massimo 3 articoli dell'ordine da ciascun utente
    • Il codice promozionale può essere utilizzato su un massimo di 10 articoli dell'ordine
    • Il codice promozionale può essere utilizzato solo per alcuni OrderItem (alcuni tipi specifici, o dove il prezzo è superiore a qualche limite ecc.)

Il Cliente può applicare questo codice promozionale sul suo Ordine (alcuni VO in questo BC), quindi viene applicato su tutti gli OrderItem che soddisfano tutte le restrizioni. Quindi, in caso di evento OrderPaid, PromoCode viene "utilizzato" su ogni restrizione di articoli da articolo. "Usato", perché questo BC dal punto di vista del cliente si limita a gestire il codice promozionale come applicato / usato e quindi il successivo controllo delle restrizioni (da parte di chiunque su questo codice promozionale) durante l'applicazione / utilizzo può essere influenzato da questo uso (se desiderato), ad esempio con UniqueUsageRestriction se un utente usa un codice promozionale, un altro non può più - la restrizione genera eccezioni.

Ora, la mia entità ha questo aspetto:

<?php

class PromoCode
{

    /** @var PromoCodeId */
    private $id;

    /** @var Details */
    private $details;

    /** @var Benefit */
    private $benefit;

    /** @var UsageRestrictions */
    private $usageRestrictions;

    /** @var PromoCodeUsesCollection */
    private $promoCodeUsesCollection;

    /** @var PromoCodeAppliesCollection */
    private $promoCodeAppliesCollection;

    public function __construct(PromoCodeId $id, Details $details, Benefit $benefit, ?UsageRestrictions $usageRestrictions = null)
    {
        $this->id                         = $id;
        $this->details                    = $details;
        $this->benefit                    = $benefit;
        $this->usageRestrictions          = $usageRestrictions ?: new UsageRestrictions();
        $this->promoCodeUsesCollection    = new PromoCodeUsesCollection();
        $this->promoCodeAppliesCollection = new PromoCodeAppliesCollection();
    }

    public function getId(): PromoCodeId
    {
        return $this->id;
    }

    public function getBenefit(): Benefit
    {
        return $this->benefit;
    }

    public function getOrderItemIdsOnWhichWasAppliedWith(OrderId $orderId){
        $this->promoCodeAppliesCollection->getOrderItemIdsWith($orderId);
    }

    public function applyFor(Order $order): void
    {
        $this->checkApplicabilityForOrder($order);

        foreach ($order->getOrderItems() as $orderItem) {
            $this->checkApplicabilityForOrderItem($order, $orderItem);
            $promoCodeApply = new PromoCodeApply($order, $orderItem);
            $this->promoCodeAppliesCollection->addApply($promoCodeApply);
        }
    }

    public function useFor(Order $order): void
    {
        $this->checkApplicabilityForOrder($order);

        foreach ($order->getOrderItems() as $orderItem) {
            $this->checkApplicabilityForOrderItem($order, $orderItem);
            $promoCodeUse = new PromoCodeUse($order, $orderItem);
            $this->promoCodeUsesCollection->addUse($promoCodeUse);
        }
    }

    public function countAppliesForOrder(OrderId $orderId): int
    {
        return $this->promoCodeAppliesCollection->countAppliesForOrder($orderId);
    }

    public function countOrdersWithUseByUser(UserId $userId): int
    {
        return $this->promoCodeUsesCollection->countOrdersWithUseByUser($userId);
    }

    public function canBeUniqueForUser(UserId $userId): bool
    {
        return $this->promoCodeUsesCollection->canBeUniqueFor($userId);
    }

    public function hasCode($code)
    {
        return $this->details->getCode()->getValue() === $code;
    }

    private function checkApplicabilityForOrder(Order $order): void
    {
        $this->usageRestrictions->checkApplicabilityForOrder($this, $order);
    }

    private function checkApplicabilityForOrderItem(Order $order, OrderItem $orderItem): void
    {
        $this->usageRestrictions->checkApplicabilityForOrderItem($this, $order, $orderItem);
    }
}

Ho la sensazione che sappia e faccia troppo, ma come dividerlo?

Non mi piace l'uso e l'applicazione delle funzioni in qualche modo, forse dovrebbe fare qualche strategia? Quindi lo rifasero su qualcosa come:

    public function applyFor(Order $order): void
    {
        $this->applyPromoCodeStrategy->applyForOrder($this, $order, $this->usageRestrictions, $this->promoCodeAppliesCollection)
    }

Ma non mi piace il fatto che sto passando a) quattro parametri, b) Quella strategia ora fa qualcosa con lo stato di promoCodeAppliesCollection proprietà senza che PromoCode lo sappia.

È in qualche modo strano che PromoCode risponde a domande come countOrdersWithUseByUser o canBeUniqueForUser ecc. Solo per riempire le domande di alcune restrizioni specifiche in UsageRestrictions. Avrei bisogno di creare un oggetto mutabile nell'entità PromoCode (con quelle raccolte con applica e usi) che non avrebbe alcun id, e gestivo tutte le cose intorno usando e controllando le restrizioni separatamente, ma non ho visto qualcosa di simile ovunque .

Quali approcci posso utilizzare per migliorare questo design? O puoi nominare qualche odore di design con possibili soluzioni qui?

    
posta Tom 29.04.2017 - 03:53
fonte

2 risposte

1

Ok, quindi non è scienza missilistica come pensavo. La soluzione è semplicemente introdurre una nuova entità (PromoCodeUsage) che è nascosta in PromoCode, è la sua implementazione e si prende cura della responsabilità di come il codice promozionale viene usato e applicato :). Quindi ora sembra bello, pulito e brillante:

<?php declare(strict_types = 1);

class PromoCode
{

    /** @var PromoCodeId */
    private $id;

    /** @var Details */
    private $details;

    /** @var Benefit */
    private $benefit;

    /** @var PromoCodeUsage */
    private $promoCodeUsage;

    public function __construct(PromoCodeId $id, Details $details, Benefit $benefit, ?UsageRestrictions $usageRestrictions = null)
    {
        $this->id             = $id;
        $this->details        = $details;
        $this->benefit        = $benefit;
        $this->promoCodeUsage = new PromoCodeUsage($usageRestrictions);
    }

    public function getId(): PromoCodeId
    {
        return $this->id;
    }

    public function getBenefit(): Benefit
    {
        return $this->benefit;
    }

    public function getCode(): Code
    {
        return $this->details->getCode();
    }

    public function hasCode($code)
    {
        return $this->details->getCode()->getValue() === $code;
    }

    public function applyFor(Order $order): void
    {
        $this->promoCodeUsage->applyFor($order);
    }

    public function useFor(Order $order): void
    {
        $this->promoCodeUsage->useFor($order);
    }

    public function countAppliesForOrder(OrderId $orderId): int
    {
        return $this->promoCodeUsage->countAppliesForOrder($orderId);
    }

    public function countOrdersWithUseByUser(UserId $userId): int
    {
        return $this->promoCodeUsage->countOrdersWithUseByUser($userId);
    }

}
    
risposta data 03.05.2017 - 17:09
fonte
2

Per me l'odore sono le varie funzioni che passano semplicemente la chiamata lungo la catena verso gli oggetti secondari, useageRestrictions, usa e applica la raccolta.

Ovviamente questo espande il numero di funzioni e quindi la responsabilità del tuo oggetto.

Considera un design più funzionale in cui l'entità dei dati per la promozione è separata dall'oggetto (o dagli oggetti) che valuta il prezzo di un paniere.

Sebbene questo possa sembrare meno OO, in effetti hai già questi oggetti nascosti dalla tua entità. Se li esponi e dai loro nomi migliori, Till? Check-out? potresti trovare che il design è più chiaro

    
risposta data 29.04.2017 - 06:16
fonte