Nick Brettel's eight puzzle design

From CSSEMediaWiki
(Difference between revisions)
Jump to: navigation, search
Line 62: Line 62:
 
# [[#Rigid Procedure]]
 
# [[#Rigid Procedure]]
 
# [[#Varied Instructions]]
 
# [[#Varied Instructions]]
 +
 +
=== Logging Participants ===
 +
 +
The experiment tested the performance of participants who trained collaboratively, or individually.  After training, all participants worked individually for testing.  This introduced the first design challenge.  Single partipants or pairs of participants can solve the puzzle in the training phase, so pairs and individuals need to be treated the same, but (for logging) produce different output.  Furthermore, a pair ''becomes'' (*alarm bells*) a single participant once training is complete.  This is my proposed design to model participants:
 +
 +
[[imkage:participants.gif]]
 +
 +
To clarify the UML, ''Phase'' (seen in the diagram in the ''Participant'' class) is an enum (see [[Enums Idiom]]) that takes one of two values: ''TRAINING'' or ''TESTING''. 
 +
The variable ''currPhase'' stores the current phase (ie if the participant is training or testing).
 +
The bottom region of a class shows the "properties" (anything for which there is a ''getWhatever()'' method).  The two properties in ''Participant'' (''currRole'' and ''time'') signify there are corresponding getters, but neither of them are actually stored as variable: they are derived attributes.
 +
 +
As you can see, the ''Participant'' class models one or more people taking part in the experiment, and they have two Roles: a ''trainingRole'' and a ''testingRole'' (the singular class name ''Participant'' may be misleading - it can model more than one person if they are working together - however, making it plural is more confusing).  The ''ParticipantRole'' handles how the log data is output, as defined by the ''printData()'' method.  A ''PairRole'' will print out data for both of the participants, while an ''IndividualRole'' will print it out for the single individual.  Also, an individual has a single "name" (not meant literally - just some identifier), while pairs have a couple of ''name''s, one per person.  The reason why an ''IndividualRole'' stores a ''wasCollaborative'' variable is because pairs may split into individuals, but we still need to know they were previously pairs, for the log output.
 +
 +
Observant readers will recognise this is strikingly similar to the [[Strategy pattern]] - or is it [[State pattern]]? Hmmm.  I originally was of the opinion it was [[Strategy Pattern]].  Each different implementation of the ''printData()'' algorithm is encapsulated in its own class, and thus is interchangeable.  This means we can vary the number of people working on the experiment together, and still output data for each person.  However this varies from the typical Strategy.  The roles don't just encapsulate an algorithm, they also store additional data about the "role" - ie the name(s) and ''wasCollaborative'' for individuals (see below), and there's the ''addName()'' method (more on that soon).  However, considering the fact that some participants change from being pairs to singles, perhaps it is a [[State pattern]].  When changing state from a single to a pair, the behaviour changes, like when changing class.  Although the trainingRole or testingRole attributes never change class, this is just due to the ''currPhase'' variable - conceptually we are still changing state.  Thanks to Phil, Blair and Greg for pointing this out at our late-night (err early-morning) discussion session :).
 +
 +
The main advantages of this approach (regardless of whether it is State or Strategy) are that we treat single participants and pairs uniformally, although they have slightly different behaviour, we [[Avoid becomes]], and its easy to extend this design if we needed, say, a ''TripleRole'' for groups of three.  Additionally, as an added bonus, the data about each participants two roles are stored like a history.
 +
For more design pattern discussion, see [[#Design patterns]].
 +
 +
 +
[[Tell, don't ask and [[Law of Demeter]] are followed - the ''Participant'' wraps up calls to the role.  To do this [[Recursion introduction]] is used: the participant just relays calls to ''printData()'' and ''addName()'' to methods in one of its roles (of the same name), depending on the ''currPhase''.  In this situation I think applying these maxims is definitely beneficial.  The ''ParticipantRole'' becomes transparent so we don't even need to know that a participant has a role, as far as anyone using a ''Participant'' is concerned, it can ''addName()'' and ''printData()'', and we don't care how.  It is effectively InformationHiding.  The alternative, something like ''participant.getCurrentRole().addName()'', requires the client to know that the participant has a role, it has a getter to access it, it stores the names, and we can change them using the method ''addName()''...
 +
 +
So what is this ''addName()'' method?  I needed some way to set a participants name.  However, to set a pair's names, we would need two names, as opposed to the one required to set an individual's name.  But if ''IndividualRole'' and ''PairRole'' have different ''setName()'' methods (with a different number of arguments), then a ''Participant'' could only ''setName()'' if it knew whether its role is training or testing.  A ''Participant'' shouldn't need to know the types of its roles, just that it has roles, and what their interface is.  Simply put, I want to [[Program to the interface not the implementation]].  To do this, a uniform way to set name(s) is required, even though we don't know how many names.  I choose to do this by using a common interface: an ''addName()'' method sets one name, and it can just be called a couple of times to set a couple of names (for pairs).  I noticed later that this was a similar idea to the [[Builder pattern]] (I don't think I can go so far as to say this is an application of the pattern) - we add parts (names) until we have built a product, and then we normally get the product (in my case, we change the product by adding parts, so there is no need to get it - we already had it).  An alternative approach would have been to pass in a Collection of names.  However, I decided this was not as good, as the number of items in the collection would be very small (and a majority of the time participants are individual), so there would be a lot of unnecessary Collections, and they do seem like overkill.

Revision as of 00:03, 23 September 2008

Contents

Intro

My project is on my Cosc411 experiment. This experiment was exploring whether people can solve a problem-solving task quicker when collaborating. Participants would solve #The Eight Puzzle five times, either individually or in pairs (training phase), then solve it five times alone (testing phase). The experiment was mostly automated (so they'd just read instructions, click next, etc.). Their performance was logged.

This introduced a few design challenges:

  • Single partipants and pairs of participants can solve the puzzle in the training phase, so pairs and individuals need to be treated the same, but (for logging) produce different output
  • The whole experiment follows a rigid process, so we need a nice way to define what we show and when.
  • Moreover, participants can take one of three paths. Individual, Collaborative, or Collaborative-resume (when the pair splits up after training, one person can continue, the other jumps to the same stage).
  • Individuals or Pairs need to get slightly different instructions, but they are very similar.
  • The experiment is largely GUI based, but we want to separate the GUI out as much as possible.

Furthermore, I would like to be able to provide a framework from which one can easily extend the experiment if needed in the future. For example:

  • can add participants who train in groups of three
  • can solve a different puzzle
  • can add an extra slide of instructions

All the code was written this semester (while taking Cosc427) and I tried to make a good design following the ideas we were taught. So effectively, I started with nothing. However, after the experiment was run I decided to move into a refactoring stage (specifically for this 427 project) with two goals:

  1. to further improve the design (duh)
  2. to refactor the design such that (in a hypothetical situation) it could be easily extended for future similar collaboration experiments

You can see the design as it was before refactoring at #Old Design.

Contents

Code

The code is written in Java 5.0 (stupid version numbers), and you can get it here: media:427projectCode.tgz.

To run it, run `java ExperimentGui`. You can run collaborative mode with `-c` or collaborative resume with `-cr` flags.

The Eight Puzzle

The eight-puzzle is a simple game where there are 8 tiles and one blank arranged in a 3x3 grid. A tile can be moved if it is adjacent to the blank, as if it is being slid across (left, right, up or down). There is a similar fifteen-puzzle where the pieces are in a 4x4 grid.

In fact, a version of the puzzle appeared on the original Macintosh System, thanks to Andy Hertzfeld, and remained on the OS for a long time following. Anyways, I digress. You can see a screenshot of the puzzle below.

8 Puzzle

See wikipedia for more details...

Old Design

This is what my design looked like when the program was actually used to run the experiment. After this, I put on a refactoring hat for the purposes of this Cosc427 assignment. I've shown it here so I can talk about some of the changes made in design.

Firstly, let me apologise that it is so big, and that you have to scroll sideways :).

And here it is:

OldDesign.gif

Tour of the Design

Here, you get a step-by-step tour through the design. This is the logical journey that digests the design in small bites. Just click the links below, in order, for a look at the main parts of the design. It'll be built up bit-by-bit, and then looking at the ../BigPicture should make more sense. Note this is not necessarily a chronological order of how it was designed, only the final design is shown, built up piecewise, in the sequence that (I think) is easiest to comprehend.

  1. #Logging Participants
  2. #Slide Show
  3. #Rigid Procedure
  4. #Varied Instructions

Logging Participants

The experiment tested the performance of participants who trained collaboratively, or individually. After training, all participants worked individually for testing. This introduced the first design challenge. Single partipants or pairs of participants can solve the puzzle in the training phase, so pairs and individuals need to be treated the same, but (for logging) produce different output. Furthermore, a pair becomes (*alarm bells*) a single participant once training is complete. This is my proposed design to model participants:

imkage:participants.gif

To clarify the UML, Phase (seen in the diagram in the Participant class) is an enum (see Enums Idiom) that takes one of two values: TRAINING or TESTING. The variable currPhase stores the current phase (ie if the participant is training or testing). The bottom region of a class shows the "properties" (anything for which there is a getWhatever() method). The two properties in Participant (currRole and time) signify there are corresponding getters, but neither of them are actually stored as variable: they are derived attributes.

As you can see, the Participant class models one or more people taking part in the experiment, and they have two Roles: a trainingRole and a testingRole (the singular class name Participant may be misleading - it can model more than one person if they are working together - however, making it plural is more confusing). The ParticipantRole handles how the log data is output, as defined by the printData() method. A PairRole will print out data for both of the participants, while an IndividualRole will print it out for the single individual. Also, an individual has a single "name" (not meant literally - just some identifier), while pairs have a couple of names, one per person. The reason why an IndividualRole stores a wasCollaborative variable is because pairs may split into individuals, but we still need to know they were previously pairs, for the log output.

Observant readers will recognise this is strikingly similar to the Strategy pattern - or is it State pattern? Hmmm. I originally was of the opinion it was Strategy Pattern. Each different implementation of the printData() algorithm is encapsulated in its own class, and thus is interchangeable. This means we can vary the number of people working on the experiment together, and still output data for each person. However this varies from the typical Strategy. The roles don't just encapsulate an algorithm, they also store additional data about the "role" - ie the name(s) and wasCollaborative for individuals (see below), and there's the addName() method (more on that soon). However, considering the fact that some participants change from being pairs to singles, perhaps it is a State pattern. When changing state from a single to a pair, the behaviour changes, like when changing class. Although the trainingRole or testingRole attributes never change class, this is just due to the currPhase variable - conceptually we are still changing state. Thanks to Phil, Blair and Greg for pointing this out at our late-night (err early-morning) discussion session :).

The main advantages of this approach (regardless of whether it is State or Strategy) are that we treat single participants and pairs uniformally, although they have slightly different behaviour, we Avoid becomes, and its easy to extend this design if we needed, say, a TripleRole for groups of three. Additionally, as an added bonus, the data about each participants two roles are stored like a history. For more design pattern discussion, see #Design patterns.


[[Tell, don't ask and Law of Demeter are followed - the Participant wraps up calls to the role. To do this Recursion introduction is used: the participant just relays calls to printData() and addName() to methods in one of its roles (of the same name), depending on the currPhase. In this situation I think applying these maxims is definitely beneficial. The ParticipantRole becomes transparent so we don't even need to know that a participant has a role, as far as anyone using a Participant is concerned, it can addName() and printData(), and we don't care how. It is effectively InformationHiding. The alternative, something like participant.getCurrentRole().addName(), requires the client to know that the participant has a role, it has a getter to access it, it stores the names, and we can change them using the method addName()...

So what is this addName() method? I needed some way to set a participants name. However, to set a pair's names, we would need two names, as opposed to the one required to set an individual's name. But if IndividualRole and PairRole have different setName() methods (with a different number of arguments), then a Participant could only setName() if it knew whether its role is training or testing. A Participant shouldn't need to know the types of its roles, just that it has roles, and what their interface is. Simply put, I want to Program to the interface not the implementation. To do this, a uniform way to set name(s) is required, even though we don't know how many names. I choose to do this by using a common interface: an addName() method sets one name, and it can just be called a couple of times to set a couple of names (for pairs). I noticed later that this was a similar idea to the Builder pattern (I don't think I can go so far as to say this is an application of the pattern) - we add parts (names) until we have built a product, and then we normally get the product (in my case, we change the product by adding parts, so there is no need to get it - we already had it). An alternative approach would have been to pass in a Collection of names. However, I decided this was not as good, as the number of items in the collection would be very small (and a majority of the time participants are individual), so there would be a lot of unnecessary Collections, and they do seem like overkill.

Personal tools