Adding a new functionality to an existing code base is a very common situation. If we have the idea of refactoring in mind, the "where" question is declined in two separate questions: the "which" and the "how" questions. We answer the "which" question by looking for the candidate class the new functionality will be connected to.
If we have no time left or no idea about what refactoring can do for us, the anwser to the "how" question might be a modified class where the connection problem has been solved through code insertion. But we know this quick and dirty approach increases the code complexity and reduces its evolutivity.
The good approach is summarized by the Single Responsibility Principle (SRP). This design principle states that:
"There should never be more than one reason for a class to change".
So the best way to connect a new functionality to an existing code base is to put it in a new class and connect this class to the "which" question target class.
For the context of a an existing class that violates the SRP principle the designer toolbox provides us with the "Extract Class" refactoring.
Back to our Bank example, the Account class has two main responsibilities. The first one is to provide the services we can expect for an Account class. But it has also an additional responsibility which is to log operations with a specific format.
The second one that is less related to the bank domain could be extracted and defined as a new class. That is what we propose here.
Here is the modified Account class:
1: public class Account
2: {
3: private String name;
4: private double balance = 0.0;
5: private AccountLogger logger;
6: public Account( String name )
7: {
8: this.name = name;
9: logger = new AccountLogger( name );
10: }
11: public String getName()
12: {
13: return name;
14: }
15: public boolean open()
16: {
17: System.out.println( "Opening account " + name );
18: return true;
19: }
20: public boolean close()
21: {
22: if( balance > 0 )
23: {
24: System.out.println( "Closing account " + name );
25: return true;
26: }
27: else
28: {
29: System.out.println( "Account " + name + " not closed" );
30: return false;
31: }
32: }
33: public void deposit( double amount )
34: {
35: balance += amount;
36: writeRecord( "Deposit", amount );
37: }
38: public void withdraw( double amount )
39: {
40: balance -= amount;
41: writeRecord( "Withdraw", amount );
42: }
43: public double getBalance()
44: {
45: return balance;
46: }
47: private void writeRecord( String operation_type, double amount )
48: {
49: logger.writeRecord( operation_type, amount );
50: }
51: }
Here are the main changes. We have written an AccountLogger class to hold all the extracted code. Now an instance of the AccountLogger class is created in the Account class constructor and the writeRecord method delegates to the AccountLogger class the record logging.
The newly created AccountLogger class:
1: import java.util.Date;
2: import java.util.Hashtable;
3: import java.io.*;
4: import java.util.Scanner;
5: public class AccountLogger
6: {
7: PrintWriter print_writer = null;
8: enum StorageType { ASCII_STORAGE, XML_STORAGE };
9: StorageType storage;
10: String account_log_name = null;
11: public AccountLogger( String name )
12: {
13: storage = StorageType.ASCII_STORAGE;
14: account_log_name = name + ".txt";
15: }
16: public void setStorage( StorageType storage )
17: {
18: this.storage = storage;
19: }
20: public void writeRecord( String operation_type, double amount )
21: {
22: if( storage == StorageType.ASCII_STORAGE )
23: {
24: writeAsciiRecord( operation_type, amount );
25: }
26: else if ( storage == StorageType.XML_STORAGE )
27: {
28: writeXMLRecord( operation_type, amount );
29: }
30: }
31: public boolean writeAsciiRecord( String operation_type, double amount )
32: {
33: if( print_writer == null )
34: {
35: try {
36: print_writer =
37: new PrintWriter(new FileWriter( account_log_name ) );
38: } catch (IOException ioe) {
39: ioe.printStackTrace();
40: return false;
41: }
42: }
43: Date date = new Date();
44: print_writer.write( date.toString() );
45: print_writer.write( " " );
46: print_writer.write( operation_type ) ;
47: print_writer.write( " " );
48: print_writer.print( amount ) ;
49: print_writer.println() ;
50: print_writer.flush();
51: return true;
52: }
53: private void writeXMLRecord( String operation_type, double amount )
54: {
55: // To be written
56: }
57: }
All the other classes remain unmodified.