simple design/programming nuggets
Post on 03-Jul-2015
715 Views
Preview:
TRANSCRIPT
Simple Programming/Design Tidbits
Banking application
Customer, Accounts, Transactions, ATM,
All transactions at an ATMclass AccountService {
List<Transaction> transactionsAt(int accountId, ATM atm) {List<Transaction> transactionList = new List<Transaction>();for (Transaction txn : transactions)
if (transaction.wasAtLocation(atm.getLocationId())) transactionList.add(txn);
return transactionList;}
}
class Transaction { ATM atm; boolean wasAtLocation(int locationId) { return atm.getLocationId() == locationId;
}}
All transactions at an ATMAll transactions at an ATMclass AccountService {
List<Transaction> transactionsAt(int accountId, ATM atm) {List<Transaction> transactionList = new List<Transaction>();for (Transaction txn : transactions)
if (transaction.wasAt(atm))if (transaction.wasAt(atm)) transactionList.add(txn);
return transactionList;}
}
class Transaction {ATM atm;boolean wasAt(ATM atm) {boolean wasAt(ATM atm) {
return this.atm.equals(locationId);return this.atm.equals(locationId);}
}
Pass object not its data
Encasulation can be broken across methods
class Customer {static double eligibilityForHomeLoan = 500000;static double eligibilityForPersonalLoan = 10000;double salary;
boolean targetForHomeLoan() { return salary > eligibilityForHomeLoan; }
boolean targetForPersonalLoan() { return salary > eligibilityForPersonalLoan; }}
Should target a customer for loan?
Should target a customer for loan?Should target a customer for loan?class Customer {
Salary salary;Salary salary; boolean targetForHomeLoan() { return salary.isEligibleForHomeLoan();return salary.isEligibleForHomeLoan(); }}class Salary {class Salary {
double amount;double amount;static double eligibilityForHomeLoan = 500000;static double eligibilityForHomeLoan = 500000;
Salary(double amount) {Salary(double amount) { this.amount = amount;this.amount = amount; }} boolean isEligibleForLoan() {boolean isEligibleForLoan() { return amount > eligibilityForHomeLoan.amount;return amount > eligibilityForHomeLoan.amount; }}}}
Avoid primitives in your domain
Primitives not only language but can be domain primitives e.g. Money/Date/Currency
class Customer { Set<Account> accounts;
Set<Account> getAccounts() { return accounts; }}
class CustomerService {void addAccount(Customer customer) {
Account account = new Account();customer.getAccounts().add(account);
}}
Open new account for a customer
class Customer { Set<Account> accounts; Account[] getAccounts() {Account[] getAccounts() { return accounts.toArray();return accounts.toArray(); }}
void addNewAccount() {void addNewAccount() {Account account = new Account();Account account = new Account();accounts.add(account);accounts.add(account);
}}}class CustomerService {
void addAccount(Customer customer) {customer.addNewAccount();customer.addNewAccount();
}}
Open new account for a customerOpen new account for a customer
Collections are mutable, donot expose them
class Customer { Set<Account> accounts;
Money balance() {Money total = Money.Zero();
for (Account account : accounts) {total += account.getBalance();
}return total;
}
Money inactiveAccounts() { for (Account account : accounts) {
if (account.isInactive())inactiveAccounts.add(account);
} }}
Customer's accounts
class Customer {Accounts accounts;Accounts accounts;
}class Accountsclass Accounts {
Set<Account> accounts;Set<Account> accounts;Money total = Money.Zero();Money balance() {
for (Account account : accounts) {total += account.getBalance();
}return total;
} Money inactiveAccounts() { for (Account account : accounts) {
if (account.isInactive())inactiveAccounts.add(account);
} }}
Customer's accounts
Collections are primitives
Create domain specific collections
class Account {InterestPlan interestPlan;Money bal;
Money applyInterest() { switch (interestPlan) {
case Simple: interest = new SICalc().calculate(bal);case Compound: interest = new CICalc().calculate(bal);......
}bal += interest;
}}
enum InterestPlan {Simple, Compound, InflationAdjusted, DurationDependent
}
Apply interest
class Account {InterestPlan interestPlan;Money bal;static Map calculators = {static Map calculators = {
Simple => new SICalc(), Simple => new SICalc(), Compound => new CICalc();Compound => new CICalc();..........
}}
Money applyInterest() {interest = calculators[interestPlan].calculate();interest = calculators[interestPlan].calculate();
}}
Apply interestApply interest
Externalize if conditions
Can use for functions as value in map as well
class Account {State state;
boolean transactionsAllowed() { return state == State.Approved || state == State.Active; }
boolean onlineTransactionAllowed() {return state == State.Active || state == State.Locked;
}}
enum State {PendingApproval, Approved, Locked, Active, Closed
}
Account Security
class Account {State state;List transactionsAllowed = {State.Approved, State.Active};List transactionsAllowed = {State.Approved, State.Active};List onlineTransactionsAllowed = {State.Locked, State.Active};List onlineTransactionsAllowed = {State.Locked, State.Active};
boolean transactionsAllowed() { return transactionsAllowed.contains(state)transactionsAllowed.contains(state); }
boolean onlineTransactionAllowed() { return onlineTransactionsAllowed.contains(state)onlineTransactionsAllowed.contains(state);
}}
enum State {PendingApproval, Approved, Locked, Active, Closed
}
Account SecurityAccount Security
void monthlyReport() {TransactionRepository repo = new TransactionRepository(...);var list = repo.getBiggerTransactionsNotTransferredToSelf();......
}
class TransactionRepository { Transactions getBiggerTransactionsNotTransferredToSelf() {
Transactions transactions = loadTransactions(customerId);....logic...
}}
Suspicious Transactions
void monthlyReport() {TransactionRepository repo = new TransactionRepository(...);var list = repo.suspiciousTransactions()repo.suspiciousTransactions();......
}
class TransactionRepository { Transactions suspiciousTransactions()suspiciousTransactions() {
Transactions transactions = loadTransactions(customerId);....logic...
}}
Suspicious TransactionsSuspicious Transactions
Tell what not how
Keep implementation encapsulation
Seen more when doing TDD
class Customer {String name;Date dateOfBirth;Accounts accounts;Money salaryForLoanCalculations;
boolean targetForHomeLoan() { return salaryForLoanCalculations.isEligibleForHomeLoan(); }
boolean targetForPersonalLoan() { return salaryForLoanCalculations.isEligibileForPersLoan(); }}
Customer
class Customer {String name;Date dateOfBirth;Accounts accounts;Money salary;Money salary;
boolean targetForHomeLoan() { return salary.isEligibleForHomeLoan();return salary.isEligibleForHomeLoan(); }
boolean targetForPersonalLoan() { return salary.isEligibileForPersonalLoan();return salary.isEligibileForPersonalLoan(); }}
CustomerCustomer
Store what not what for
Seen more when doing TDD
class Customer {String name;Accounts accounts;Money salary;
boolean isLoyal() { if (salary > 20000 && accounts.balance() > 100000) {
return true;}return false;
}}
Is Loyal Customer?
class Customer {String name;Accounts accounts;Money salary;
boolean isLoyal() { return salary > 20000 && accounts.balance() > 100000);return salary > 20000 && accounts.balance() > 100000); }}
Is Loyal Customer?Is Loyal Customer?
class Customer {Accounts accounts;boolean isLoyal() {
....}
void issueCard(String accountNumber) {Account account = accounts.get(accountNumber);if (isLoyal() == true)
account.issuePlatinum();else
account.issueOfLastType();}
}
Issue platinum card?
class Customer {Accounts accounts;boolean isLoyal() {
....}
void issueCard(String accountNumber) {Account account = accounts.get(accountNumber);if (isLoyal())if (isLoyal())
account.issuePlatinum();else
account.issueOfLastType();}
}
Issue platinum card?Issue platinum card?
class Customer {String name;Date dateOfBirth;
public void validateForNew() throws ValidationException {ValidationException exception = new ValidationException();if (name == null || name.isEmpty())
exception.add(“Name not specified”);if (dateOfBirth == null)
exception.add(“Date of birth not specified”);else if (dateOfBirth.isBefore(1990))
exception.add(“below 20 years are not eligible”);if (exception.hasErrors()) {
throw exception;}
}}
New Customer
class Customer {String name;Date dateOfBirth;
public ValidationError validateForNew() {public ValidationError validateForNew() {ValidationError error = new ValidationError();ValidationError error = new ValidationError();if (name == null || name.isEmpty())
error.add(“Name not specified”);if (dateOfBirth == null)
error.add(“Date of birth not specified”);else if (dateOfBirth.isBefore(1990))
error.add(“below 20 years are not eligible”);
return error;return error;}
}
New CustomerNew Customer
Error during validation is not exceptional scenario
public class Customer { public void gotMarried() { title = "Mrs"; }
public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false;
Customer customer = (Customer) o;
if (!firstName.equals(customer.firstName)) return false; if (!lastName.equals(customer.lastName)) return false; if (!title.equals(customer.title)) return false;
return true; }
public int hashCode() { int result = firstName.hashCode(); result = 31 * result + lastName.hashCode(); result = 31 * result + title.hashCode(); return result; }}
Customer got married
public void test() { Customer customer = new Customer("foo", "bar", "Miss"); Map map = new HashMap(); map.put(customer, "rich");
assertTrue(map.get(customer) != null); customer.gotMarried(); assertTrue(map.get(customer) != null); <-- FailsassertTrue(map.get(customer) != null); <-- Fails }
Customer got married
Don't use mutable fields for hashcode
Account account = AccountRepository.load(1);....cleanUp(account);
public static void cleanUp(Account account) { if (null != account) { deleteAccount(account, null); account = null; } }
Get rid of large object from memory
Objects references are always passed by value
Thats why C# has explicit pass by ref (not recommending essentially)
Class Customer {Name name;Date dateOfBirth;Accounts accounts;
Name getName() {if (name == null) name = new Name();return name;
}}
Get customer's name
Do not change data in getter, it deceives
ToString() is object's user interface
Reserve it and use for debugging only
public class Customer { List<Account> accounts = new ArrayList<Account>();
public void isAccountInactive(String accountNumber, int maxInactivityAllowed) { int i = accounts.indexOf(new Account(accountNumber)); Account account = accounts.get(i); inactive(account, maxInactivityAllowed); }
private static boolean inactive(Account account, int maxInactivityAllowed) { return account.getLastActivity().within(maxInactivityAllowed); }}
What can we do here?
public class Customer { List<Account> accounts = new ArrayList<Account>();
public void isAccountInactive(String accountNumber, int maxInactivityAllowed) { int i = accounts.indexOf(new Account(accountNumber)); Account account = accounts.get(i); account.inactive(maxInactivityAllowed);account.inactive(maxInactivityAllowed); }}
Look for staticsLook for statics
Look out for static methods and hints (by IDE) to make methods static
Use the suggestion by not accepting it
class Account {Transactions transactions;Customer customer;
boolean suggestLinkingOfAccounts() { if (transactions != null) { Transactions customerXAs = transactions.with(customer); return customerXAs.totalAmount() > 10000; } return false; }}
Suggest linking of accounts?
class Account {Transactions transactions;Customer customer;
boolean suggestLinkingOfAccounts() { if (transactions != null) {
return transactions.withAndInExcessOf(customer, 10000);return transactions.withAndInExcessOf(customer, 10000); } return false; }}
Suggest linking of accounts?Suggest linking of accounts?
Tell don't ask at second level(unless for performance reasons)
class Customer {Transactions todaysTransactions;Accounts accounts;
public Customer(Accounts account, Date date) {this.accounts = accounts;todaysTransactions = accounts.TransactionDoneOn(date);
}
Transactions todaysTransaction() {Return todaysTransactions;
}}
Today's transactions
class Customer {Accounts accounts;
public Customer(Accounts account)public Customer(Accounts account) {this.accounts = accounts;
}
Transactions allTransactionsOn(Date date) {Transactions allTransactionsOn(Date date) {Return accounts.TransactionDoneOn(date);Return accounts.TransactionDoneOn(date);
}}}
Today's transactions
Model state based on domain
State shouldn't be based on object's usage, methods are for that
Suggestions might not always apply
top related