Modern Programming: Object Oriented Programming and Best Practices
上QQ阅读APP看书,第一时间看更新

Drawing an Object

I see a red door and I want to paint it black. No colors any more I want them to turn black.

Rolling Stones, Paint it Black

If object-oriented programming is the activity of modelling a problem in software, then the kinds of diagrams (and verbal descriptions) that software teams use to convey the features and behavior of those objects are metamodeling – the modeling of models. The rules, for example, the constraints implied when using CRC cardshttps://dl.acm.org/citation.cfm?id=74879, are then metametamodels: the models that describe how the models of the models of the problems will work.

Unified Modeling Language

Plenty of such systems (I will avoid the word metametamodels from now on) have been used over time to describe object systems. The UML (Unified Modeling Language) is the result of combining three prior techniques: the three Elven Kings, Grady Booch, Ivar Jacobson, and James Rumbaugh bent their rings of power (respectively, the Booch Method, Object-Oriented Software Engineering, and the Object Modelling Technique – the latter mostly recognized today because the majority of diagrams in the famous Design Patterns book are drawn to its rules) to the One Rational Ring, wielded by Mike Devlin.

As an aside, Rational started as a company making better Ada applications and tools for other Ada programmers to make better Ada applications, including the R1000 workstation, optimized for running Ada programs and featuring an integrated development environment. The R1000 did not take off but the idea of an IDE did, and through a couple of iterations of their Rose product (as well as the UML and Rational Unified Process), made significant inroads into changing the way organizations planned, designed, and built software.

The UML and, to differing extents, its precursor modelling techniques, represent a completist approach to object modelling in which all aspects of the implementation can be represented diagrammatically. Indeed, tools exist to "round-trip" convert UML into compatible languages like Java and back again into the UML representation.

The model you create that both encapsulates enough of the "business" aspects of the system to demonstrate that you have solved a problem and enough of the implementation aspects to generate the executable program is not really a model, it is the program source. In shooting for completeness, the UML family of modelling tools have missed "modelling" completely and simply introduced another implementation language.

If the goal of message-passing is to solve our big problem through the concerted operation of lots of small, independent computer programs loosely coupled by the communications protocol, then we should be able to look at each object through one of two lenses: internal or external. In fact, the boundary itself deserves special consideration, so there are three views:

  1. The "external" lens: What messages can I send to this object? What do I need to arrange in order to send them? What can I expect as a result?
  2. The "internal" lens: What does this object do in response to its messages?
  3. The "boundary" lens: Does the behavior of this object satisfy the external expectations?

The final two of these things are closely intertwingled. Indeed some popular implementation disciplines, such as Test-Driven Development lead you to implement the object internals only through the boundary lens, by saying "I need this to happen when this message is received," then arranging the object's internals so that it does, indeed, happen.

The first is separated from the others, though. From the outside of an object I only need to know what I can ask it to do; if I also need to know how it does it or what goes on inside, then I have not decomposed my big problem into independent, small problems.

UML class diagrams include all class features at all levels of visibility: public, package, protected, and private; simultaneously. Either they show a lot of redundant information (which is not to a diagram's benefit) or they expect the modeler to take the completist approach and solve the whole big problem at once, using the word "objects" to give some of that 1980s high-technology feel to their solution. This is a downhill development from Booch's earlier method, in which objects and classes were represented as fluffy cloud-shaped things, supporting the idea that there's probably some dynamism and complexity inside there but that it's not relevant right now.

Interestingly, as with Bertrand Meyer's statement that "an object is a software machine allowing programs to access and modify a collection of data," explored in the section on analysis and design, we can find the point at which Grady Booch overshot the world of modelling tools in a single sentence in Chapter One of his 1991 book Object-Oriented Design with Applications.

Note

Perhaps there is a general principle in which the left half of a sentence about making software is always more valuable than the right half. If so, then the (Agile Manifestohttp://agilemanifesto.org/) is the most insightfully-designed document in our history.

The sentence runs thus:

Object-oriented design's underlying concept is that one should model software systems as collections of cooperating objects...

So far, so good.

... treating individual objects as instances of a class ...

I would suggest that this is not necessary, and that classes, and particularly inheritance, deserve their own section in this part of the book (see Finding a Method to Run section).

... within a hierarchy of classes.

And here we just diverge completely. By situating his objects within "a hierarchy of classes," Booch is encouraging us to think about the whole system, relating objects taxonomically and defining shared features. This comes from a good intention – inheritance was long seen as the object-oriented way to achieve reuse – but promotes thinking about reuse over thinking about use.

Class-Responsibility-Collaborator

Just as the UML represents a snapshot in the development of a way of describing objects, so do CRC cards, introduced by Kent Beck and Ward Cunningham in 1989, and propagated by Rebecca Wirfs-Brock, Brian Wilkerson, and Lauren Wiener in their textbook Designing Object-Oriented Software.

The CRC card describes three aspects of an object, none of which is a cyclic redundancy check:

  • The Class names
  • The Responsibilities of the object
  • The Collaborators that the object will need to work with

Not only does this school of design focus on the messaging aspect of objects (the responsibilities will be things I can ask it to do and the collaborators will be other objects it asks to do things), but it introduces a fun bit of anthropomorphism. You and I can each pick up a card and "play object," having a conversation to solve a problem, and letting that drive our understanding of what messages will be exchanged.

David West, in his 2004 book, Object Thinking, presents the object cube, which extends the CRC card into three dimensions by adding five more faces:

  • A textual description of instances of the class
  • A list of named contracts (these are supposed to indicate "the intent of the class creator as to who should be able to send particular messages," and in his examples are all either "public" or "private")
  • The "knowledge required" by an object and an indication of where it will get that knowledge
  • The message protocol is a list of messages the object will respond to
  • Events generated by the objects

Some bad news: you can't make a cube out of 3x5 index cards; and you can't buy 5x5 index cards. But that's just an aside. Again, as with using the UML, we've got to record the internals and externals of our object in the same place, and now we need to use large shelves rather than index boxes to store them.

With both of these techniques, the evolution seems to have been one of additive complexity. Yes, you can draw out the network of objects and messages, oh and while you're here you can also...

And rationally, each part of each of these metamodels seems to make sense. Of course, at some point, I need to think about the internals of this object; at some point, I need to consider its instance variables; and at some point, I need to plan the events emitted by the object. Yes, but not at the same point, so they don't need to be visible at the same time on the same model.

Jelly Donuts and Soccer Balls

Ironically, there is a form of object diagram that makes this separation between the externals and internals clear, though I have only seen it in one place: The NeXT (and subsequently Apple) jelly-donut modelhttp://www.cilinder.be/docs/next/NeXTStep/3.3/nd/Concepts/ObjectiveC/1_OOP/OOP.htmld/index.html This isn't a tool that programmers use for designing objects, though: it's an analogy used in some documentation.

It's an analogy that some authors disagree with. In Object Thinking, David West says that the jelly donut model (which he calls the soccer-ball model, after Ken Auer) is the model of choice of the "traditional developer," while "an object thinker" would represent an object anthropomorphically, using a person.

West may well argue that the jelly donut/soccer ball model represents traditional thinking because it reflects the Meyer-ish view that your system is designed by working out what data it needs and then carving that up between different objects. Ironically, Bertrand Meyer would probably also reject the soccer ball model, for an unrelated reason: Eiffel follows the Principle of Uniform Reference, in which an object field or a member function (method) is accessed using the same notation. To an Eiffel programmer, the idea that the data is "surrounded" by the methods is superfluous; the jelly donut indicates the use of a broken language that allows the sweet jelly to escape and make everything else sticky.