Add a comment. Active Oldest Votes. Improve this answer. Bernhard Barker Bernhard Barker But, when we import a package with all its classes, aren't the implementation hidden from the user importing the package?
Wouldn't that make class names as ADTs too? I hope my question is clear. SMVaidhyanathan Once you have a concrete implementation, it's by definition no longer abstract and thus can't be an abstract data type. It's like the difference between a list of properties like texture, what it looks like and what it's made of that defines what ice cream is which would be like an ADT and a recipe for how to make a particular type of ice cream which would be like the class.
The actual ice cream would be like the object. The implementation details are still there, and it affects how the class and its objects behaves and possibly what other methods it has, even if you can't directly see the implementation details.
Kongen Chen Kongen Chen 81 1 1 silver badge 2 2 bronze badges. This should be the selected correct answer! Classes have a slightly different terminology than ADTs and add other characteristics, like: they for example may live in packages their members are called attributes and methods attributes and methods have a certain visibility constraint And methods: can be abstract , too.
Jens Piegsa Jens Piegsa 6, 5 5 gold badges 53 53 silver badges 98 98 bronze badges. A class is an implementation of an abstract data type ADT.
Windl U. Windl 2, 18 18 silver badges 45 45 bronze badges. You mean "a set of types "? Sign up or log in Sign up using Google. Not everything is an object.
Objects implement something which some people call procedural data abstraction. Abstract data types implement a different form of abstraction. With procedural data abstraction objects , you might write something like this for an Int set interface:.
Now consider two implementations of IntSet, say one that's backed by lists and one that's backed by a more efficient binary tree structure:. Notice that unionWith must take an IntSet argument. It could use some run time type information to check it and use a more efficient algorithm if it is, but it still could be passed a ListIntSet and have to fall back to a less efficient algorithm.
Compare this to ADTs, where you may write something more like the following in a signature or header file:. We program against this interface. Notably, the type is left abstract.
You don't get to know what it is. Then we have a BST implementation then provides a concrete type and operations:. Now union actually knows the concrete representations of both s1 and s2, so it can exploit this for an efficient implementation.
We can also write a list backed implementation and choose to link with that instead. I've written C ish syntax, but you should look at e.
Standard ML for abstract data types done properly where you can e. IntSetStruct and ListImpl. IntSetStruct, say. The converse of this is that procedural data abstraction objects allow you to easily introduce new implementations that work with your old ones. But this is a trade-off: you lose informative types for binary methods!
Often you end up having to expose more functionality and implementation details in your interface than you would with an ADT implementation. Now I feel like I'm just retyping the Cook essay, so really, read it! Cook suggests that an example of an abstract data type is a module in C. Indeed, modules in C involve information hiding, since there are public functions that are exported through a header file, and static private functions that don't.
Additionally, often there are constructors e. This is analogous to a way that two different groups from abstract algebra cannot interoperate that is, you can't take the product of an element of one group with an element of another group.
This is because groups assume the closure property of group the product of elements in a group must be in the group. However, if we can prove that two different groups are in fact subgroups of another group G, then we can use the product of G to add two elements, one from each of the two groups.
Cook ties the difference between ADTs and objects partially to the expression problem. Roughly speaking, ADTs are coupled with generic functions that are often implemented in functional programming languages, while objects are coupled with Java "objects" accessed through interfaces. Both generic functions and objects implement polymorphism, but with generic functions, the programmer KNOWS which function will be executed by the generic function without looking at the code of the generic function.
With objects on the other hand, the programmer does not know how the object will handle the arguments, unless the programmers looks at the code of the object. Usually the expression problem is thought of in terms of "do I have lots of representations? In the first case one should organize code by representation as is most common, especially in Java. In the second case one should organize code by functions i. If you organize your code by representation, then, if you want to add extra functionality, you are forced to add the functionality to every representation of the object; in this sense adding functionality is not "additive".
If you organize your code by functionality, then, if you want to add an extra representation - you are forced to add the representation to every object; in this sense adding representations in not "additive". Possible to leverage knowledge of the representation of an ADT for performance, or to prove that the ADT will guarantee some postcondition given a precondition.
This means that programming with ADTs is about doing the right things in the right order chaining together pre-conditions and post-conditions towards a "goal" post condition. In this case, the advantages of objects are that 1 it's easy to change representations without changing the interface and 2 objects can inter-operate. However, this defeats the purpose of OOP in the sense of smalltalk. It should be apparent now that dynamic dispatch i.
This is so that it's possible to define procedures in a generic way, that doesn't assume a particular representation. If the implementation were to change to represent a triangle as two sides and the angle between them, for instance, then the behavior of the code in main would change, and it would no longer print Thus, we need to abstract the initialization of a Triangle , avoiding having to initialize each member directly.
After that call, the Triangle has been properly initialized and can be used with the other ADT functions. Now if the implementation of Triangle changes, as long as the interface remains the same, the code in main will work as before. The following illustrates an implementation of Triangle that represents a triangle by two sides and an angle:. Here, we have added accessor or getter functions for each of the sides, allowing a user to obtain the side lengths without needing to know implementation details.
The user is generally prohibited from accessing struct member variables directly, as those are implementation details of the ADT. This convention also holds in testing an ADT , since tests should only exercise the behavior of an ADT and not its implementation. When designing an abstract data type, we must build a data representation on top of existing types.
Usually, there will be cases where the underlying data representation permits combinations of values that do not make sense for our ADT. For example, not every combination of three double s represents a valid triangle — a double may have a negative value, but a triangle may not have a side with negative length.
The space of values that represent valid instances of a triangle abstraction is a subset of the set of values that can be represented by three double s, as illustrated in Figure Figure 39 Representation invariants define the valid subset of the values allowed by the data representation of an ADT.
We do so by specifying representation invariants for our ADT, which describe the conditions that must be met in order to make an object valid. For a triangle represented as a double for each side, the following representation invariants must hold:. The triangle inequality must hold: the sum of any two sides must be strictly greater than the remaining side. We can use assertions to check for them as well, where possible:.
As mentioned above, we adhere to the convention of only interacting with an ADT through its interface. Usually, this means that we do not access the data members of an ADT in outside code.
However, occasionally we have the need for an ADT that provides no more functionality than grouping its members together. Such an ADT is just plain old data POD 1 , without any functions that operate on that data, and we define its interface to be the same as its implementation. Technically, the Person struct we saw last time is an aggregate but not a POD. What we mention here for POD types generally applies to aggregates as well.
The following is an example of a Pixel struct used as a POD:. The Pixel ADT consists of just a data representation with no further functionality. Since it is a POD, its interface and implementation are the same, so it is acceptable to access its members directly. As with procedural abstraction, data abstraction is also defined in layers, with each layer interacting solely with the interface of the layer below and not its implementation.
For example, we can represent an image using three matrices, one for each color channel. Any code that uses an image relies on the image interface, without needing to know that it is implemented over three matrices. Each matrix in turn can be represented using a single-dimensional array.
Code that uses a matrix relies on the 2D abstraction provided by the interface without needing to know that it is implemented as a 1D array under the hood. Figure 40 Abstraction layers for an image.
0コメント