2005 Exam answers
m (Reverted edits by Ebybymic (Talk); changed back to last version by Aidan Bebbington) |
|||
(12 intermediate revisions by 4 users not shown) | |||
Line 16: | Line 16: | ||
#: ''Answer:'' [[Avoid concrete base classes]] | #: ''Answer:'' [[Avoid concrete base classes]] | ||
# [4 marks] Very badly designed software should be completely rewritten rather than progressively edited. | # [4 marks] Very badly designed software should be completely rewritten rather than progressively edited. | ||
− | #: ''Answer:'' | + | #: ''Answer:'' The Reconstruction pattern from the [[Big ball of mud]] paper by Foote and Yoder |
== Question 2 == | == Question 2 == | ||
Line 23: | Line 23: | ||
:''Answer:'' | :''Answer:'' | ||
+ | :Style and elegance have a number of benefits: | ||
+ | :*A clean design is easier to extend. Adding features to convoluted code requires much more thought and effort to avoid accidentally breaking other parts of the system. A good design will make it easier to quickly deliver changes to clients in the future and will save on future development costs. | ||
+ | :*The code will be easier to understand, so new developers and developers working in new areas of the code will not have to spend as much time learning how the system works. This will save in development costs. | ||
+ | :*Elegant code is less likely to be buggy, and bugs that do exist are easier to fix. This will improve the acceptance of the software by clients and reduce support costs. | ||
+ | :*Developers will have much better morale if they are working with nice code. Good morale leads to better productivity and staff retention. | ||
+ | |||
+ | :To me, OO design is about creating models that are easily comprehensible and maintainable by humans. We humans are influenced by style and elegance, so this suggests it is important to design. One could implement an entire banking system with a single class and a single String field; it is Turing complete. However, this clearly fails to model the domain effectively. This is obvious because the design has little style and elegance. So to me, a lack of style and elegance suggests some other more fundamental, concrete issue with the design. It's just that the lack of style and elegance is often the most immediately obvious symptom. There may be situations where we sacrifice style and elegance in favor of a more sophisticated and flexible design. However, this is not common and should only be done as necessary. | ||
== Question 3 == | == Question 3 == | ||
Line 29: | Line 36: | ||
:''Answer:'' | :''Answer:'' | ||
+ | |||
+ | '''Conflicts:''' | ||
+ | * [[Do the simplest thing that could possibly work]]: To apply the Law of Demeter, wrapper methods often have to be added to classes, which is extra work. | ||
+ | * [[Don't repeat yourself]]: If wrapper methods are being used extensively, this could be viewed as repeating yourself since these wrapper methods are all very similar. If multiple classes wrap the same kind of object, some wrapper methods may end up being exactly duplicated. | ||
+ | * [[Single responsibility principle]] and [[One key abstraction]]: If a class has to wrap its contained objects and provide a public interface to perform functionality on them, it will effectively have multiple responsibilities. | ||
+ | |||
+ | '''Support:''' | ||
+ | * [[Keep related data and behavior in one place]] | ||
+ | * [[Tell, don't ask]] | ||
+ | * [[Message chain smell]] | ||
+ | * [[Beware of many accessors]] | ||
+ | * [[Don't expose mutable attributes]] | ||
== Question 4 == | == Question 4 == | ||
Line 35: | Line 54: | ||
:''Answer:'' | :''Answer:'' | ||
+ | |||
+ | * I would advise the GoF to leave out the [[Interpreter]] pattern because I think it is an analysis pattern rather than a general design pattern. All other design patterns are applicable to a wide range of domains while the interpreter pattern is a specific version of the [[Composite]] pattern applied to the domain of languages. I therefore feel that it does not fit in with the other patterns in the book. | ||
+ | |||
+ | * I would also advise the GoF to leave out the [[Singleton]] pattern. I think this pattern is a classic example of a pattern that is easily overused by developers despite the fact that it can have some unwanted consequences. Therefore, I feel that the singleton pattern should be excluded from the book to not further encourage its use. | ||
+ | |||
+ | * I would suggest a modification to the [[Composite]] pattern. In particular, I would suggest moving the addChild, removeChild and getChild methods from the Component to the Composite. The reason for this is that in the current design these methods are overridden in the Leaf class to do nothing. This results in the contract for these methods being quite vague and complex. In fact, clients can't really be sure if a child was added or removed when calling this method because they might not know if they are dealing with a Leaf or Composite. See [[Composite]] for a more detailed explanations of the options for this pattern and their consequences. | ||
== Question 5 == | == Question 5 == | ||
Line 44: | Line 69: | ||
== Question 6 == | == Question 6 == | ||
− | 6. [18 marks] Suggest a series of refactorings (from | + | 6. [18 marks] Suggest a series of refactorings (from [[Martin Fowler 1999]]) that you would apply to the Java code of Figure 1. (Don’t just name them – explain how the code would change.) |
− | public class Snorkulator { | + | public class Snorkulator {<br/> |
− | + | public int startX; | |
+ | public int startY;<br/> | ||
+ | public int endX; | ||
+ | public int endY;<br/> | ||
+ | private Crudule[] crudules;<br/> | ||
+ | private int frubule;<br/> | ||
+ | public Snorkulator(int theFrubule, Crudule[] theCrudules) { | ||
+ | theFrubule++; | ||
+ | frubule = theFrubule; | ||
+ | initCrudules(theCrudules); | ||
+ | }<br/> | ||
+ | private void initCrudules(Crudule[] theCrudules) { | ||
+ | crudule = theCrudules; | ||
+ | }<br/> | ||
+ | public Crudule[] getCrudules() { | ||
+ | crudules[frubule-1].init(); | ||
+ | return crudules(); | ||
+ | }<br/> | ||
+ | public void setStart(int x, int y) { | ||
+ | startX = x; | ||
+ | startY = y; | ||
+ | }<br/> | ||
+ | public void setEnd(int x, int y) { | ||
+ | endX = x; | ||
+ | endY = y; | ||
+ | }<br/> | ||
+ | public void glarp() { | ||
+ | if (frubule == 1) | ||
+ | crudule[0].exglarp((startX–endX)/2, (startY–endY)/2); | ||
+ | else if (frubule == 2) | ||
+ | crudule[1].inglarp((startY–endY)/2, (startX–endX)/2); | ||
+ | else | ||
+ | crudule[frubule-1].noglarp(startX + 18, startY + 32); | ||
+ | } | ||
} | } | ||
+ | |||
:''Answer:'' | :''Answer:'' | ||
+ | :*The ''start'' and ''end'' variables: | ||
+ | :**[[Extract Class]]: Extract x and y coordinates into a new class called ''Point'' and replace ''startX'', ''startY'', ''endX'' and ''endY'' with two ''Point'' objects: ''start'' and ''end'' | ||
+ | :**[[Encapsulate Field]]: Make ''start'' and ''end'' private and make setters and getters for them. In combination with the addition of the ''Point'' class, the old ''setStart'' and ''setEnd'' methods which take two parameters will be replaced by single parameter versions. This also has the effect of [[Introduce Parameter Object]]. | ||
+ | |||
+ | :*The constructor | ||
+ | :**[[Remove assignments to Parameters]]: Don't increment ''theFrubule'', instead increment ''frubule'' after the assignment. (This will be redundant after making Frubule a class; see below) | ||
+ | :**[[Inline Method]]: Remove ''initCrudules'' and instead use the assignment directly in the constructor. | ||
+ | |||
+ | :*The ''getCrudules'' method: | ||
+ | :**[[Encapsulate Collection]]: Have ''getCrudules'' return a read-only version of ''crudules'' and add methods to add and remove objects from it. | ||
+ | :**[[Separate Query from Modifier]]: Move the first line of ''getCrudules'' (with the ''init'' call) to a separate method. | ||
+ | |||
+ | :*The ''glarp'' method: | ||
+ | :**[[Introduce Explaining Variable]]: Add two variables for (startX-endX)/2 and (startY-endY)/2 respectively. | ||
+ | :**[[Replace Conditional with Polymorphism]]: Use polymorphism rather than using a value switch on ''frubule''. (See below) | ||
+ | |||
+ | :*The ''Frubule'' class: | ||
+ | :**[[Replace Type Code with Class]]: Create a new class, Frubule, to replace the frubule field. It appears that this class should also contain the appropriate crudule in order to [[Replace Conditional with Polymorphism]] in the glarp method. If this is appropriate the Frubule class will handle all matters of Crudules, leaving the Snorkulator to maintain two Points and a Frubule. | ||
== Question 7 == | == Question 7 == | ||
[36 marks] See [[Sorcerers design]]. | [36 marks] See [[Sorcerers design]]. |
Latest revision as of 03:17, 25 November 2010
Contents |
Question 1
[20 marks for whole question] For each of the following descriptions (a) – (e):
- [1 mark each] Name a maxim (or pattern, code smell, etc) that captures the idea.
- [1 mark each] Explain the rationale for this maxim (i.e. why the maxim exists).
- [2 marks each] Comment on the validity and value of this maxim.
- [4 marks] Methods that change attribute values should be distinct from methods that merely return values.
- Answer: Command query separation
- [4 marks] Methods should not directly access attributes defined by a superclass.
- Answer: Avoid protected data
- [4 marks] Classes should be designed around their behaviour, rather than their data.
- [4 marks] Classes that have subclasses should not be directly instantiated.
- Answer: Avoid concrete base classes
- [4 marks] Very badly designed software should be completely rewritten rather than progressively edited.
- Answer: The Reconstruction pattern from the Big ball of mud paper by Foote and Yoder
Question 2
[6 marks] Some employers believe that aesthetic issues such as style and elegance have no place in OO software design: all that matters is functionality. Is this true? Write an argument to convince a sceptical employer of your view.
- Answer:
- Style and elegance have a number of benefits:
- A clean design is easier to extend. Adding features to convoluted code requires much more thought and effort to avoid accidentally breaking other parts of the system. A good design will make it easier to quickly deliver changes to clients in the future and will save on future development costs.
- The code will be easier to understand, so new developers and developers working in new areas of the code will not have to spend as much time learning how the system works. This will save in development costs.
- Elegant code is less likely to be buggy, and bugs that do exist are easier to fix. This will improve the acceptance of the software by clients and reduce support costs.
- Developers will have much better morale if they are working with nice code. Good morale leads to better productivity and staff retention.
- To me, OO design is about creating models that are easily comprehensible and maintainable by humans. We humans are influenced by style and elegance, so this suggests it is important to design. One could implement an entire banking system with a single class and a single String field; it is Turing complete. However, this clearly fails to model the domain effectively. This is obvious because the design has little style and elegance. So to me, a lack of style and elegance suggests some other more fundamental, concrete issue with the design. It's just that the lack of style and elegance is often the most immediately obvious symptom. There may be situations where we sacrifice style and elegance in favor of a more sophisticated and flexible design. However, this is not common and should only be done as necessary.
Question 3
[6 marks] Identify maxims that either conflict with or support the Law of Demeter. For each one, explain how it conflicts with / supports the Law of Demeter. Using these ideas as justification, make a recommendation for how (or whether) experienced software engineers should apply the Law of Demeter.
- Answer:
Conflicts:
- Do the simplest thing that could possibly work: To apply the Law of Demeter, wrapper methods often have to be added to classes, which is extra work.
- Don't repeat yourself: If wrapper methods are being used extensively, this could be viewed as repeating yourself since these wrapper methods are all very similar. If multiple classes wrap the same kind of object, some wrapper methods may end up being exactly duplicated.
- Single responsibility principle and One key abstraction: If a class has to wrap its contained objects and provide a public interface to perform functionality on them, it will effectively have multiple responsibilities.
Support:
- Keep related data and behavior in one place
- Tell, don't ask
- Message chain smell
- Beware of many accessors
- Don't expose mutable attributes
Question 4
[10 marks] If the Gang of Four decided to produce a new edition of their design patterns book, (updated with UML diagrams and examples in current OO languages), and they asked for your advice on specific ways to improve the book and its patterns, what would you tell them?
- Answer:
- I would advise the GoF to leave out the Interpreter pattern because I think it is an analysis pattern rather than a general design pattern. All other design patterns are applicable to a wide range of domains while the interpreter pattern is a specific version of the Composite pattern applied to the domain of languages. I therefore feel that it does not fit in with the other patterns in the book.
- I would also advise the GoF to leave out the Singleton pattern. I think this pattern is a classic example of a pattern that is easily overused by developers despite the fact that it can have some unwanted consequences. Therefore, I feel that the singleton pattern should be excluded from the book to not further encourage its use.
- I would suggest a modification to the Composite pattern. In particular, I would suggest moving the addChild, removeChild and getChild methods from the Component to the Composite. The reason for this is that in the current design these methods are overridden in the Leaf class to do nothing. This results in the contract for these methods being quite vague and complex. In fact, clients can't really be sure if a child was added or removed when calling this method because they might not know if they are dealing with a Leaf or Composite. See Composite for a more detailed explanations of the options for this pattern and their consequences.
Question 5
5. [4 marks] The wiki pages ConflictingIdeas and DesignPatternsBreakRules describe opposing OO design concepts. Identify and describe a conflict that has not already been documented on these pages.
- Answer:
Question 6
6. [18 marks] Suggest a series of refactorings (from Martin Fowler 1999) that you would apply to the Java code of Figure 1. (Don’t just name them – explain how the code would change.)
public class Snorkulator {
public int startX; public int startY;
public int endX; public int endY;
private Crudule[] crudules;
private int frubule;
public Snorkulator(int theFrubule, Crudule[] theCrudules) { theFrubule++; frubule = theFrubule; initCrudules(theCrudules); }
private void initCrudules(Crudule[] theCrudules) { crudule = theCrudules; }
public Crudule[] getCrudules() { crudules[frubule-1].init(); return crudules(); }
public void setStart(int x, int y) { startX = x; startY = y; }
public void setEnd(int x, int y) { endX = x; endY = y; }
public void glarp() { if (frubule == 1) crudule[0].exglarp((startX–endX)/2, (startY–endY)/2); else if (frubule == 2) crudule[1].inglarp((startY–endY)/2, (startX–endX)/2); else crudule[frubule-1].noglarp(startX + 18, startY + 32); } }
- Answer:
- The start and end variables:
- Extract Class: Extract x and y coordinates into a new class called Point and replace startX, startY, endX and endY with two Point objects: start and end
- Encapsulate Field: Make start and end private and make setters and getters for them. In combination with the addition of the Point class, the old setStart and setEnd methods which take two parameters will be replaced by single parameter versions. This also has the effect of Introduce Parameter Object.
- The start and end variables:
- The constructor
- Remove assignments to Parameters: Don't increment theFrubule, instead increment frubule after the assignment. (This will be redundant after making Frubule a class; see below)
- Inline Method: Remove initCrudules and instead use the assignment directly in the constructor.
- The constructor
- The getCrudules method:
- Encapsulate Collection: Have getCrudules return a read-only version of crudules and add methods to add and remove objects from it.
- Separate Query from Modifier: Move the first line of getCrudules (with the init call) to a separate method.
- The getCrudules method:
- The glarp method:
- Introduce Explaining Variable: Add two variables for (startX-endX)/2 and (startY-endY)/2 respectively.
- Replace Conditional with Polymorphism: Use polymorphism rather than using a value switch on frubule. (See below)
- The glarp method:
- The Frubule class:
- Replace Type Code with Class: Create a new class, Frubule, to replace the frubule field. It appears that this class should also contain the appropriate crudule in order to Replace Conditional with Polymorphism in the glarp method. If this is appropriate the Frubule class will handle all matters of Crudules, leaving the Snorkulator to maintain two Points and a Frubule.
- The Frubule class:
Question 7
[36 marks] See Sorcerers design.