Re: How does one model behavior?
Date: Wed, 09 Apr 2008 18:24:55 +0100
Message-ID: <47FCFBE7.FC18958E_at_ua.pt>
> (...)
> Sorry. I don't know enough OO to provide enlightenment. I understand data
> pretty well.
>
> Perhaps you could tell me how you express "what a class has to do".
In OO, a class is (or at least, it should be!) an Abstract Data Type (possibly partial) implementation (an ADT is a type which is completely defined by its public operations and their semantics).
Hence, the behavior of a class is specified by its ADT.
In a pure OO world one manages "data" *only* through the operations (functions or methods, if you will) which are applicable to it.
> This might be close to what I'm asking for when I say "how to you model
> behavior".
Contracts attached to ADTs:
- class invariants, and;
- method preconditions and postconditions.
For instance, the behavior of a generic class STACK could be approximated by the following class (using Eiffel syntax):
- This is a line comment!
deferred class STACK[E] -- a deferred class cannot be instantiated
feature -- exported to everyone
count: INTEGER is
- Number of elements in STACK deferred -- the body implementation is deferred to descendant classes! end;
empty: BOOLEAN is
- Is STACK empty? do Result := count = 0 -- no need to defer this implementation to descendants end;
full: BOOLEAN is
- Is STACK full? deferred end;
top: E is
- STACK's last pushed element require -- precondition not empty deferred ensure -- postcondition same_count: count = old count end;
push(elem: E) is
require
not full
deferred
ensure
not empty; one_more: count = old count + 1; element_placed_in_the_top: top = elem; correct_old_top: old empty or else under_top = old topend;
pop is -- procedure (Eiffel promotes strict query/command separation)
require
not empty
deferred
ensure
not full; one_less: count = old count - 1; correct_new_top: empty or else top = old under_topend;
feature {STACK} -- exported only to STACK itself
under_top: E is
- Stack's pushed element before top require at_least_two_elements: count >= 2 deferred ensure same_count: count = old count end;
invariant
(empty and count = 0) or (not empty and count > 0)
end -- STACK
As you can verify there are no explicit data declarations, other than possible function arguments and results (only operations, whose behavior is specified and depends on each other).
This stack specification and construction is also not
linked to a possible (state based) internal representation
(array, linked list, or any other).
In OO, other than for object creation, a client of a
stack may rely only on this interface to manipulate
stacks (dynamic binding, together with subtype
polymorphism, allows us to put any object of a
type descendant of STACK, where a STACK is
expected.
Also, although a push message sent to a STACK object carries a "data"(*) element of type E with it, the stack's semantics does not depend, in any way, on the semantics of such element (that is why we can use unbounded parametric polymorphism in the stack class).
(*) is fact, in a pure OO world this is also an object (an instance of a (partial) ADT implementation).
It is also important to note that in Eiffel contracts (invariants, preconditions and postconditions) are inherited in descendant classes, so a STACK descendant is *required* to obey STACK's specification. Hence this (deferred) class defines the behavior of all its possible descendants (STACK_ARRAY[E], STACK_LINKED_LIST[E], etc.).
Best regards,
-miguel Received on Wed Apr 09 2008 - 19:24:55 CEST