Adventures in Software Craftsmanship
Day 1 - Afternoon
Software Design Patterns
Helps developers reuse successful designs by basing new designs on prior experience.
Improves understandability for developers familiar with the pattern.
Identifies participating classes and instances, their roles and collaborators, and the distribution of responsibilities. This helps ensure adherence to Design Principles.
A design pattern is a general reusable solution to a commonly occurring problem within software design. It is a description or template for how to solve a problem that can be used in many different situations.
Software Design Patterns
Creational
Structural
Behavioral
Creation of objects for you rather than instantiating objects directly
Composition of classes and objects to obtain new functionality
Communication and interaction between objects
Software Design ANTI-Patterns
Common processes or solutions that, although might seem effective, have been found to be ineffective.
Factory Pattern
Encapsulates object creation logic by delegating to implementations of a common Factory interface without specifying the exact class of object to create.
Use when
sourcemaking.com
interface Account {
String getAccountNumber();
}
interface AccountFactory {
Account createAccount();
}
class BankAccountFactory implements AccountFactory {
Account createAccount() {
Account account = new BankAccount("01234567890");
account.setInterestRate(0.01);
return account;
}
}
class CreditCardAccountFactory implements AccountFactory {
Account createAccount() {
Account account = new CreditCardAccount("0000123456789999");
account.setCreditLimit(5000);
return account;
}
}
...
switch (accountType) {
case BANK:
return new BankAccountFactory();
case CREDIT:
return new CreditCardAccountFactory();
}
...Builder Pattern
Solves the "telescoping constructor" problem by encapsulating individual construction steps that can be invoked individually.
Use when
sourcemaking.com
http
.authorizeRequests()
.antMatchers("/login.jsp").permitAll()
.anyRequest().hasRole("USER")
.and()
.exceptionHandling()
.accessDeniedPage("/login.jsp?authorization_error=true")
.and()
.csrf()
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
.logoutUrl("/logout")
.logoutSuccessUrl("/login.jsp")
.and()
.formLogin()
.loginProcessingUrl("/login")
.failureUrl("/login.jsp?authentication_error=true")
.loginPage("/login.jsp");
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.build();
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);Singleton Pattern
Restricts the number of instantiations of a class to one single instance.
Use when
Better than static methods
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
@Configuration
public class TdpAppSpringConfig {
@Inject
org.apache.commons.configuration.Configuration epfApplicationConfigUtil;
@Bean
public DataSource dataSource() {
String postgresUrl = epfApplicationConfigUtil.getString("postgresql.jdbcurl");
String postgresUsername = epfApplicationConfigUtil.getString("postgresql.username");
String postgresPassword = epfApplicationConfigUtil.getString("postgresql.password");
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl(postgresUrl);
dataSource.setUsername(postgresUsername);
dataSource.setPassword(postgresPassword);
return dataSource;
}
}
@Repository
public class QuestionsDao extends JdbcDaoSupport {
@InjectLogger
private Logger logger;
@Inject
public QuestionsDao(final DataSource dataSource) {
setDataSource(dataSource);
}
...Facade Pattern
A facade object provides a simplified interface to a larger body of code to make a subsystem easier to use.
Use when
sourcemaking.com
public boolean createNewFile() throws IOException {
SecurityManager security = System.getSecurityManager();
if (security != null) security.checkWrite(path);
return fs.createFileExclusively(path);
}
public boolean delete() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkDelete(path);
}
return fs.delete(this);
}
public String getName() {
int index = path.lastIndexOf(separatorChar);
if (index < prefixLength) return path.substring(prefixLength);
return path.substring(index + 1);
}
}Iterator Pattern
Encapsulates and decouples the algorithm for traversing a data structure, providing a way to access the elements sequentially without exposing the structure's underlying implementation.
Use when
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
public void removeNaughtyWords(final List<String> words) {
Iterator<String> wordIter = words.iterator();
while (wordIter.hasNext()) {
String next = wordIter.next();
if (NAUGHTY_LIST.contains(next)) {
wordIter.remove();
}
}
}Command Pattern
Uses a command object to represent the information needed to call a method at a later time. Command information includes the method's name, object that owns the method, and values for the method's parameters.
Use when
sourcemaking.com
interface TransactionExecutor {
void addTransactionCommand(TransactionCommand transaction);
}
interface TransactionCommand {
void execute();
}
class DepositCommand implements TransactionCommand {
Account account;
double amount;
void execute() {
account.addFunds(amount);
}
}
class WithrawalCommand implements TransactionCommand {
Account account;
double amount;
void execute() {
account.removeFunds(amount);
}
}Observer Pattern
An object (the subject) maintains a list of its dependents and notifies them of any state changes.
Use when
sourcemaking.com
Test-Driven Development
Requirements must be completely understood through use cases and user stories.
TDD Benefits
Unit Testing Conventions
Test structure
Avoid
@Before
public void setUp() {
bill = new Bill(2, new Server("gtb012", "Reid"));
calculator = new BillCalculator(.05);
}
@After
public void tearDown() {
bill = null;
calculator = null;
}
@Test
public void testCalculateTax() {
bill.addItem(new Item("milk", 1.23));
bill.addItem(new Item("cereal", 2.13));
double tax = calculator.calculateTax(bill);
assertEquals(0.17, tax);
}Unit Test Mocking
Mock objects can simulate the behavior of complex, real objects when the real object is impractical or impossible to incorporate in a unit test.
Creating a mock object will stub the method implementations.
Usage of a mock object by the unit under test (UUT) allows control and verification of the invocation of the mock by the UUT.
@Test
public void testReadConfigurationFromPropertiesFile_OneHttpStream() {
when(appConfig.getProperty(StreamEmitterFactory.PROPS_KEY_STREAM_COUNT)).thenReturn(1);
when(appConfig.getString(StreamEmitterFactory.PROPS_KEY_STREAM_PREFIX + 1 + "." + StreamEmitterFactory.PROPS_KEY_TYPE)).thenReturn("http");
when(appConfig.getString(StreamEmitterFactory.PROPS_KEY_STREAM_PREFIX + 1 + "." + StreamEmitterFactory.PROPS_KEY_NAME)).thenReturn("http1");
Iterator mockIter = mock(Iterator.class);
when(appConfig.getKeys(StreamEmitterFactory.PROPS_KEY_STREAM_PREFIX + 1)).thenReturn(mockIter);
//Iterate 4 times for 4 different property keys for http configuration
when(mockIter.hasNext()).thenReturn(true, true, true, true, false);
when(mockIter.next()).thenReturn("upf.dfs.stream1.type",
"upf.dfs.stream1.name",
"upf.dfs.stream1.host",
"upf.dfs.stream1.port");
when(appConfig.getString("upf.dfs.stream1.type")).thenReturn("http");
when(appConfig.getString("upf.dfs.stream1.name")).thenReturn("http1");
when(appConfig.getString("upf.dfs.stream1.host")).thenReturn("localhost");
when(appConfig.getString("upf.dfs.stream1.port")).thenReturn("9000");
streamFactory.readConfigurationFromPropertiesFile();
verify(mockIter, times(5)).hasNext();
verify(mockIter, times(4)).next();
verify(appConfig, times(6)).getString(anyString());
StreamEmitter emitter = streamFactory.getEmitter("http1");
assertNotNull(emitter);
StreamMessageEntryPoint entryPoint = emitter.getStreamMessageEntryPoint();
assertNotNull(entryPoint);
}Pair Programming
Driver
Navigator
Exercise Requirements
Restaurant order and transaction system.