/* --- CSU213 Fall 2006 Lecture Notes --------- Copyright 2006 Viera K. Proulx Lecture 9: September 25, 2006 Designing methods for complex class hierarchies ----------------------------------------------- */ /*----------------------------------------------- We start with a class diagram and examples of data: +-----+ | Emp | +-----+ +-----+ | / \ --- | ---------------------- | | +-------------+ +-----------------+ | Worker | | Boss | +-------------+ +-----------------+ | String name | | String name | | int tasks | | String unit | +-------------+ | int tasks | +-| ListofEmp peons | | +-----------------+ v +-----------+ | ListofEmp |<---------------+ +-----------+ | +-----------+ | | | / \ | --- | | | --------------------- | | | | +-------------+ +----------------+ | | MTListofEmp | | ConsListofEmp | | +-------------+ +----------------+ | +-------------+ | Emp first | | | ListofEmp rest |---+ +----------------+ *-----------------------------------------------* class Examples{ Examples(){} Emp wkA = new Worker("A", 3); Emp wkB = new Worker("B", 5); Emp wkC = new Worker("C", 6); Emp wkD = new Worker("D", 4); Emp wkE = new Worker("E", 5); Emp wkF = new Worker("F", 2); Emp wkG = new Worker("G", 8); Emp wkH = new Worker("H", 6); ListofEmp mtlist = new MTListofEmp(); ListofEmp grpAlist = new ConsListofEmp(this.wkC, this.mtlist); Emp mike = new Boss("Mike", "Group A", 10, this.grpAlist); ListofEmp secAlist = new ConsListofEmp(this.mike, new ConsListofEmp(this.wkD, new ConsListofEmp(this.wkE, this.mtlist))); Emp jack = new Boss("Jack", "Section A", 25, this.secAlist); ListofEmp secBlist = new ConsListofEmp(this.wkF, new ConsListofEmp(this.wkG, this.mtlist)); Emp jenn = new Boss("Jenn", "Section B", 15, this.secBlist); ListofEmp secClist = new ConsListofEmp(this.wkH, this.mtlist); Emp pat = new Boss("Pat", "Section C", 20, this.secClist); ListofEmp secDlist = new ConsListofEmp(this.wkB, this.mtlist); Emp pete = new Boss("Pete", "Section D", 10, this.secDlist); ListofEmp operList = new ConsListofEmp(this.jack, new ConsListofEmp(this.jenn, new ConsListofEmp(this.pat, this.mtlist))); Emp dave = new Boss("Dave", "Operations", 70, this.operList); ListofEmp financeList = new ConsListofEmp(this.pete, new ConsListofEmp(this.wkA, this.mtlist)); Emp anne = new Boss("Anne", "Finance", 20, this.financeList); ListofEmp ceoList = new ConsListofEmp(this.dave, new ConsListofEmp(this.anne, this.mtlist)); Emp meg = new Boss("Meg", "CEO", 100, this.ceoList); } *-----------------------------------------------* Our first task is to understand what information is represented by our data. The class names hint at an organizational chart for a company. We come up with several different ways to interpret the data and choose the following: +-------------+ | Meg CEO 100 | +-----+-------+ | +-------------------+--------------+ | | +---------+----------+ +--------+--------+ | Dave Operations 70 | | Anne Finance 20 | +---------+----------+ +--------+--------+ | | +-------------+-+---------------+ +-----+------+ | | | | | +-------+------+ +------+-------+ +-----+-------+ +------+-------+ +--+--+ | Jack SecA 25 | | Jenn SecB 15 | | Pat SecC 20 | | Pete SecD 10 | | A 3 | +------+-------+ +------------+-+ +----------+--+ +-------+------+ +-----+ | | | | +---+--------+-------+ +----+-----+ +----+ | | | | | | | | +-----+--------+ +-+---+ +-+---+ +---+-+ +-+---+ +-+---+ +-+---+ | Mike grpA 10 | | D 4 | | E 5 | | F 2 | | G 8 | | H 6 | | B 5 | +-------+------+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ | +--+--+ | C 6 | +-----+ The numbers represent the number of tasks each person is responsible for. A boss can delegate certain number of tasks to subordinates (peons), but remains responsible for the task. To make sure we really understand the data definition, we rewrite the class diagram data definition in the form of HtDP data definition: -----------------------------------------------*/ /* ;; An Employee is one of ;; -- Worker ;; -- Boss ;; A Boss is (make-sup String Number [Listof Employee]) (define-struct sup (name tasks peons)) ;; A Worker is (make-worker String Number)) (define-struct worker (name tasks)) ;; A [Listof Employee] is one of ;; -- empty ;; -- (cons Employee [Listof Employee]) *-----------------------------------------------* We are now ready to design methods for these classes. The following questions intrigue us: -- How many subordinates does a boss have? -- How many tasks did a boss delegate to subordinates? -- How many tasks is a boss directly responsible for? -- How many levels of subordinates does a boss have? *------ class Boss: method loSub() ------------* We start with a seemingly simple question: Produce a list of the names of all subordinates that a boss has. (Remember to include 'subordinates of subordinates'!) Step 1: Analyze the problem... The method belongs to the class Boss, and does not require any other information than the boss in question. Step 2: Purpose statement and method header. // produce a list of names of all subordinates (direct or indirect) of this boss LoString loSub(){...} -- We assume we know how to define the classes that represent a list fo Strings. Step 3: Examples: anne.loSub() --> new ConsLoString("Pete", new ConsLoString("B", new ConsLoString("A", new MTLoString()))) jenn.loSub() --> new ConsLoString("E", new ConsLoString("F", new MTLoString())) Now that we know how thw list of String-s will be built, we can write any subsequent examples using as a shortcut Scheme-like notation: anne.loSub() --> list ("Pete", "B", "A") jenn.loSub() --> list ("E", "F") Step 4: Template: // produce a list of names of all subordinates (direct or indirect) of this boss LoString loSub(){ ... this.name ... -- String ... this.unit ... -- String ... this.tasks ... -- int ... this.peons ... -- ListofEmp } We know we will not need 'this.unit' or 'this.tasks'. We are not sure about 'this.name'. But, it is clear that we want the list of the names of all peons, as well as the list of all their subordinates... So, let's put on our wishlist the method, to be defined in the classes that represent the list of employees, that produces the desired list of names: // produce the list of all employees and their subordinates from this list LoString loNames() Step 5: Method Body: Using the method in our wish list, we can easily finish the method body: // produce a list of names of all subordinates (direct or indirect) of this boss LoString loSub(){ return this.peons.loNames(); } Step 6: Tests Well --- we have to wait until the method in the wish list is defined. *---- class ListofEmp: method loNames() ---------------------------------------* We already have the purpose statement and the header. Next we need examples: (new MTListofEmp()).loNames() --> new MTLoString() financeList.loNames() --> list ("Pete", "B", "A") jenn.peons.loNames() --> list ("E", "F") From the first example we see that the body of the method 'loNames' in the class MTListofEmp will be: // produce the list of all employees and their subordinates from this list LoString loNames(){ return new MTLoString(); } In the class ConsListofEmp the template will be: // produce the list of all employees and their subordinates from this list LoString loNames(){ ... this.first ... -- Emp ... this.rest ... -- ListofEmp ... this.rest.loNames() ... -- LoString } We read aloud the purpose statement for 'this.rest.loNames() ...: 'Produce the list of all employees and their subordinates from the rest of this list' So, that is fine, but we still need the name of the first employee --- and any subordinates this employee may have. We recall that 'Emp' is an interface, not a class. We need to let the class hierarchy that represents employees handle our request for a list of names. So, let's put on our wishlist the method, to be defined in the classes that represent one employee, that produces the desired list of names: // produce the list of this employee's name together with the names of all // of his/her subordinates LoString getNames(){...} *---- class Emp: method getNames() ----------------------------------------* Again we need examples: wkD.getNames() --> list("D") pete.getNames() --> list ("Pete", "B") jack.getNames() --> list ("Jack", "Mike", "C", "D") Notice, that we get a list of names regardless whether the employee is a worker, or a boss. In the class Worker: -------------------- The first example applies. The template is: ... this.name ... -- String ... this.tasks ... -- int and we see that the body becomes: // produce the list of this employee's name together with the names of all // of his/her subordinates LoString getNames(){ return new ConsLoString(this.name, new MTLoString()); } We run the tests to finish the design of the method 'getNames' in the wish list. In the class Boss: ------------------ The second and third example apply. We see that the list always starts with the name of this boss, and is followed by the list of all subordinates. The template is the following: ... this.name ... -- String ... this.unit ... -- String ... this.tasks ... -- int ... this.peons ... -- ListofEmp ... this.peons.loSub() ... -- LoString and the body then combines the name of the boss with the list of the names of his/her subordinates: // produce the list of this employee's name together with the names of all // of his/her subordinates LoString getNames(){ return new ConsLoString(this.name, this.peons.loSub()); } We can now run the tests for the method 'getNames' in classes that represent one employee. We still need to finish the body of the method 'loNames' in the class ConsListofEmp. We recall our template, but add the method 'getNames' we just defined for the classes that represent ListofEmp: // produce the list of all employees and their subordinates from this list LoString loNames(){ ... this.first ... -- Emp ... this.rest ... -- ListofEmp ... this.first.getNames() ... -- LoString ... this.rest.loNames() ... -- LoString } The body will be: // produce the list of this employee's name together with the names of all // of his/her subordinates LoString loNames(){ return this.first.getNames().append(this.rest.loNames()); } Well - we introduced yet another helper method - in the classes that represent a slit of String-s: // produce a list of String-s by appending the given list of String-s // to this list of String-s LoString append(LoString that) We omit the narrative describing the design of this method --- but include it in the final code. We run the tests for the method 'loNames" in the classes that represent a list of employees and the tests for the original method 'loSub' for the class Boss. Complete code with examples is below. *----------------------------------------------------------------------------* */ // to represent an employee in a company interface Emp { // produce the list of this employee's name together with the names of all // of his/her subordinates LoString getNames(); } // to represent a Worker in a company class Worker implements Emp { String name; int tasks; Worker(String name, int tasks) { this.name = name; this.tasks = tasks; } // produce the list of this employee's name together with the names of all // of his/her subordinates LoString getNames(){ return new ConsLoString(this.name, new MTLoString()); } } // to represent a supervisor in a company class Boss implements Emp { String name; String unit; int tasks; ListofEmp peons; Boss(String name, String unit, int tasks, ListofEmp peons) { this.name = name; this.unit = unit; this.tasks = tasks; this.peons = peons; } // produce a list of names of all subordinates (direct or indirect) of this boss LoString loSub(){ return this.peons.loNames(); } // produce the list of this employee's name together with the names of all // of his/her subordinates LoString getNames(){ return new ConsLoString(this.name, this.peons.loNames()); } } /* +-----------+ | ListofEmp |<---------------+ +-----------+ | +-----------+ | | | / \ | --- | | | --------------------- | | | | +-------------+ +----------------+ | | MTListofEmp | | ConsListofEmp | | +-------------+ +----------------+ | +-------------+ | Emp first | | | ListofEmp rest |---+ +----------------+ */ // to represent a list of employees in a company interface ListofEmp { // produce the list of all employees and their subordinates from this list LoString loNames(); } // to represent a list of employees in a company class MTListofEmp implements ListofEmp { MTListofEmp() { } // produce the list of all employees and their subordinates from this list LoString loNames(){ return new MTLoString(); } } // to represent a list of employees in a company class ConsListofEmp implements ListofEmp { Emp first; ListofEmp rest; ConsListofEmp(Emp first, ListofEmp rest) { this.first = first; this.rest = rest; } // produce the list of all employees and their subordinates from this list LoString loNames(){ return this.first.getNames().append(this.rest.loNames()); } } /* +----------+ | LoString |<---------------+ +----------+ | +----------+ | | | / \ | --- | | | --------------------- | | | | +------------+ +---------------+ | | MTLoString | | ConsLoString | | +------------+ +---------------+ | +------------+ | String first | | | LoString rest |-+ | +---------------+ | | | | +--+ */ // to represent a list of String-s interface LoString { // produce a list of String-s by appending the given list of String-s // to this list of String-s LoString append(LoString that); } // to represent an empty list of String class MTLoString implements LoString { MTLoString() { } // produce a list of String-s by appending the given list of String-s // to this list of String-s LoString append(LoString that){ return that; } } // to represent a nonempty list of String class ConsLoString implements LoString { String first; LoString rest; ConsLoString(String first, LoString rest) { this.first = first; this.rest = rest; } // produce a list of String-s by appending the given list of String-s // to this list of String-s LoString append(LoString that){ return new ConsLoString(this.first, this.rest.append(that)); } } class Examples{ Examples(){} Emp wkA = new Worker("A", 3); Emp wkB = new Worker("B", 5); Emp wkC = new Worker("C", 6); Emp wkD = new Worker("D", 4); Emp wkE = new Worker("E", 5); Emp wkF = new Worker("F", 2); Emp wkG = new Worker("G", 8); Emp wkH = new Worker("H", 6); ListofEmp mtlist = new MTListofEmp(); ListofEmp grpAlist = new ConsListofEmp(this.wkC, this.mtlist); Emp mike = new Boss("Mike", "Group A", 10, this.grpAlist); ListofEmp secAlist = new ConsListofEmp(this.mike, new ConsListofEmp(this.wkD, new ConsListofEmp(this.wkE, this.mtlist))); Emp jack = new Boss("Jack", "Section A", 25, this.secAlist); ListofEmp secBlist = new ConsListofEmp(this.wkF, new ConsListofEmp(this.wkG, this.mtlist)); Emp jenn = new Boss("Jenn", "Section B", 15, this.secBlist); ListofEmp secClist = new ConsListofEmp(this.wkH, this.mtlist); Emp pat = new Boss("Pat", "Section C", 20, this.secClist); ListofEmp secDlist = new ConsListofEmp(this.wkB, this.mtlist); Emp pete = new Boss("Pete", "Section D", 10, this.secDlist); ListofEmp operList = new ConsListofEmp(this.jack, new ConsListofEmp(this.jenn, new ConsListofEmp(this.pat, this.mtlist))); Emp dave = new Boss("Dave", "Operations", 70, this.operList); ListofEmp financeList = new ConsListofEmp(this.pete, new ConsListofEmp(this.wkA, this.mtlist)); Emp anne = new Boss("Anne", "Finance", 20, this.financeList); ListofEmp ceoList = new ConsListofEmp(this.dave, new ConsListofEmp(this.anne, this.mtlist)); Emp meg = new Boss("Meg", "CEO", 100, this.ceoList); // Examples of lists of Strings: LoString mtstring = new MTLoString(); LoString annePeons = new ConsLoString("Pete", new ConsLoString("B", new ConsLoString("A", new MTLoString()))); LoString jennPeons = new ConsLoString("E", new ConsLoString("F", new MTLoString())); LoString annejenn = new ConsLoString("Pete", new ConsLoString("B", new ConsLoString("A", new ConsLoString("E", new ConsLoString("F", new MTLoString()))))); // test for the method append in the classes LoString boolean testAppend = (check this.mtstring.append(this.annePeons) expect this.annePeons) && (check this.annePeons.append(this.jennPeons) expect this.annejenn); // test the method loNames in the class ListofEmp boolean testLoNames = (check this.mtlist.loNames() expect new MTLoString()) && (check this.financeList.loNames() expect new ConsLoString("Pete", new ConsLoString("B", new ConsLoString("A", new MTLoString())))) && (check this.secBlist.loNames() expect new ConsLoString("F", new ConsLoString("G", new MTLoString()))); Boss annie = new Boss("Anne", "Finance", 20, this.financeList); Boss jennie = new Boss("Jenn", "Section B", 15, this.secBlist); // text the method loSub in the class Boss boolean testLoSub = (check this.annie.loSub() expect new ConsLoString("Pete", new ConsLoString("B", new ConsLoString("A", new MTLoString())))) && (check this.jennie.loSub() expect new ConsLoString("F", new ConsLoString("G", new MTLoString()))); // test the method getNames in the classes Worker and Boss (Emp union) boolean testGetNames = (check this.wkD.getNames() expect new ConsLoString("D", new MTLoString())) && (check this.pete.getNames() expect new ConsLoString("Pete", new ConsLoString("B", new MTLoString()))) && (check this.jack.getNames() expect new ConsLoString("Jack", new ConsLoString("Mike", new ConsLoString("C", new ConsLoString("D", new ConsLoString("E", new MTLoString())))))); } /*---------------------------------------------------------------------------- Postlude. The goal here was to illustrate how using the design recipe systematically allows you to solve a complex problem and make sense of how to proceed at each step. Looking at the result it is clear that there are several tasks, but the design recipe guided us in identifying each subproblem in the right context and led us to solution. To practice what you learned, design the methods that answer the other questions posed in the beginning - how many subordinates does a boss have, how many layers of subordination does each boss have, or to make sure that a boss and his subordinates did not delegate more tasks than they were responsible for. For example, if Anne has been assigned only 10 tasks, both she and her boss Meg would have violated this rule. ----------------------------------------------------------------------------*/