Re: Best Way to Pass Info Between Objects?
Novice wrote:
Patricia Shanahan<pats@acm.org> wrote :
Novice wrote:
...
I'm trying to reason out the best way to pass information from an
instantiating class to an instantiated class. So, let's say class Foo
invokes class Bar to do something. Bar needs some specific
information from Foo to do its job. What is the best way to pass this
information from Foo to Bar?
...
I'm not sure if there is a generally agreed-upon formula here or
whether it is more a case of individual style and preference. I'd be
very interested in your comments on this subject. Right now, I feel
like I'm being pretty inconsistent in my techniques and would like to
standardize them along the best possible lines.
Parameters.
>> Here is one programming process approach that often simplifies these
>> questions: Write the Bar unit tests in parallel with designing Bar.
>>
>> If the Bar design works well for both unit testing and use by a Foo,
>> it will probably be a reasonably robust module that does not need to
>> be changed too often because of changes to other modules.
>
> But in the meantime, are there any general rules I can use to make these
> decisions for code I am developing now? Or do I really need to master
> several books first?
Mastery can occur in a flash. Knowledge takes longer, but key points will
carry you as far as you need in the short term.
If you read /Effective Java/ as Volker Borchert recommended, you will have
enough to make you competent, provided you understand and practice Joshua
Bloch's suggestions.
What follows is a long and detailed answer that incorporates what others have
told you and provides specific examples. I have not actually compiled these
examples, but I intended them to be compilable.
Summary:
It's all about the model (types, attributes, behaviors).
You should program in terms of interfaces and generics with interface parameters.
Express your model in terms of experiments to try to break it. Those are tests.
Write an interface for each type you're modeling. Too much in the interface?
Refactor. A type should be cohesive.
Write a test class for the interface. You can write it one test method at a
time, but the test class will eventually cover all public (and protected)
methods of the implementation.
Write the minimum compilable implementation of the interface so you can run
the tests.
Observe the test failures.
Fix it.
Often fixing the test failure means a modification to the test class, just as
we added the explicit test for the constructor failure in the example.
Repeat until the whole type passes all tests.
That cycle applies any time the type or an implementation of it changes. A
smart developer uses a continuous-integration tool like Hudson or many others
to rerun a test suite any time changes are checked in to version control.
One last thing - be pretty familiar with the classes in the java.lang.* and
java.util.* packages, especially the collections framework.
Long answer:
You have to map out the behaviors correctly, then the methods and constructors
make sense. Don't send a person's name, address, favorite color, medical
history and descriptions of pets as discrete parameters, but as properly
modeled objects:
register(Person person, Collection<Pet> pets)
The signature is concise and easy to read, yet the parameters can manipulate
quite a lot of data. Constructors can be a little hairier because they
assemble such disparate particles, but even those should be modeled:
public Person(Name name, Calendar dob,
Collection<Relationship> relationships)
Where you absolutely must specify lots of teeny little things to build an
object, use a builder that ultimately returns the desired type, preferably as
an immutable target.
String message = new StringBuilder("This is the ").append(count)
.append("th inference since ").append(formatter.format(rememberWhen))
.append('.').toString();
Programming is as much or more a way of thinking about problems as it is
coding. Anyone can write code, but you still need the right code.
This comes from how you model the scenario or process or whatever you seek to
express in a program. As you model your problem space, look for patterns as
Stefan Ram recommended, not just the formal ones in the literature but in
general terms. I call it "nouns and verbs" - what are the actors (nouns) and
their attributes (adjectives) and their behaviors (action verbs). Nouns are
types, attributes are getters (accessor methods) and setters (mutator
methods), and action verbs are behavioral methods.
Here's a Person type (noun) with just one attribute (adjective), how it's named:
public interface Person
{
String getName();
}
Without a very good reason otherwise, all attributes should be 'getX()' only,
not 'setX(X x)'. Then you make the underlying variable 'final' and immutable
as well.
Now express your model as experiments, or tests - "if I return a person's name
(attribute 'getName()') I will never get a 'null'." For this you'll need an
interface to express the type:
and a unit test:
import static org.junit.Assert.assertNotNull;
import org.junit.Before;
import org.junit.Test;
public class PersonTest
{
Person person;
@Before public void initialize()
{
person = new PersonImpl();
}
@Test public void testGetName()
{
assertNotNull("null name", person.getName());
}
}
Oh, that means you need a 'PersonImpl'. Go barebones at first; let the test
fail with the very minimum code needed to compile.
public class PersonImpl implements Person
{
@Override
public String getName()
{
return null;
}
}
Gosh, how can you make the test pass? Guess you'd better give that Person a
name. Better make it read-only!
public class PersonImpl implements Person
{
private static final String name;
public Person(String name)
{
this.name = name;
}
@Override
public String getName()
{
return name;
}
}
That forces a change to the test:
public class PersonTest
{
Person person;
@Before public void initialize()
{
person = new PersonImpl();
}
@Test public void testGetName()
{
Person person = new Person(null);
assertNotNull("null name", person.getName());
}
}
Oh, we really should expect an exception if you try to instantiate a 'null'
name. Write the test first.
public class PersonTest
{
private static final String TEST_NAME = "Jan Doe";
Person person;
@Test(expected=IllegalArgumentException.class)
public void testGetNameNull()
{
Person person = new Person(null);
}
@Test public void testGetName()
{
Person person;
try
{
person = new Person(TEST_NAME);
}
catch (Exception exception)
{
fail("unexpected exception. " + exception.getLocalizedMessage());
}
assertNotNull("null name", person.getName());
}
}
Now change the implementation to pass the test:
public class PersonImpl implements Person
{
private static final String name;
public Person(String name)
{
if (name == null)
{
throw new IllegalArgumentException("null name");
}
this.name = name;
assert this.name != null
}
@Override
public String getName()
{
assert this.name != null
return name;
}
}
The 'assert' keyword marks where the algorithm depends on certain facts, in
this case the non-nullity of the 'name' attribute. It's a postcondition of
the constructor and a precondition of the getter.
It's a lot of frakkin boilerplate in the implementation, or concrete class. I
prefer to think of it as armor plate. Anyway, once you've set all the castle
guards at the concrete class, you use the interface for general programming:
public class CountryClub
{
private final Set<Person> members = new HashSet<Person>();
public boolean addMember(Person person)
{
if (isUnqualified(person))
{
throw new SnobException("We do not find " + person + " suitable.");
}
return members.add(person);
}
public String showMembers()
{
return members.toString();
}
private boolean isUnqualifed(person)
{
return person.getName().equals("Lew");
}
}
By the way, that SnobException message and 'showMembers()' show us the need
for a 'Person#toString()' method, and if you read /Effective Java/ you see
that that goes hand-in-glove with 'equals(Object other)', 'hashCode()' and
'compareTo(Person other)' (if the type is 'Comparable'). All four (or three
if not 'Comparable') methods must agree on what makes one 'Person' different
from another. (Hint: In this simple model the name attribute should be
unique, so equality and hash will be based entirely on the name, and
'toString()' will return the name.)
You can write a test for that.
There you go, mastery in a flash.
--
Lew
Honi soit qui mal y pense.
http://upload.wikimedia.org/wikipedia/commons/c/cf/Friz.jpg