Introduction to Eiffel
Object-oriented programming draws on just a few main ideas. I will talk about three of the important ones and illustrate their realization in the Eiffel language.
The first important idea is encapsulation: the packaging of data with means to manipulate it. Such a package, written in a programming language is a class, but an instance of a class in execution (or in storage) is an object.
In Eiffel, everything exists within a class. There are no external variables or routines. A class has features. Features in turn are either attributes or routines.
Attributes store values, including references to objects. They may be constant or variable.
Routines do things. Routines are either procedures or functions. Functions return results and are not supposed to change system state. Procedures change system state but return nothing.
All features, even constant or variable attributes, are said to be “called”. This is perhaps less strange than it might seem, for in Eiffel, a call to a function with no arguments is written the same as a call to an attribute. If in some class you write:
that := the.other
the.other may be either a function or an attribute, in this context it makes no difference.
So, you encapsulate data and the routines that manipulate it in a class. Assertions, mentioned previously, are also parts of a class and serve to express class preconditions, constraints and invariants.
The second important idea is inheritance.
Once you have a class, which describes what you know about the how and why of some sort of object, it may further benefit you to derive a new class from it, with additions and variations, without touching the program code in the original class. Inheritance is a mechanism that allows this.
For example, you might derive BEE from INSECT. Many features of BEE would inherit directly from INSECT, some features would be modified, and BEE would provide a few new features of its own.
A rule of thumb, called the “is-a” rule, furnishes one way to determine whether A might usefully inherit from B. Examine the sentence “A is a B”. Does it make sense? It should if A is a reasonable candidate to inherit from B. For example, “BEE is an INSECT” passes this test, so BEE might inherit from INSECT. Then, INSECT will be ancestor of BEE, and BEE will be a descendent INSECT.
The “has-a” rule furnishes a contrast. If “A has a B” makes more sense than “A is a B”, it may be prudent to let A reference or have a feature of type B, rather than inheriting from B. “BEE has a STINGER” makes more sense than “BEE is a STINGER” or for that matter “STINGER is a BEE”. Therefore, class BEE should have a feature of type STINGER. That makes BEE a client of STINGER, and STINGER a supplier to BEE.
The client-supplier relationship offers a client less detailed control than a descendent. A client may use or not use a feature of a supplier, but it cannot redefine such a feature. Many of a supplier's features may be hidden from a client, while they will be visible to a descendent. The positive side of this is that the client will be relatively unaffected by details of a supplier's implementation and less likely to be impacted by changes in a supplier.
Both client and inheritance relationships can facilitate software reuse. The traditional function call is more akin to the client relationship, and many attempts at reuse in the past, prior to object-oriented approaches, have made use of the function call. However, we still find ourselves writing and rewriting familiar pieces of functional code too complex to make good candidates for library routines.
The implementation details of inheritance may seriously affect its suitability as a mechanism for reuse. In the ideal implementation, problems arising in descendent classes could always be resolved there. Unfortunately, with many languages, problems arising in descendent classes require changes to ancestors.
This becomes more true with multiple inheritance, a technique by which a class may enjoy, or perhaps not enjoy, multiple ancestors. This technique is a powerful one, but it is unavailable in many object-oriented languages and discouraged in most of the rest. Among languages that have reached commercial viability, Eiffel offers a superior implementation of multiple inheritance.
In its broad sense, this indicates a situation where a simple request may elicit different but not entirely inconsistent responses, depending on the target of the request. These responses may be arrived at by entirely different means.
For example, you might have classes that look in part like this:
class INSECT -- Description of a standard -- insect. ... feature flee is do -- How a standard -- insect flees. ... end; -- flee end class BEE inherit INSECT redefine flee end; ... feature flee is do -- How a bee flees. ... end; --flee end class COCKROACH inherit INSECT redefine flee end; ... feature flee is do -- How a cockroach flees. ... end; -- flee end class WATERBUG inherit INSECT redefine flee end; ... feature flee is -- How a water bug flees. ... end; -- flee end
Then, you might have examples of BEE, COCKROACH, and WATERBUG bound to references of INSECT:
-- Define references to an -- INSECT and to a BEE. insect:INSECT; bob:BEE; -- Bind some particular -- insect to the reference insect := bugs.get -- Now you have a BEE, a -- COCKROACH or a WATERBUG. -- Make it flee after its own -- fashion, be it that of BEE, -- COCKROACH, or WATERBUG. -- This is a polymorphic call, -- as the code executed will -- depend on the type of the -- object bound to insect. insect.flee; -- You can't make a WATERBUG -- collect pollen. -- For this you need a BEE, and -- a trial assignment is -- available to assign objects -- that might conform to the -- type of a reference. bob ?= insect; -- Then maybe you can make bob -- the bee collect pollen. -- If he isn't a BEE or a -- conforming type, bob is Void. if bob /= Void then bob.collect_pollen end;
Another sort of polymorphism, sometimes called parametric polymorphism, is supported in Eiffel as genericity.
The final sort of polymorphism I'll mention is found as function overloading in other languages. Here, a function may be defined multiple times, with different types and numbers of arguments. When the function is called, the actual function invoked depends on the argument list.
Function overloading is not implemented in Eiffel. Workarounds are present, and arithmetic operations are handled as special cases, but the general case of function overloading is felt by the designers of Eiffel to be too full of potential ambiguities, type-checking failures, complications, and interactions to be worthwhile just now. A lively thread on this topic is seen from time to time on the newsgroup comp.lang.eiffel.
Special Reports: DevOps
Have projects in development that need help? Have a great development operation in place that can ALWAYS be better? Regardless of where you are in your DevOps process, Linux Journal can help!
With deep focus on Collaborative Development, Continuous Testing and Release & Deployment, we offer here the DEFINITIVE DevOps for Dummies, a mobile Application Development Primer, advice & help from the experts, plus a host of other books, videos, podcasts and more. All free with a quick, one-time registration. Start browsing now...