A class A
class Dormitory {
private Room[] rooms;
/** @return an array containing the students living in room roomNumber **/
public Student[] lookupStudents(int roomNumber) {
return rooms[roomNumber].getOccupants();
}
}
class Room {
private Vector occupants;
/** @return an array containing the occupants of this room **/
public Student[] getOccupants();
/** @effects adds s to this room's occupants **/
public void addOccupant(Student s);
}
The MDD for this program is:
MDDs can tell you information about the structure of the system, which can be used in several ways:
We will cover three design patterns today:
interface CartesianPoint {
/** @return the x coordinate of this point **/
float getX();
/** @return the y coordinate of this point **/
float getY();
...
}
Suppose that you have an already implemented class
PolarPoint, which represents a point, but uses polar
coordinates. The specification for PolarPoint is:
class PolarPoint {
/** @effects creates a new PolarPoint pp such that
* p.r = r && p.theta = theta
**/
PolarPoint(float r, float theta);
/** @return r **/
float getR();
/** @return theta **/
float getTheta();
...
}
If we want to use PolarPoint to satisfy the
CartesianPoint interface, we can write an adapter. We can do
this using either subclassing or delegation. The solution using
subclassing would look like:
class SubclassingCartesianPoint extends PolarPoint
implements CartesianPoint {
/** @return the x coordinate of this point **/
float getX() {
return getR()*Math.sin(getTheta());
}
/** @return the y coordinate of this point **/
float getY() {
return getR()*Math.cos(getTheta());
}
...
}
A solution using delegation would look like:
class DelegatingCartesianPoint implements CartesianPoint {
PolarPoint pp;
DelegatingCartesianPoint(float x, float y) {
pp = new PolarPoint(Math.sqrt(x*x+y*y),Mat.atan(x/y));
}
/** @return the x coordinate of this point **/
float getX() {
return pp.getR()*Math.sin(pp.getTheta());
}
/** @return the y coordinate of this point **/
float getY() {
return pp.getR()*Math.cos(pp.getTheta());
}
...
}
Question: What are the advantages and disadvantages of using
delegation versus subclassing?
Answer: One feature of using subclassing is that all of the methods of the superclass will be inherited. So if the superclass contains additional methods beyond the spec that you need to meet, and these methods are useful, you get these methods for free. However, in some cases, you might not want the ADT to have these methods, which means that you would have to override the methods with exceptions in the subclass. In such a case, delegation is a cleaner solution.
To summarize, the Adapter pattern is useful when you want to use an existing class, but its interface does not match the interface that you need.
Consider a document-processing application. We might want to define abstract classes Document and Application to represent documents and the applications that handle the documents. There may be some functionality common to all types of applications, except that they rely on the creation of new documents of the appropriate type. For instance, consider a method newDocument, which creates a new document, adds it to the application's list of documents, and opens the document. We could write this as:
public void newDocument() {
Document doc = createDocument();
docs.add(doc);
doc.open();
}
where createDocument is a factory method that the concrete
subclasses of Application must implement, that returns a new
document of the correct type. Let's say we wanted to implement an
application to create and edit resumes, called ResumeWizard,
which handles documents of type Resume. The object model for
this system would look like:
In Application, createDocument would be declared abstract as:
public abstract Document createDocument();and in ResumeWizard, it could be implemented as:
public Document createDocument() {
return new Resume();
}
Using this factory method allows us to put the reusable code of
newDocument in the superclass, even though it needs to create
new objects whose types depend on the subclass.
In general, the Factory Method pattern can be used when a class can't anticipate what class it needs to create or when a class wants its subclasses to specify the types of objects to create.
Factory objects can be used for similar reasons. A factory object encapsulates all of the factory methods into one object. In the above example, we could introduce an interface DocumentFactory, which defines a method createDocument. Each Application could contain a field of type DocumentFactory, which would create documents for the specific type of document to be used. The code for the system would be changed to:
class Application {
DocumentFactory factory;
public void newDocument() {
Document doc = factory.createDocument();
docs.add(doc);
doc.open();
}
...
}
class ResumeWizard extends Application {
public ResumeWizard() {
factory = new ResumeFactory();
...
}
...
}
class ResumeFactory implements Factory {
Document createDocument() {
return new Resume();
}
}
There would no longer be a createDocument method in
ResumeWizard.
The Flyweight pattern should be used when:
You have been enlisted to help the 6.170 TAs design a student-tracking database. Unfortunately, the TAs don't practice what they preach, and they've written no specs, only giving you a skeletal outline. You're just going to have to make educated guesses about how these classes should work and what these methods do.
class Roster {
Roster();
void addSection(Section s);
Section lookupSection(int sectionNumber);
Student lookupStudent(String username);
}
class Section {
Section(int sectionNumber);
int getSectionNumber();
void addStudent(Student s);
Student lookupStudent(String username);
}
class Student {
Student(String name);
String getName();
}
Not trusting the TAs, you decide to analyze the quality of this design by constructing a module dependence diagram.
Construct a minimal MDD involving the 4 classes referenced above: Roster, Section, Student, and String. Ignore all self-dependences and dependences based on inheritance. Think about how the methods would be implemented and consider only those dependences which are necessary.
Consider the case from Problem 3.2 of Problem Set 5. Say the UI class needs to know the sizes of both folders and messages. Assume Folder and Message are independent classes, and that each knows its own size.
Now consider the case where Folder and Message are both subclasses of the EmailObject interface, which has a getSize() method.
A simple representation for a character is a data structure that contains the character code, typographic style information (font, size, and style), and the location in the document. However, representing all of this information does not allow sharing. Use the Flyweight pattern to restructure the system to allow for more sharing of character objects. You may assume that documents keep track of the objects in the document in some sort of collection structure. In order to draw a document to the screen, for example, the document code would iterate over all of the objects and call their draw method.
With your new representation of characters, how would you have to change the methods:
/** @effects draws this character to the screen **/ public void draw(); /** @return true iff this characters bounding box include p **/ public boolean intersects(Point p);
A Card represents a person's MIT ID card, which has several attributes, such as the name and ID number of the holder, the color of the card, the label on the card for the type of holder, and the access rights that have been programmed into the card. Access rights are represented by AccessPrivileges, which might encapsulate a location and times of day that the person may access that location (although the actual spec of this class has not been included). The PersonnelOffice contains static methods to retrieve information about people at MIT, such as their addresses and what buildings they may access. The CardOffice class represents the card office, which can create new cards for people. It currently can handle card creation for students and faculty members. The FactoryCardOffice is another class to represent the card office, but which uses uses CardFactorys to create new cards for people. There are two types of CardFactorys in the current system -- StudentCardFactory, which creates cards for students; and FacultyCardFactory, which creates cards for faculty members.