|
|
|
Names, Modularity, and Maintainability
In large programs, unless they are designed correctly, it is difficult
to anticipate the results of modifying or adding code.
Subprograms are a way of breaking up processes to
abstract a commonly used piece of code and to
isolate the effect of changes or modifications to code.
Objects, packages, and abstract data types are ways of abstracting
data, to encapsulate data structures.
First we will look at ADTs, then at methods to help declutter name
spaces.
Abstraction
What is abstraction? In general, elimination of details. In programming abstraction often means
describing the "what" not "how". The notion of "interface" is one way that abstraction appears
in programming. Informally, the interface of a component is the rules for using the component. It
tells you what operations the component has, the way to use them, and what they will do. In some
languages the notion of interface appears directly and concretely as syntax in the language.
Note: Interfaces in Java are a bit at odds with the above definition. Java interfaces are used in place
of multiple inheritance so no one Java interface necessarily describes the whole interface, in the
sense I'm using it here, of a class.
Example of how abstraction helps: compare how strings are handled in perl and C. In perl, strings are
abstract: you don't know or care about their representation. There is just a set of "string-ish"
operations that can be applied to them. In C, strings are arrays of characters. There are library
routines to do "string-ish" operations but the "array-ness" shows through everywhere, making
string manipulations in C much more tedious and error-prone than in Perl.
Abstract Data Types
Recall that a type is
a set of values and a set of allowable operations on those values.
Floating point type, such as Pascal type real
-
Historically - different architectures have supported different floating
point represenations
-
Some operations supported, +, -, *
-
Actual layout of bits is (or should be) hidden
What about ability to define new types?
A abstract data type is
-
The declarations of the type and the operations on the type,
defined in a single syntactic unit.
-
The representation is hidden.
An example of an abstract data type: a stack.
- values - a stack of integers
- operations - push, pop, create, destroy, full
An example C Stack ADT. First there is the definition of the
ADT, in a header file.
/* In stack.h */
typedef struct {
int values[];
int top;
} stack_type;
/* operations */
void push(stack_type stack, int value);
int pop(stack_type stack);
int isEmpty(stack_type stack);
stack_type newStack(int capacity);
stack_type destroy();
Next there is the implementation of the stack.
/* In stack.c */
#include "stack.h"
/* operations */
void push(stack_type stack, int value) {
....
}
int pop(stack_type stack) {
...
}
...
Finally, there is the use of the stack.
/* In main.c */
#include "stack.h"
main() {
stack_type stack;
push(stack, 23); /* Should be an Error! */
stack = newStack(50);
stack.values[30] = 100; /* Shouldn't be able to do this! */
}
Unsatisfactory because
- stack type internals are not hidden, main.c knows about stack
internals. This dramatically reduces reliability.
- can't hide
- stack type objects are not "protected" in the sense that other
operations could manipulate the internals of the stack object
- stack use/definition separate: potentially, user could
cut and paste stack.h into main, then any changes to stack.h
would not be manifest in main.c.
- not like an integer, type cannot be enforced by compiler,
user doesn't have to do a newStack prior to using a stack!
- name conflicts galore - what if user wants a list type,
and has a pop operation on lists?
- allocation/deallocation up to the user
- non-orthogonal of type of elements in stack, why can't I have a stack
of chars?
Ada was designed to help.
-
Largest language design effort in history
-
1974 - Department of Defense begins design
-
1978 - Requirements docment completed, design goes to bid
-
1979 - Four designs proposed, evaluation begins
-
1979 - One design chosen, called Ada, opened for public comment and
500 language reports received
-
1981 - 1983 - Language is standardized
-
Designed by committee, had lots of features
-
1985 - First compilers appear
Kinds of operations in an ADT
- constructor - create an object, usually heap-dynamic, but sometimes
stack-dynamic
- destructor - destroy the object, freeing space. Object should not
be used after the destructor (how do we guarantee?).
- accessor - read a value in the object
- iterator - enumerate values in the object
- mutator - modify the object
Ada example of stack encapsulation.
-- In Ada.stack file
package Stack_Pack is
-- Public interface, visible to outside world
type Stack_Type is limited private;
Max_Size : constant : 100;
function Push(Stk : in out Stack_Type, Element : in Integer): Boolean;
function Pop(Stk : in out Stack_Type): Integer;
...
private
-- hidden from clients
type Stack_Type is array (1..Max_Size) of Integer;
end Stack_Pack;
Then there is the body of the stack.
-- In stack.ada
package body Stack_Pack is
function Push(Stk : in out Stack_type; Element : in Integer) : boolean
is begin
...
end Push;
...
end Stack_Pack;
These two would be compiled with the code that uses the stack.
with Stack_Pack; -- makes names defined in Stack_Pack visible here
use Stack_Pack; -- do not have to qualify those names
procedure Use_stacks is
Stack: Stack_Type; -- constructor!
begin
if (!Push(Stack, 42)) then ...
...
end Use_stacks; -- Stack is deallocated
C++ ADT.
/* In stack.h */
class stack {
private: //** Visible only here (and to friends)
int *stackPtr;
int maxSize;
int *top;
public:
stack() { //** constructor
stackPtr = new int [100];
...
}
stack(int size) { //** constructor
stackPtr = new int [size];
...
}
~stack() {delete [] stackPtr;}; //** Destructor
void push(int number) { ... }
...
} //** class stack
/* In main.cc */
#include
void main() {
stack stk; //** Calls the constructor, create a new instance
stk.push(42);
...
} //** Destructor called for stk
Operator "overloading" refers to an operator changing
depending upon the type of the operands.
Languages that support the definition of abstract data types should
also allow operator overloading so that new types can be used
with existing operators.
Parameterising the data type.
stack_type st(char, 3); /* Create a stack of characters of size 3*/
Compiler generates different code.
Packages
A package is a group of declared components,
e.g., types, constants, variables, functions. It is
an encapsulated set of bindings
Example Package
package Earth is
constant float gravity = 9.6;
constant integer circumference = 29000;
...
type continent is (Africa, ..., SudAmerica);
end Earth;
is set of bindings
gravity --> 9.6
circumference --> 29000
...
continent --> the type {Africa, ..., SudAmerica}
But note that a package defines just a single environment.
Environments often has many bindings in common, such as one would
expect Jupiter to have a similar set of bindings as Earth.
Source of Information
These lecture notes are based on Chapter 11 in "Programming Languages, 6ed"
by Robert Sebesta and Chapter 6 in
"Programming Language Concepts and Paradigms" by David Watt.
|