Gran parte del mio codice sembra attraversare la seguente evoluzione -
Iterazione 1
interface IAccountManager {
void Import(string importData);
}
class SalesAccountManager : IAccountManager {
private int GetAccountId(string data) {
//Some data conversions, database references, calculations etc
return accountId;
}
private int GetAccountName(string data) {
//Some data conversions, database references, calculations etc
return name;
}
public void Import(string importData) {
SalesAccount a = new SalesAccount();
a.Id = GetAccountId(importData.Substring(11, 55); //hardcoding just to keep this example simple
a.Name = GetAccountName(importData);
a.Save();
}
}
Funziona tutto bene, tutti sono contenti ma poi trovo che devo aggiungere un BusinessAccountManager
. Dopo un'ulteriore ispezione, sembra che i metodi GetAccountId
e GetAccountName
non cambieranno. Quindi, ciò che sembra una soluzione naturale? Creiamo solo una classe base astratta che eredita da IAccountManager
e rendiamo questi metodi protetti e rendiamo Import
astratto. Ora, abbiamo -
Iterazione 2
abstract class AbstractAccountManage : IAccountManager {
protected int GetAccountId(string data) {...No change here...}
protected int GetAccountName(string data) {...No change here...}
public abstract void Import(string importData);
}
class SalesAccountManager : AbstractAccountManager {
public void Import(string importData) {...No change here...}
}
class BusinessAccountManager : AbstractAccountManager {
public void Import(string importData) {
BusinessAccount a = new BusinessAccount();
a.Id = GetAccountId(importData.Substring(11, 55); //hardcoding just to keep this example simple
a.Name = GetAccountName(importData);
a.Save();
}
}
Ma sembra che questa classe astratta sia fondamentalmente una classe di supporto, quindi avrei potuto fare quanto segue:
Iteration 3
interface IAccountManagerStrategy {
public int GetAccountId(string data);
public int GetAccountName(string data);
}
class AccountManagerStrategy : IAccountManagerStrategy {
public int GetAccountId(string data) {...No change here...}
public int GetAccountName(string data) {...No change here...}
}
interface IAccountManager {
void Import(string importData);
}
class SalesAccountManager : IAccountManager {
private IAccountManagerStrategy accountManagerStrategy {get;}
public SalesAccountManager(IAccountManagerStrategy accountManagerStrategy) {
this.accountManagerStrategy = accountManagerStrategy;
}
public void Import(string importData) {
SalesAccount a = new SalesAccount();
a.Id = accountManagerStrategy.GetAccountId(importData.Substring(11, 55); //hardcoding just to keep this example simple
a.Name = accountManagerStrategy.GetAccountName(importData);
a.Save();
}
}
class BusinessAccountManager : AbstractAccountManager {
//Similar to above but for a BusinessAccount
}
Nota: l'implementazione dei metodi GetAccountId
e GetAccountName
non è molto probabile che cambi (ma non si sa mai).
Con lo sfondo sopra -
Domande
-
Da un punto di vista dell'architettura software, è un buon approccio per refactoring tali classi astratte che fungono da repository di metodi privati condivisi (simili a quelli precedenti) in classi helper (o pattern di strategia, se vogliamo chiamarlo così ) come visto in Iteration 3? Questa domanda non è per questo caso specifico ma in generale.
-
Se è un buon approccio per fare questo refactoring, allora qual è l'uso reale delle classi astratte e la parola chiave
protected
per quanto riguarda il design / architettura del software? -
Una classe astratta come
AbstractAccountManager
in precedenza violerebbe SRP dopo che i metodi sono stati cambiati da privato a protetto (suppongo si possa sostenere che non fa più solo una cosa, ma è difficile dire quando fermarsi identificando quale dovrebbe essere una singola responsabilità)?
Nota: questa domanda non riguarda la composizione rispetto al polimorfismo. Si tratta di capire l'uso corretto di abstract
e capire perché o perché non refactoring i metodi concreti di una classe astratta in classi helper / utility.