24. Paulo Clavijo @pclavijo
More of …
- Good names
- SOLID code
- Value objects
- Pure functions
...
Less of …
- Duplicate code
- Long Method
- Data Clumps
- Message Chains
...
27. Paulo Clavijo @pclavijo
class Login {
private ContactInfo contactInfo;
public boolean authenticate(String userName, String password) {
// logic to validate that password:
// is at least 8 characters long
// contains a capital letter, a lowercase, a number …
…
// logic to verify if password matches user password
…
}
28. Paulo Clavijo @pclavijo
class User {
private ContactInfo contactInfo;
public User(ContactInfo contactInfo) {
this.contactInfo = contactInfo;
}
public String getFullAddress() {
String fullAddress = contactInfo.getStreetName() + " " +
contactInfo.getStreetNumber() + ", " +
contactInfo.getZipCode() + ", " +
contactInfo.getCity() + ", " +
contactInfo.getCountry();
return fullAddress;
}
29. Paulo Clavijo @pclavijo
class Invoice {
private Customer customer;
public Invoice(Customer customer) {
this.customer = customer;
}
public double getShippingCost() {
if (customer.getAddress().getCountry().isInEurope())
return STANDARD_SHIPPING_COST;
else
return OUTSIDE_EU_SHIPPING_COST;
}
...
}
37. Paulo Clavijo @pclavijo
function getPayAmount() {
if (isDead) return deadAmount();
if (isSeparated) return separatedAmount();
if (isRetired) return retiredAmount();
return normalPayAmount();
}
function getPayAmount() {
let result;
if (isDead)
result = deadAmount();
else {
if (isSeparated)
result = separatedAmount();
else {
if (isRetired)
result = retiredAmount();
else
result = normalPayAmount();
}
}
return result;
}
38. Paulo Clavijo @pclavijo
if (summer())
charge = summerCharge();
else
charge = regularCharge();
if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd))
charge = quantity * plan.summerRate;
else
charge = quantity * plan.regularRate + plan.regularServiceCharge;
40. Paulo Clavijo @pclavijo
function amountInvoiced(aDateRange) {...}
function amountReceived(aDateRange) {...}
function amountOverdue(aDateRange) {...}
function amountInvoiced(startDate, endDate) {...}
function amountReceived(startDate, endDate) {...}
function amountOverdue(startDate, endDate) {...}
41. Paulo Clavijo @pclavijo
class Scorer {
constructor(candidate, medicalExam, scoringGuide) {
this._candidate = candidate;
this._medicalExam = medicalExam;
this._scoringGuide = scoringGuide;
}
execute() {
this._result = 0;
this._healthLevel = 0;
// long body code
}
}
function score(candidate, medicalExam, scoringGuide) {
let result = 0;
let healthLevel = 0;
// long body code
}
42. Paulo Clavijo @pclavijo
function setHeight(value) { this._height = value; }
function setWidth (value) { this._width = value; }
function setDimension(name, value) {
if (name === "height") {
this._height = value;
return;
}
if (name === "width") {
this._width = value;
return;
}
}
43. Paulo Clavijo @pclavijo
function totalOutstanding() {
return customer.invoices
.reduce((total, each) => each.amount + total, 0);
}
function sendBill() {
emailGateway.send(formatBill(customer));
}
function getTotalOutstandingAndSendBill() {
const result = customer.invoices
.reduce((total, each) => each.amount + total, 0);
sendBill();
return result;
}
44. Paulo Clavijo @pclavijo
const names = input
.filter(i => i.job === "programmer")
.map(i => i.name);
const names = [];
for (const i of input) {
if (i.job === "programmer")
names.push(i.name);
}
45. Paulo Clavijo @pclavijo
class EuropeanSwallow {
get plumage() { return 'average'; }
}
class AfricanSwallow {
get plumage() { return (this.numberOfCoconuts > 2) ? 'tired' : 'average'; }
}
class NorwegianBlueParrot {
get plumage() { return (this.voltage > 100) ? 'scorched' : 'beautiful'; }
}
switch (bird.type) {
case 'EuropeanSwallow':
return "average";
case 'AfricanSwallow':
return (bird.numberOfCoconuts > 2) ? "tired" : "average";
case 'NorwegianBlueParrot':
return (bird.voltage > 100) ? "scorched" : "beautiful";
default:
return "unknown";
}
46. Paulo Clavijo @pclavijo
function createEmployee(name, type) {
switch (type) {
case "engineer":
return new Engineer(name);
case "salesman":
return new Salesman(name);
case "manager":
return new Manager(name);
}
}
function createEmployee(name, type) {
return new Employee(name, type);
}
47. Paulo Clavijo @pclavijo
class UnknownCustomer {
get name() {return 'occupant';}
}
if (aCustomer === 'unknown') customerName = 'occupant';
53. Paulo Clavijo @pclavijo
class EuropeanSwallow {
get plumage() { return 'average'; }
}
class AfricanSwallow {
get plumage() { return (this.numberOfCoconuts > 2) ? 'tired' : 'average'; }
}
class NorwegianBlueParrot {
get plumage() { return (this.voltage > 100) ? 'scorched' : 'beautiful'; }
}
switch (bird.type) {
case 'EuropeanSwallow':
return "average";
case 'AfricanSwallow':
return (bird.numberOfCoconuts > 2) ? "tired" : "average";
case 'NorwegianBlueParrot':
return (bird.voltage > 100) ? "scorched" : "beautiful";
default:
return "unknown";
}
54. Problem
Solution
You have a conditional that chooses different behaviors
depending on the type of an object.
Move each leg of the conditional to an overriding method
in a subclass. Make the original method abstract.
55. Mechanics
● If classes do not exist for polymorphic behavior, create them together
with a factory function to return the correct instance.
● Use the factory function in calling code.
● Move the conditional function to the superclass.
● If the conditional logic is not a self-contained function, use Extract
Function to make it so.
● Pick one of the subclasses. Create a subclass method that overrides the
conditional statement method. Copy the body of that leg of the
conditional statement into the subclass method and adjust it to fit.
● Repeat for each leg of the conditional.
● Leave a default case for the superclass method. Or, if superclass should
be abstract, declare that method as abstract or throw an error to show it
should be the responsibility of a subclass.