Abstract Data Types II Sufficient operations Operations on
Abstract Data Types II
Sufficient operations • Operations on an ADT are sufficient if they meet all the requirements – They must be able to create all the values and perform all the operations required by the application • Remember that the application cannot directly access the internal values – They should be able to create all the values and perform all the operations required by any application in a given class of applications
Necessary operations • An operation on an ADT is necessary if omitting it would fail to meet the requirements – If the application can implement an operation easily and efficiently by combining other operations, that operation is unnecessary – It’s OK to have unnecessary operations if they add significantly to the convenience of using the ADT
Convenience operations • An operation is a convenience operation if it could be accomplished by some overly complex combination of other operations • Convenience operations should be justified – Will it be used often? – Does it really simplify the user’s task? – Would the user expect this operation to be provided? – Is it significantly more efficient?
Necessary and sufficient operations • A class should define a necessary and sufficient set of operations – Convenience operations should be justified • Similarly, a class should have a necessary and sufficient data representation – In general, a class should not contain data that can be easily computed from other data in the class
Example: Strings • Necessary and sufficient operators: – A constructor: public String(char[] chs) – Ways to access data: • public int length() • public char. At(int index) • Would you be happy with just these? • If you invented the String class, could you justify operations such as equals and string concatenation? • Convenience operators aren’t all bad!
Types of operations (p. 113) • A constructor is creates a value of the ADT from input values • An accessor uses a value of the ADT to compute a value of some other type • A transformer uses a value of the ADT to computer another value of the ADT – A mutative transformer changes the value of the ADT it is given – An applicative transformer takes one ADT and, without changing it, returns a new ADT
Requirements • The constructors and transformers must together be able to create all legal values of the ADT • The accessors must be able to extract any data needed by the application
Operations in Java • Constructors can be implemented with Java constructors – A constructor’s job is to construct an object of a class in a valid state – That should be a constructor’s only job • Accessors and transformers can be implemented with Java methods – Mutative transformers are typically (but not always) implemented as void methods – Sometimes they both modify an object and return it
Example: String • Constructors: – "This is syntactic sugar for a constructor" – public String(char[] chs) • Accessors: – public int length() – public char. At() • Transformers (applicative only): – public String substring(int i, int j) – public String concat(String that) (also +) • Etc.
Immutable objects • A String is immutable: it cannot be changed • The String class has no mutative transformers – Operations such as string concatenation create new Strings • Advantages: – Efficient (for most uses) – Easy to use and simple to understand (changing a String in one object doesn’t change it in other objects) • Disadvantage: – Every call to a transformer creates a new String
Example: String. Buffer • Constructors: – public String. Buffer(String s) • Accessors: – public int length() – public char. At() • Transformers (applicative): – public String substring(int i, int j) • Transformers (mutative): – public String. Buffer append(Object obj) • Etc.
Mutable objects • A String. Buffer is mutable: it can be changed • The String. Buffer class has both applicative and mutative transformers • Advantage: – Efficient (for doing a lot of string manipulation) • Disadvantage: – Can be confusing (example coming up shortly) • Operations on Strings are done by converting to String. Buffers, doing the work, and converting back
Safe use of Strings public class Person { private String name; Person(String name) { this. name = name; } } String jan = "Jan"; Person doctor = new Person(jan); String dan = "D" + jan. substring(1, 2); Person secretary = new Person(dan);
Unsafe use of String. Buffers public class Person { private String. Buffer name; Person(String. Buffer name) { this. name = name; } } String. Buffer buffer = new String. Buffer("Jan"); Person doctor = new Person(buffer); buffer. set. Char. At(0, 'D'); Person secretary = new Person(buffer);
Summary • A class should define a necessary and sufficient set of operations • Convenience operations should be justified • Operations can be classified as: – Constructors – Accessors – Transformers (applicative or mutative) • Immutable objects are often preferable to mutable objects
The End
- Slides: 17