Better Faster Lighter Java [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Better Faster Lighter Java [Electronic resources] - نسخه متنی

Justin Gehtland; Bruce A. Tate

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید










3.2 Distilling the Problem




Virtuosos in any

profession have a common gift: they
can distill a problem to its basic components. In physics, Einstein
identified and captured many complex relationships in a simple
equation, e=mc2. Beethoven captured and
repeated a motif consisting of four notes in his fifth symphony
that's endured for centuries. Programming demands
the same focus. You've got to take a set of
requirements, identify the essential elements, strip away everything
that doesn't belong, and finally break down and
solve the problem.



To improve your programming, you don't have to live
in a cave, reading about a design pattern that covers every
possibility. You don't need to know the latest,
hottest framework. You've just got to focus on the
right problem, distill it to the basics, and hammer out the simplest
solution that will work. In this section, I'm going
to take a set of requirements, distill them, and turn them into code.




3.2.1 Collecting Requirements




Let's take a simple example. Say that
you're building an ATM. Your job is to build the
support for an


account. In keeping
with Agile programming practices, you've decided to
keep a simple set of requirements in a table. You'll
record the requirement number, a brief description, a size
(timeline), and choose a programmer. Your team is small and your
cycles are short, so that's all that you think
you'll need. Table 3-1 shows the
basic requirements.






Table 3-1. Requirements for account project



Number






Description






Size (hours)






Assigned






1






Keep a balance and account number






2






Report a negative balance






3






Have a six-digit account number






4






Don't let users change the account number






5






Remember the balance when the user comes back






6






Let users make deposits






7






Let users make withdrawals






8






Display the balance






9






Display debit/credit buttons






10






Print a new balance when the user is done






11






Make the account secure






12






Display the bank's logo






13






Use lighter colors






14






Let the users print out their balance






15






Make the user type a four digit password






16






Make the user insert a card






17






It should be transactional (it all works, or it all fails)





These requirements are typical of the types of things
you'll get from your customers. They are far from
complete, but that, too, is normal. The job of the requirements
document is to accumulate requirements as you understand more about
your application. At the moment, you're the one
assigned to this task. You'll size tasks later. At
this point, you should focus on the problem at hand. Your job is to
build support for an account.




3.2.2 Whittling Away the Noise




Your first job is to whittle away some of the noise. Take any of the
issues that may fit neatly elsewhere and push them out to the
perimeter. Immediately, you recognize that you should separate the
user interface from the base account. You also see that keeping a
balance means that the account will need to be persistent.
You'll use your relational database. Security
probably doesn't belong in the account itself; it
would probably be better left to another layer of the architecture,
like perhaps a façade, but security is also a special
case. Too many developers treat security as an afterthought,
something they can sprinkle on top of an application to make it
"safe." No such pixie dust exists,
though; security layers should be treated with the same respect you
show to everything else in your code.



Essentially, you need to build a persistent account. Rather than
trying to build a design document, you'll start to
code. It's a small enough problem to get your head
around, and you can always refactor. In fact, you'll
probably refactor several times as you think of ways to simplify and
improve

your design. The new set of requirements
is shown in Table 3-2.






Table 3-2. Updated, whittled-down requirements



Number






Description






Size (hours)






Assigned






1






Keep a balance and account number






2






BT






2






Report a negative balance






1






BT






3






Have a six-digit account number






1






BT






4






Don't let users change the account number






1






BT






5






Remember the balance when the user comes back






4






BT






6






Let users make deposits






1






BT






7






Let users make withdrawals






1






BT






17






It should be transactional (it all works, or it all fails)






?






BT





Keep in mind what you're trying to accomplish.
You're not discarding the rest of the tasks.
You'll still need security and a user interface.
Instead, you first want to carve out a manageable size of
development. When that's completed and tested, you
can go ahead and layer on the other aspects of the application, like
the user interface and the persistence. Also, keep in mind that as an
example, these requirements may have a finer grain than they would in
a production project. When you've got a rough unit,
you can start to code.



I'm going to omit the JUnit test cases to keep this
example (and this book) brief, but I recommend that you code test
cases first, as we did in Chapter 2. The next
task is to rough out an interface. You don't yet
need a full implementation. I recommend that you oversimplify,
attaching everything to a single class until the ultimate design
becomes clear. For this example, start with the implementation for
requirements 1, 6, and 7, in a simple class called
Account. Scoping and a simple constructor can take
care of requirement 4. Stub out the rest with empty methods, so that
you've got a simple interface.



Start by organizing in a package, and add a simple constructor:



package bank;
public class Account {
float balance = 0;
private String accountNumber = null;
Account (String acct, float bal) {
accountNumber = acct;
balance = bal;
}



Next, add the accessors for the members. Remembering your
requirements, you want to keep the account number private, so you
scope it accordingly, and omit the setter.



  public float getBalance ( ) {
return balance;
}
public void setBalance(float bal) {
balance = bal;
}
private String getAccountNumber ( ) {
return accountNumber;
}
public float debit(float amount) {
balance = balance - amount;
return balance;
}
public float credit(float amount) {
balance = balance + amount;
return balance;
}



Finally, add some stubs for methods that you'll need
later. You may not decide to do things in this way, but it helps the
ultimate design to emerge if you can capture some placeholders.



  public void save( ) {}
public void load( ) {}
public void beginTransaction( ) {}
public void endTransaction( ) {}
public void isValid(String accountNumber) {}
}



This is a reasonable start. You've covered
requirements 1, 3, 4, 5 and 6, and you've got a head
start on the rest. You're probably not completely
happy with the design. It's already time to
refactor. Since you've been writing unit tests all
along, you can do so with confidence, knowing that your tests will
let you know if you break anything along the way.




3.2.3 Refining Your Design




Sometimes, a metaphor


can help you analyze your design. In
this case, think of the job that we want the account to do. At least
four things surface:





The getters and setters should tell you that this class holds data.
That's a classic data access object, or value
object, depending on the terminology you're used to.





The validation, debit, and credit methods should tell you that
you're doing business logic.





The class saves and retrieves data from the database.





The beginTransaction and
endTransation suggest that you're
also doing transactional processing.






Some of my past clients would have stopped designing at this point.
If you're an EJB programmer, you're
thinking that you've got a match: the class is
transactional, persistent, and possibly distributed. Step away from
that sledge-o-matic, and pick up a plain old ordinary hammer.
It's time to break this puppy down.



Not many would complain if you suggested that it's a
good idea to separate the business logic from the value object.
Today, many modelers like to always separate value objects from the
business domain. Persistence frameworks and other middleware made it
easier to build systems that way. But designs are simpler and much
easier to understand when you can leave them together.



Now, think about the save and load methods, as well as the
transactional methods. Another metaphor is useful in this situation:
think of a folder that holds paper. The paper represents your data
and the folder represents a value object. Think of the save and load
methods as filing the folder for later access. You would not expect
the folder to be able to file itself. In principle, it makes sense to
break the persistence methods away from the accessor methods and the
business logic. For now, let's move the
transactional methods with the persistence.



The result is clean, well-defined business logic, and a data

access object (DAO) built explicitly to
access the database. The DAO should be able to save and retrieve
accounts. Here's the code to load an account using
JDBC:



public static Account load(String acct) throws NotFoundException, SQLException {
Account valueObject;
ResultSet result = null;
String sql = "SELECT * FROM ACCOUNT WHERE (accountNumber = ? ) ";
PreparedStatement stmt = null;
stmt = conn.prepareStatement(sql);
try {
stmt.setString(1, acct);
result = stmt.executeQuery( );
if (result.next( )) {
account.setAccountNumber(result.getString("accountNumber"));
account.setBalance((float)result.getDouble("balance"));
return account;
} else {
throw new NotFoundException("Account Object Not Found!");
}
} finally {
if (stmt != null) {
stmt.close( );
}
}
}



The save code is similar. It's a little ugly, but
that's okay. You'll only be reading
this code when you're interested in the database
details. You'll be able to test the business logic
of the account without wiring it to the data access object.
You'll also be able to add sophistication to the
business logic without thinking about persistence, and you can change
the persistence layer without impacting the business logic.



Consider transactions for a moment. Rather than bringing in the
heavyweight artillery like JTA or EJB, start with the simplest
solution. You can lean on the transaction support of your database
engine and access it through your JDBC connection. That means the
JDBC connection should probably be attached elsewhere, because
you'll want all of your different data access
objects to potentially participate in the same transaction. For
example, if a user opened an account, you'd probably
want to update the user and the first account deposit at the same
time.



You know you need to refactor. Where's the correct
place for the JDBC connection, and the associated transaction
support? It's not in the account itself or the
Account data access object.
You'll need to create something, and
you'll need some type of connection manager. If that
strategy doesn't work out, you can always refactor
again. Lean on these types of iterative refinements to improve your
design as you progress.



Although this is a trivial example, it demonstrates how the process
works. You write tests, code a little, refactor a little, and repeat
the cycle until your eventual design emerges. After wading through
these details, it's time to look at issues at a
higher level.




/ 111