Sometimes the methods in a class need help. One solution is to add private helper methods to the class. We would probably make these methods private because they are not part of the public interface of the class. (The public interface consists of all methods required by the customer.)
class MyClass {
//
interface methods:
public void meth1() {
System.out.println("invoking
meth1");
helperA();
helperB();
}
public void meth2() {
System.out.println("invoking
meth2");
helperB();
helperC();
}
public void meth3() {
System.out.println("invoking
meth3");
}
//
helper methods:
private void helperA() {
System.out.println("invoking
helperA");
}
private void helperB() {
System.out.println("invoking helperB");
}
private void helperC() {
System.out.println("invoking
helperC");
}
}
In some cases these helper methods may be useful to other classes. In this case we can create a class containing the helper methods. In this case the helper methods would be public. We call the class containing the helper methods the provider and the class containing the methods that use the helper methods the client.
There are several possibilities:
1. Each client method contains its own instance of the provider class in a local variable.
2. Client methods share an instance of the provider class in a field.
Option 2 can be subdivided into two cases:
2A. Every instance of the client class has its own instance of the provider class.
2B. Several instances of the client class share an instance of the provider class.
In UML we can represent the Client-Provider relationship as a directed association from the Client class icon to the Provider class icon:
The Provider end of the association indicates that the client refers to the provider as "helper", which will be a private field in the Client class. The multiplicity, 1, indicates that each client instance will have exactly one provider instance.
On the Client end of the association the multiplicity N might have several values:
N = 1: Each client has
its own provider
N = 2: Two clients share one provider
N = 7..9: A provider will be shared by
7, 8, or 9 clients
N = *: A provider will be shared by 0
or more clients
Here is how the UML class diagram might be translated into Java. First, the Provider class contains three public methods:
class Provider {
public void serviceA() {
System.out.println("invoking
serviceA");
}
public void serviceB() {
System.out.println("invoking
serviceB");
}
public void serviceC() {
System.out.println("invoking
serviceC");
}
}
Second, the client class has a private field named helper. This field can be initialized by a new, unshared provider by the default constructor, or it can be initialized to a predefined (and therefore sharable) provider using the second constructor:
class Client {
private Provider helper;
public Client() {
helper = new Provider();
}
public Client(Provider sharedHelper) {
helper = sharedHelper;
}
public void meth1() {
System.out.println("invoking
meth1");
helper.serviceA();
helper.serviceB();
}
public void meth2() {
System.out.println("invoking
meth2");
helper.serviceB();
helper.serviceC();
}
public void meth3() {
System.out.println("invoking
meth3");
}
}
Of course the implementations of all methods are just for demonstration purposes. But notice that 100% of the provider methods are used by 66.6% of the client methods. Later we will be interested in how much a client depends on a provider. The weaker this dependency, the easier it is to replace or modify the provider without violating the client.
Here is a test harness for the Client class:
public class TestClient {
public static void main(String[]
args) {
Provider p1 = new Provider();
Client c1 = new Client();
Client c2 = new Client(p1);
Client c3 = new Client(p1);
c1.meth3();
c2.meth2();
c3.meth1();
}
}
Here's a UML object diagram (a class diagram containing objects and links instead of classes and associations) depicting memory just after c3 is created by main:
Note: I am using dependency arrows instead of links so that I can show the arrowheads indicating the direction of dependency.
It is often desirable to separate business logic from presentation logic. In this case the client deals with presentation while the provider provides the business logic:
Of course client and provider can both be business classes:
Note that when the provider end of an association doesn't have a name, then the name is usually the same as the name of the provider class. In this case we have two provider accounts and so we might store them in an array:
class AccountHolder {
private Account[] account = new
Account[2];
// etc.
}
Alternatively, we could have to 1-1 associations:
Now the client can better distinguish between the two providers:
class AccountHolder {
private Account savings;
private Account checking;
// etc.
}
In this case the providers are purely utilitarian:
If all calculators are the same, we might consider making our providers utility classes. In a utility class all methods are static. In other words, we don't need to ask calculator objects to invoke calculator methods:
Here's a Java interpretation:
class TaxCalculator {
public static Money getTax(Money total)
{
// ...
}
}
class ShippingCalculator {
public static Money getCost(double
weight, Address dest) {
// ...
}
}
class Cart {
private ArrayList<Product> items;
private Address customerAddress;
public void add(Product item) {
items.add(item); }
public Money getTotal() {
Money total = new Money(0);
double weight = 0;
for(Product item: items) {
total =
total.add(item.getPrice());
weight += item.getWeight();
}
result = result.add(TaxCalculator.getTax(result));
result = result.add(
ShippingCalculator.getCost(weight,
customerAddress));
return result;
}
}
It's entirely possible for a class to have an association to itself:
Here's a Java interpretation:
class Person {
private Person mostAdmires;
// etc.
}
We might call this a recursive class.
Note that while a class may have an association to itself, instances may or may not be self referential:
In a bidirectional association sometimes the client becomes the provider and the provider the client. In this case we call both classes peers:
One issue with bidirectional associations is which peer controls the association. If the Actor controls the association, then the Role class can be simple:
class Role {
private Actor actor;
public Actor getActor() { return actor;
}
void setActor(Actor newActor) {
actor = newActor;
}
}
But the actor must take care to preserve the multiplicities:
class Actor {
private Role role;
public Role getRole() { return role; }
public void setRole(Role newRole) {
role.setActor(null);
role = newRole;
role.setActor(this);
}
}
Worse yet, somehow users must know that the actor is the controlling end of the association. If the user calls setActor, then the links won't be set correctly. To mitigate this problem somewhat, we have given setActor package scope. But this doesn't solve the problem completely.
Why not have setActor call actor.setRole?