Claudio Pattarello
Code Designer - cookbook
mercoledì 14 ottobre 2015
IOP vs OOP (if oriented programming vs object oriented programming)
#IAD14
Quante volte avete scritto codice trabbocante di IF? Quante volte per un design errato avete riempito il vostro codice di branch per gestire casi particolari? In questa sessione dimostreremo come è possibile ridurre o concentrare il numero di IF sfruttando le buone pratiche dell’ OOP e dei Design Pattern.
Grazie per aver partecipato alla sessione.
venerdì 29 novembre 2013
dal TDD al BDD
Durante l'Agile Day 2013 ho tenuto una sessione che
verte sull'adozione del BDD nello sviluppo/progettazione del software.
Il materiale della sessione "dal TDD al BDD":
Grazie per la partecipazione e il feedback.
lunedì 11 febbraio 2013
Extreme Design: una piccola provocazione (6° parte)
"Aggiungere
una nuova funzionalità non deve essere invasivo"
Come
possiamo evitare di violare il seguente principio?
Dobbiamo
rispettare un'altro principio:
"Le
operazioni da eseguire devono essere isolate nel contesto di
utilizzo"
Avevamo
definito il contratto di Account come segue:
public
interface IAccount
{
void
BalanceRequest(IBalanceResponse
balance);
void
Deposit(Amount
amount);
void
Withdrawal(Amount
amount, IWithdrawalResponse
withdrawal);
void
MovementsRequest(IMovementsResponse
movements);
}
Questo
implica che Account deve gestire direttamente l'elenco dei movimenti,
ma proviamo a separare le responsabilità isolando la funzionalità
MovemensRequest in un'altra intefaccia:
public
interface IMovementsAccount
{
void
MovementsRequest(IMovementsResponse
movements);
}
Ora
Account è definito come segue:
public
interface IAccount
{
void
BalanceRequest(IBalanceResponse
balance);
void
Deposit(Amount
amount);
void
Withdrawal(Amount
amount, IWithdrawalResponse
withdrawal);
}
public
class Account
: IAccount
{
private
Amount
current;
public
Account()
{
current
= new
Amount(0);
}
public
void
BalanceRequest(IBalanceResponse
balance)
{
balance.Return(current);
}
public
void
Deposit(Amount
amount)
{
current
= new
Amount(current.Value
+ amount.Value);
}
public
void
Withdrawal(Amount
amount, IWithdrawalResponse
withdrawal)
{
if
(current.Value >= amount.Value)
{
current
= new
Amount(current.Value
- amount.Value);
withdrawal.Completed();
return;
}
withdrawal.CreditInsufficient();
}
}
Siamo
tornati alla versione precedente alla richiesta di gestire i
movimenti.
Ora
vediamo come progettare un oggetto per gestire i movimenti eseguiti
sul conto corrente:
public
class MovementsAccount:
Account, IMovementsAccount,
IWithdrawalResponse
{
private
readonly
List<IMovementVisit>
movements = new List<IMovementVisit>();
private
Amount
amountWithdrawal;
public
override
void
Deposit(Amount
amount)
{
base.Deposit(amount);
movements.Add(new
DepositMovement(amount));
}
public
override
void
Withdrawal(Amount
amount, IWithdrawalResponse withdrawal)
{
amountWithdrawal
= amount;
base.Withdrawal(amount,
this);
}
void
IMovementsAccount.MovementsRequest(IMovementsResponse
movement)
{
movement.Return(movements);
}
void
IWithdrawalResponse.Completed()
{
movements.Add(new
WithdrawalMovement(amountWithdrawal));
}
void
IWithdrawalResponse.CreditInsufficient()
{
}
}
In
questa soluzione, ereditiamo dalla classe Account, facciamo l'override
dei metodi Deposit e Withdrawal che sono coinvolti nella fase di
registrazione dei movimenti.
Ovviamente
abbiamo dovuto modificare la visibilità di Deposit e Withdrawal
indicano che sono metodi virtuali. Quindi nella nostra progettazione
di Account dovevamo avere una sfera di cristallo per prevedere che in
futuro avremmo dovuto gestire i movimenti. Forse a questo punto è
bene definire anche BalanceRequest come virtuale? Allora tutti i
metodi dovrebbero essere virtuali?
Vediamo
come abbiamo gestito la registrazione dei movimenti e poi analizziamo
un'altra strada per evitare di creare i metodi virtuali.
Deposit
risulta essere molto intuitivo, si deve solo occupare di registrare
il movimento di deposito. Invece withdrawal ha una complicazione,
registriamo il movimento solo se l'importo richiesto è inferiore al
credito residuo. A questo punto creiamo un nuovo oggetto che decora
le notifiche di IWithdrawalResponse e
notifica all'oggetto richiedente il risultato del prelievo.
public
interface IWithdrawalResponseDispatcher
: IWithdrawalResponse
{
void
SetClients(params
IWithdrawalResponse[]
inners);
}
public
class
WithdrawalResponseDispatcher
: IWithdrawalResponseDispatcher
{
private
IWithdrawalResponse[]
inners = new
IWithdrawalResponse[0];
public
void
SetClients(params
IWithdrawalResponse[]
inners)
{
this.inners
= inners;
}
void
IWithdrawalResponse.Completed()
{
foreach
(var
inner in
inners)
inner.Completed();
}
void
IWithdrawalResponse.CreditInsufficient()
{
foreach
(var
inner in
inners)
inner.CreditInsufficient();
}
}
Notate
che l'oggetto non è altro che un Observer per le notifiche.
Aggiorniamo
MovementsAccount:
public
class MovementsAccount
: Account, IMovementsAccount,
IWithdrawalResponse
{
private
readonly
IWithdrawalResponseDispatcher
withdrawalResponseDispatcher;
private
readonly
List<IMovementVisit>
movements = new
List<IMovementVisit>();
private
Amount
amountWithdrawal;
public
MovementsAccount(IWithdrawalResponseDispatcher
withdrawalResponseDispatcher)
{
this.withdrawalResponseDispatcher
= withdrawalResponseDispatcher;
}
public
override
void
Deposit(Amount
amount)
{
base.Deposit(amount);
movements.Add(new
DepositMovement(amount));
}
public
override
void
Withdrawal(Amount
amount, IWithdrawalResponse
withdrawal)
{
amountWithdrawal
= amount;
withdrawalResponseDispatcher.SetClients(this,
withdrawal);
base.Withdrawal(amount,
withdrawalResponseDispatcher);
}
void
IMovementsAccount.MovementsRequest(IMovementsResponse
movement)
{
movement.Return(movements);
}
void
IWithdrawalResponse.Completed()
{
movements.Add(new
WithdrawalMovement(amountWithdrawal));
}
void
IWithdrawalResponse.CreditInsufficient()
{
}
}
A questo punto per ovviare i metodi virtuali potremmo seguire la
stessa procedura e creare una classe che decora Account e che
gestisca i movimenti:
public
class MovementsAccount
: IAccount, IMovementsAccount
{
private
readonly
IAccount
inner;
private
readonly IWithdrawalResponseDispatcher withdrawalResponseDispatcher;
private
readonly
List<IMovementVisit>
movements;
private Amount amountWithdrawal;
private Amount amountWithdrawal;
public
MovementsAccount(IAccount
inner, IWithdrawalResponseDispatcher withdrawalResponseDispatcher)
{
this.inner
= inner;
this.withdrawalResponseDispatcher = withdrawalResponseDispatcher;
movements
= new
List<IMovementVisit>();
}
public
void
BalanceRequest(IBalanceResponse
balance)
{
inner.BalanceRequest(balance);
}
public
void
Deposit(Amount
amount)
{
inner.Deposit(amount);
movements.Add(new
DepositMovement(amount));
}
public
void
Withdrawal(Amount
amount, IWithdrawalResponse
withdrawal)
{
amountWithdrawal = amount;
inner.Withdrawal(amount, withdrawalResponseDispatcher);
withdrawalResponseDispatcher.SetClients(this, withdrawal);
}
void
IMovementsAccount.MovementsRequest(IMovementsResponse
movement)
{
movement.Return(movements);
}
}
In
questo modo MovementsAccount contiene un' istanza di Account con la
quale interagisce senza la necessità di modificare i metodi di
Account creandoli virtuali.
Ora
l'oggetto Movements che gestiva la richiesta dei movimenti deve
interagire con IMovementsAccount, questo è corretto visto che
volevamo isolare le operazioni in base al contesto e alla
responsabilità.
public
class Movements
: IOperation, IMovementsResponse,
IMovementVisitor
{
private
readonly
IMovementsAccount
account;
private
readonly
IViewResponse
view;
public
Movements(IMovementsAccount
account, IViewResponse
view)
{
this.account
= account;
this.view
= view;
}
//...
}
Per
finire l'unica modifica “invasiva” che dobbiamo fare è cambiare
l'inizializzazione delle istanze di Account.
Prima
di isolare le responsabilità:
private
static void
Main()
{
var
account = new
Account();
var
view = new
View();
var
operations = new
IOperation[]
{
new
Balance(account,
view),
new
Deposit(account,
view),
new
Withdrawal(account,view),
new
Movements(account,view)
};
new
Program(new
Operations(view,
operations));
}
Dopo
la modifica con la decorazione:
private
static void
Main()
{
var
account = new
MovementsAccount(new
Account(),
new WithdrawalResponseDispatcher());
var
view = new
View();
var
operations = new
IOperation[]
{
new
Balance(account,
view),
new
Deposit(account,
view),
new
Withdrawal(account,view),
new
Movements(account,view)
};
new
Program(new
Operations(view,
operations));
}
Oppure
usando l'ereditarietà:
private
static void
Main()
{
var
account = new
MovementsAccount(new WithdrawalResponseDispatcher());
var
view = new
View();
var
operations = new
IOperation[]
{
new
Balance(account,
view),
new
Deposit(account,
view),
new
Withdrawal(account,view),
new
Movements(account,view)
};
new
Program(new
Operations(view,
operations));
}
Non
è molto importante disquisire se in questo caso è meglio usare
l'ereditarietà o la composizione per progettare l'oggetto
MovementsAccount.
Il
punto cardine rimane: possiamo fare sviluppi incrementali non
invasivi nel nostro codice se e solo se isoliamo le responsabilità e le
interazioni tra gli oggetti.
Questo è possibile se, mentre sviluppiamo, ricordiamo i seguenti principles:
Iscriviti a:
Post (Atom)