![]() |
|
Consider the following data definitions:
;; A home is either ;; A house is (make-house
string num num) ;; Examples (make-house "1600 Penn
Ave." 20 10) ;; An apt is (make-apt string num
num) ;; Examples: (make-apt "123 Faceless
Alley" 42 1)
;; - a house or
;; - an apartment
;; where address is the street address
;; nBed is the number of bedrooms and
;; nBath is the number of baths
(define-struct house ( address nBed nBath))
(make-house "314 Skid Row" 1 0)
(make-house "1 Suburb Way" 4 2)
;; where address is the street address
;; aptNum is the apartment number
;; nBed is the number of bedrooms
(define-struct apt (address aptNum nBed ))
(make-apt "2 Swanky Drive" 1024 3)
(make-apt "64 Garden Drive" 256 2)
By the above definitions, we can say that a house is a home and an apt is a home. That is, a home is an abstraction of both a house and an apt.
;; A person is (make-person
symbol num home) ;; Examples (make-person 'Stephen 42 (make-apt
"512 Lost Trail" 1010 1)) |
Person is a struct that holds a struct, what we call a "compound data structure". By the above definition, we can say that a person "has a" home.
We've made templates for structures like home, apt and house, but how would we go about creating a template for person?
The trick here is to never overlook the simple answer. Remember that home is just another data type, so we should treat it as any other data type we've encountered before in this situation:
(define (aPersonFunc ...aPerson...) (cond [(person? aPerson) (...(person-name aPerson)... ...(person-age aPerson)... ...(person-home aPerson)...)] [else Something for Invalid Input]))
Why not also include all the attributes of home?
Think Abstraction and Encapsulation -- two related notions here.
The abstraction of a person is that a person has a home, a whole home, not pieces of a home. The person deals with a home as a single, encapsulated entity. Ones says that the person deals with a home at the abstraction level of an abstract encapsulated entity, not at the lower abstraction level of the parts of a home.
To deal with the person's home, a function on person must therefore use various other functions that deal with processing home structures.
This is not to say that some function on a person may indeed use the parts of a home. Such a function may use the accessor functions of home, but these accessors are no different than any other function that processes a home structure. Thus even in these situations, the function on the person, at the most abstract level, still deals with the home as an encapsulated entity.
A couple of mantras:
Any given function should run at a specified and fixed abstraction level. Encapsulation is one way of expressing abstraction.
|
Above, we encountered the two main types of relationships that data structures can have to each other: "is a" and "has a".
As evidenced by the data structure specification that is purely comments, "is a" is not a well developed concept in Scheme, so I will use the object-oriented programming ("OOP") term here: "is a" is a relational concept refered to as "inheritance" because it is akin to the notions of parent-child. Examples of inheritance relationships:
We can graphically represent inheritance using a standard "Unified Modeling Language" (UML) diagram (simplified here):
(Note the solid lines and solid arrowheads)
"Has a" is better developed in Scheme and is a relational concept refered to as "composition" because it deals with how one entity is composed of other entities. That is, the parts of one entity are other entities in of themselves. Examples of composite relationships:
Notice how composition can be one-way or two-way. Once again, we can represent compositional relationships using a simplified UML diagram:
(Note the solid lines, open arrowheads, the numbers that tell us how many items are held, and a label that tells us the attribute name if it is different than the structure's name.)
©2003 Stephen Wong