public
interface IMovementsResponse
{
void
Return(IEnumerable<IMovementVisit>
movements);
}
Come
per le altre operazioni dobbiamo sottoscrivere il contratto con
IOperation e implementiamo IMovementsResponse per restituire l'elenco
dei movimenti registrati in Account.
public
class Movements
: IOperation, IMovementsResponse, IMovementVisitor
{
private
readonly
IAccount
account;
private
readonly
IViewResponse
view;
public
Movements(IAccount
account, IViewResponse
view)
{
this.account
= account;
this.view
= view;
}
public
void
Display(Action<string>
action)
{
action("Movimenti");
}
public
void
Execute()
{
account.MovementsRequest(this);
}
void
IMovementsResponse.Return(IEnumerable<IMovementVisit>
movements)
{
foreach
(var
movement in
movements)
movement.Accept(this);
}
void
IMovementVisitor.Visit(WithdrawalMovement
movement)
{
view.Response(string.Format("{0}
- PRELIEVO per euro: {1}",
movement.Date,
movement.Value));
}
void
IMovementVisitor.Visit(DepositMovement
movement)
{
view.Response(string.Format("{0}
- DEPOSITO per euro: {1}",
movement.Date,
movement.Value));
}
}
Notiamo
che dopo aver ricevuto la lista dei movimenti, per ogni movimento
verifichiamo il tipo e notifichiamo all'operatore i dati del
movimento. Per fare questo definiamo due tipologie di movimenti:
public
abstract class
Movement
{
private
readonly
Amount
amount;
protected
Movement(Amount
amount)
{
this.amount
= amount;
Date
= DateTime.Now;
}
public
decimal
Value { get
{ return
amount.Value; } }
public
DateTime
Date { get;
private
set;
}
}
public
class
DepositMovement
: Movement,
IMovementVisit
{
public
DepositMovement(Amount
amount) : base(amount)
{
}
void
IMovementVisit.Accept(IMovementVisitor
visitor)
{
visitor.Visit(this);
}
}
public
class
WithdrawalMovement
: Movement,
IMovementVisit
{
public
WithdrawalMovement(Amount
amount) :
base(amount)
{
}
void
IMovementVisit.Accept(IMovementVisitor
visitor)
{
visitor.Visit(this);
}
}
Usiamo
il pattern Visitor per capire quale tipo di movimento stiamo iterando
e come deve essere notificato il suo stato.
Per
ottenere questo risultato, ogni movimento deve implementare la
seguente interfaccia
public
interface
IMovementVisit
{
void
Accept(IMovementVisitor
visitor);
}
Mentre
per intercettare il tipo di movimento in fase di analisi, l'oggetto
Movements implementa l'interfaccia:
public
interface IMovementVisitor
{
void
Visit(WithdrawalMovement
movement);
void
Visit(DepositMovement
movement);
}
Ora
aggiorniamo l'oggetto Account per gestire i movimenti:
public
interface IAccount
{
void
BalanceRequest(IBalanceResponse
balance);
void
Deposit(Amount
amount);
void
Withdrawal(Amount
amount, IWithdrawalResponse
withdrawal);
void
MovementsRequest(IMovementsResponse
movements);
}
public
class Account
: IAccount
{
private
Amount
current;
private
readonly
List<IMovementVisit>
movements;
public
Account()
{
current
= new
Amount(0);
movements
= new
List<IMovementVisit>();
}
public
void
BalanceRequest(IBalanceResponse
balance)
{
balance.Return(current);
}
public
void
Deposit(Amount
amount)
{
current
= new
Amount(current.Value
+ amount.Value);
movements.Add(new
DepositMovement(amount));
}
public
void
Withdrawal(Amount
amount, IWithdrawalResponse
withdrawal)
{
if
(current.Value >= amount.Value)
{
current
= new
Amount(current.Value
- amount.Value);
movements.Add(new
WithdrawalMovement(amount));
withdrawal.Completed();
return;
}
withdrawal.CreditInsufficient();
}
public
void
MovementsRequest(IMovementsResponse
movement)
{
movement.Return(movements);
}
}
Vediamo
lo stack per capire meglio la gestione dei movimenti. Facciamo un
deposito e poi un prelievo, al termine delle due operazioni
richiediamo la lista dei movimenti:
Movements.IMovementVisitor.Visit(WithdrawalMovement movement)
WithdrawalMovement.IMovementVisit.Accept(IMovementVisitor visitor)
Movements.IMovementVisitor.Visit(DepositMovement
movement)
DepositMovement.IMovementVisit.Accept(IMovementVisitor
visitor)
Movements.IMovementsResponse.Return(IEnumerable<IMovementVisit> movements)
Movements.IMovementsResponse.Return(IEnumerable<IMovementVisit> movements)
Account.MovementsRequest(IMovementsResponse
movement)
Movements.Execute()
Operations.IOperationsInput.Return(string
value)
View.IViewOperations.Request(IOperationsInput operations)
Operations.Display()
Come
per i casi precedenti siamo stati abbastanza ligi nel rispettare le
limitazioni che ci siamo prefissati, ma questa volta abbiamo fatto
una violazione. Infatti l'aggiunta dei movimenti ha implicato la
modifica del codice esistente.
Sono
state inserite due righe di codice rispettivamente in:
public
void
Deposit(Amount
amount)
{
current
= new
Amount(current.Value
+ amount.Value);
movements.Add(new
DepositMovement(amount));
}
public
void
Withdrawal(Amount
amount, IWithdrawalResponse
withdrawal)
{
if
(current.Value >= amount.Value)
{
current
= new
Amount(current.Value
- amount.Value);
movements.Add(new
WithdrawalMovement(amount));
withdrawal.Completed();
return;
}
withdrawal.CreditInsufficient();
}
Comportando
la seguente violazione:
"l'aggiunta
di una nuova operazione non deve essere invasiva"
Ovvero
abbiamo violato il principle:
Nel
prossimo post vedremo come evitare questa violazione.
In
conclusione: siamo riusciti a sviluppare una soluzione che
rispettasse alcune regole per:
- avere un buon design,
- isolare le responsabilità,
- permettere di aggiungere funzionalità progressivamente
- non intaccare il codice esistente
- ridurre i rami di if solo a logica di confronto (i.e. Account.Withdrawal)
Sono
principi che dovrebbero essere sempre ricordati quando progettiamo del codice. Ovviamente questo comporta una forte disciplina e uno
sforzo nella stesura del codice, ma la pulizia ottenuta ripaga gli
sforzi.
Analizzando
il risultato finale ci accorgeremo che per implementare il tutto
abbiamo creato 15 interfacce e 11 classi, in un altro post cercheremo
di implementare lo stesso risultato ma con un codice più "snello".