|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 5 of 5
Figure 3. Instantiate the DBC-enabled stack. Click on thumbnail to view full-size image.
Figure 3 illustrates the following sequence of steps:
StackImpl instantiates
DBC.newInstance(), which instantiates DBC (the invocation handler)
DBC finds, instantiates, and stores all StackImpl's assertion classes in the DBC instance
DBC checks the invariants
DBC instance as its invocation handler, instantiates and returns to the client
Note: StackImpl does not specify any new assertions. However, StackImpl does inherit the assertions defined in Stack and as a result must adhere to them. Figure 3 (and later Figure 4) illustrates the inheritance nature of the defined assertions.
Now that a DBC-enabled Stack instance exists, we can test its contractual obligations. For example, to see what happens when a Stack client violates a precondition assertion, let's call method stack.item() before any object is put on Stack. Here is the client code:
Object obj = stack.item();
Clearly the above statement violates the precondition at line 23 in Listing 1. Line 23 implies that an item from stack cannot be retrieved if the stack is empty. Therefore the following error message displays when the code executes:
Precondition violated: !obj().empty() at dbc.test.StackImpl.item
Clearly, the client caused this violation; it should have known not to retrieve an item from an empty stack.
Postconditions can also be violated. Remember these are the supplier's responsibility; in this example, StackImpl is the supplier. Therefore, to violate a postcondition, you must change the StackImpl implementation by removing or commenting out the put() implementation at line 41 in Listing 2. Then, if a client executes the following code segment that uses put():
stack.put("Any Object");
then StackImpl will violate at least two postconditions declared at lines 43 and 44 in Listing 1. Had the stack been empty prior to the
put() call, the postcondition at line 42 in Listing 1 would be violated too. Here is the error message that would report:
Postcondition violated: !obj().empty() at dbc.test.StackImpl.put
The supplier is also responsible for the class invariant. To see what happens when an invariant is violated, you could change
line 30 in Listing 2 to count() == 1. This will violate the invariant declared at line 6 in Listing 1. When the following client code executes:
Stack stack = (Stack)DBC.newInstance(new StackImpl(10));
the following error message will indicate a violated invariant:
Invariant violated: obj().empty() == (obj.count() == 0) at dbc.test.StackImpl
As mentioned earlier, no assertions were declared in StackImpl, but the assertions declared in Stack were still enforced. This is an important concept to understand. A descendent cannot break the contract established by its
ascendants. However, that does not mean StackImpl cannot have its own assertions; it may, as long as it fulfills the contract specified by Stack.
Figure 4 illustrates steps that occur when the client calls method put().
Figure 4. Method put() is called. Click on thumbnail to view full-size image.
Figure 4 illustrates the following sequence of steps:
stack.put().
DBC.invoke(). Method DBC.invoke() is the dynamic proxy's invocation handler.
DBC.invoke() checks the preconditions with the help of naming conventions and Java reflection.
DBC.invoke() calls the primary object (StackImpl.put).
DBC.invoke() checks the postconditions with the help of naming conventions and Java reflection.
DBC.invoke() checks the invariants.
As described earlier, preconditions have an optional wait statement, which is used in Listing 1 at line 23 and line 41 for methods item() and put(), respectively. This wait statement has no effect under regular circumstances; line 23 and 41 act as regular preconditions. For example, if the client
calls method item() when the stack is empty, a precondition violation will occur, as the precondition states. If, however, we create the stack
object as a separate object, the preconditions at line 23 and 41 would turn into wait statements. These statements will wait until the precondition is true.
The wait statements become relevant in situations of concurrent access. For instance, suppose the stack object is accessed from multiple
threads. Suppose one thread reads (stack.item()), while another thread writes (stack.put(Object)). In Java, to achieve thread safety, you use the keyword synchronized. In the Stack example, we would implement the method item() as follows:
public synchronized Object item()
{
while (!empty())
wait()
return 'top object';
}
Notice that item() caller(s) must wait until the stack isn't empty, as the precondition for item() states. To use preconditions as wait statements, you must be able to tell the DBC runtime environment that multiple threads will use the object. The DBC runtime
environment needs to know that the object is separate. You can show that when you construct the object. The class dbc.DBC has an additional method: public static Object newInstance(Object obj, boolean separate). If the second argument separate in the newInstance() method above is true, the DBC runtime will do the following:
synchronizedwait statement after the Boolean statement will wait until that precondition is true (in the worst case, it waits forever)
By adding the wait statement to the appropriate preconditions, you create a stack that you can use in a single-threaded environment as well
as a multithreaded environment. Because preconditions are part of the stack specification and not its implementation, any dbc.test.Stack implementation will work in a single-threaded environment as well as a multithreaded environment.
In this article, we utilized DBC in Java by leveraging the following existing Java technologies:
Hopefully, this article has inspired you to try DBC, or at least think of developing software in terms of contracts between clients and suppliers. A DBC way of thinking is a great tool for developing high quality software. Additionally, DBC may even enhance your object-oriented analysis and design skills.
You can use the DBCProxy framework not only during design and development phases, but also during testing phases. The overhead of using the framework is minor. However, I don't recommend that you use the framework in a production system. The DBCProxy framework intends to aid in developing high quality software. It uses the Proxy pattern, while leveraging JDK 1.3's new dynamic proxy feature, as a means to execute assertions.
As I mentioned earlier, the DBCProxy framework will only work when objects are referenced within the interfaces they implement. This means that every DBC-enabled object must be based on at least one interface, and only methods that have been declared in an interface can be DBC-enabled. This is a consequence and limitation of dynamic proxies. However, it might not be a serious limitation since, in general, using interfaces in code is considered good design.
Read more about Core Java in JavaWorld's Core Java section.