2008 Exam answers
m (Reverted edits by Ebybymic (Talk); changed back to last version by Nelson Shaw) |
|||
(4 intermediate revisions by 3 users not shown) | |||
Line 54: | Line 54: | ||
***[[Information hiding]] | ***[[Information hiding]] | ||
***[[Avoid protected data]] | ***[[Avoid protected data]] | ||
+ | *Make the customers field of Broker depend on abstract class rather than implementation. E.g. change ArrayList to List. | ||
+ | **'''Violations''' | ||
+ | ***[[Program to the interface not the implementation]] | ||
==Question 2== | ==Question 2== | ||
Line 61: | Line 64: | ||
'''b) A maxim that might be measured automatically by software tools''' | '''b) A maxim that might be measured automatically by software tools''' | ||
* There are a lot of maxims like this that can be measured automatically. I think essentially any maxim which can be evaluated by simply looking at the syntax of a program or the relationships between parts of a program can be easily measured. Examples include: | * There are a lot of maxims like this that can be measured automatically. I think essentially any maxim which can be evaluated by simply looking at the syntax of a program or the relationships between parts of a program can be easily measured. Examples include: | ||
− | **[[Acyclic dependencies principle]] - This is easy to measure by looking at which parts of the program depend on each other. | + | **[[Acyclic dependencies principle]] - This is easy to measure by looking at which parts of the program depend on each other. In some languages, cycles of include statements won't compile, so this maxim already can be measured in some cases. |
**[[Avoid downcasting]] - This can be easily measured by looking for casts that go down the inheritance hierarchy. | **[[Avoid downcasting]] - This can be easily measured by looking for casts that go down the inheritance hierarchy. | ||
**[[Law of Demeter]] - This can be measured by looking at method calls to check that they all comply with the law | **[[Law of Demeter]] - This can be measured by looking at method calls to check that they all comply with the law | ||
Line 76: | Line 79: | ||
* I think that the Strategy pattern violates Riel's heuristic [[Keep related data and behavior in one place]] because it deliberately separates the behaviour to be performed from the data it operates on. -- [[User:JaninaVoigt|Janina]] | * I think that the Strategy pattern violates Riel's heuristic [[Keep related data and behavior in one place]] because it deliberately separates the behaviour to be performed from the data it operates on. -- [[User:JaninaVoigt|Janina]] | ||
* For a very similar reason it also violates [[Behavioral completeness]] | * For a very similar reason it also violates [[Behavioral completeness]] | ||
+ | * The [[Visitor]] pattern violates the [[Acyclic dependencies principle]] | ||
'''e) A GoF pattern that allows a single object to do the job of multiple similar objects by externalising their changeable properties.''' | '''e) A GoF pattern that allows a single object to do the job of multiple similar objects by externalising their changeable properties.''' | ||
Line 132: | Line 136: | ||
* [[Singleton]]: The two subclasses of HoseType, RigidHoseType and FlexHoseType are both singletons. They have a static getType() / getHoseType() method which returns an instance of the class. They also each contain a single static instance, the singleton object. The constructors of HoseType, RigidHoseType and FlexHoseType are all protected to stop clients outside the class hierarchy creating new instances. This is a deviation from the normal singleton pattern, where the constructors are usually made private. | * [[Singleton]]: The two subclasses of HoseType, RigidHoseType and FlexHoseType are both singletons. They have a static getType() / getHoseType() method which returns an instance of the class. They also each contain a single static instance, the singleton object. The constructors of HoseType, RigidHoseType and FlexHoseType are all protected to stop clients outside the class hierarchy creating new instances. This is a deviation from the normal singleton pattern, where the constructors are usually made private. | ||
− | * [[Bridge]]: The Hose, HoseType and HoseType subclasses are an instance of the Bridge design pattern. The Hose represents the abstraction while the HoseType and its subclasses are the implementors of the abstraction. The Hose class implements its methods by mostly forwarding requests to its hose type. | + | * [[Bridge]]: The Hose, HoseType and HoseType subclasses are an instance of the Bridge design pattern. The Hose represents the abstraction while the HoseType and its subclasses are the implementors of the abstraction. The Hose class implements its methods by mostly forwarding requests to its hose type. This differs from the GoF pattern in that there is no abstract ''Abstraction'' class with a concrete ''RefinedAbstraction'' class; there is just a single abstraction class (''Hose''). |
* [[Adapter]]: The Outlet, OpenJoiner and Joiner form an adapter pattern. The OpenJoiner acts like an outlet and has the same interface of an outlet but it is really a joiner. As such, it is the adapter in the pattern. It forwards requests to the appropriate Joiner method, which is the adaptee, while extending Outlet to get the interface defined by outlet (the Target). This is an instance of an object based adapter pattern. | * [[Adapter]]: The Outlet, OpenJoiner and Joiner form an adapter pattern. The OpenJoiner acts like an outlet and has the same interface of an outlet but it is really a joiner. As such, it is the adapter in the pattern. It forwards requests to the appropriate Joiner method, which is the adaptee, while extending Outlet to get the interface defined by outlet (the Target). This is an instance of an object based adapter pattern. | ||
Line 142: | Line 146: | ||
* [[Facade]]: The IrrigationSystem class can be seen as an instance of the facade pattern in that it provides a simple interface for testing for a complex subsystem. Using IrrigationSystem, other clients can test the performance of an irrigation model. However, IrrigationSystem only provides an interface for simple operations while clients requiring more sophisticated operations instead use the subsystem directly. Instead, it is only a partial facade which does not entirely hide the subsystem from the rest of the system. This is different from the way the facade pattern is described in GoF, where the intend is to hide the entire subsystem rather than just a small part of it. | * [[Facade]]: The IrrigationSystem class can be seen as an instance of the facade pattern in that it provides a simple interface for testing for a complex subsystem. Using IrrigationSystem, other clients can test the performance of an irrigation model. However, IrrigationSystem only provides an interface for simple operations while clients requiring more sophisticated operations instead use the subsystem directly. Instead, it is only a partial facade which does not entirely hide the subsystem from the rest of the system. This is different from the way the facade pattern is described in GoF, where the intend is to hide the entire subsystem rather than just a small part of it. | ||
− | * [[Template Method]]: | + | |
+ | === Spurious Patterns === | ||
+ | These patterns do not actually exist in the design, but might be viewed as patterns by a casual observer. | ||
+ | * [[Template Method]]:: Fitting could be viewed as having a template method ''pressureChanged'', which uses the ''calculateInflow'' and ''calculateOutflow'' methods in subclasses. However, ''pressureChanged'' is an abstract method so this wouldn't be possible. Additionally, ''End'' has no implementation of ''calculateOutflow'', either in itself or its superclass. | ||
+ | * [[Strategy]]: ''Fitting'' and its subclasses could be viewed as implementing the Strategy design pattern for ''Coupling'', with the ''pressureChanged'' method. Although this fits the structure of the pattern, it does not fit the intent. | ||
+ | * [[Flyweight]]: The subclasses of ''HoseType'' are singletons and could be viewed as ''ConcreteFlyweights''. Additionally, ''Hose'' passes a hose length parameter to methods in ''HoseType'' when necessary, which resembles the ''extrinsicState'' parameter in the operations of a Flyweight pattern. Other than this, the implementations are quite different and the design is actually an implementation of the [[Bridge]] pattern (see above). |
Latest revision as of 03:17, 25 November 2010
Contents |
Question 1
Possible improvements to the design:
- Replace the Pair class with a Transaction class and two subclasses CreditTransaction and DebitTransaction. These classes will contain the information that was contained in Pair, and any other behavior related to transactions which is likely to have leaked out into the rest of the system.
- Currently, the Pair class is used to keep track of the Stock a Stockholder owns (price and date of purchase or sale) and to record transactions (the stock that was traded and the date of the trade).
- Violations:
- Behavioral completeness - The Pair class currently only acts as a data container but does not contain the behaviour related to that data.
- One key abstraction - Pair currently does not capture one particular domain concept but tries to do several things at once. This is also related to Single responsibility principle.
- Separation of concerns - Pair should only have one particular responsibility but at the moment fulfills several. These should be separated.
- Beware type switches - A Pair object will be used differently depending on the types of its objects, which will require a type switch.
- Data class smell - Pair is a data class
- Replace Stock class with CompanyStock and OwnedStock. These classes will not share a common super class as they differ greatly in what they represent.
- The Stock class currently tries to represent both a single share (OwnedStock) and the master share with information about the stock for a particular company (CompanyStock).
- Violations:
- Single responsibility principle - The Stock class tries to represent two very different concepts at the same time.
- One key abstraction
- A Stock object should not know about its owner - it should be sufficient for just the StockHolder to know about the Stocks it owns
- Broker and StockHolder should both hold a reference to an Account object. This would replace the brokersAccount property of Broker. The Account class would manage Transactions and include a reference to the account holder (Broker or StockHolder).
- Violations: Single responsibility principle
- Broker and StockHolder should both hold a reference to ContactDetails object. Alternatively they could both inherit from a Person class, but if we favor composition over inheritance this would seem the less preferred solution. This also wouldn't be a good solution if a Broker can be a company as well as a person. This would allow personal details of Brokers and StockHolders to be stored. In the current design the notion of a Person is mixed with that of an Account. The improved design separates these two and would allow someone to have multiple accounts.
- Violations: Single responsibility principle
- Once the notion of a person (Stockholder) and an account has been separated, all the accounts for one particular person can be stored as a Set by the object representing the person.
- At the moment, accounts are chained together through the next link in the StockHolder class. This further shows that two different concepts have been muddled up. This also means that redundant data will be stored since for each account the data for the person is stored separately, rather than that data being stored only once.
- Violations:
- The trades field should be moved out of the GUI class. The GUI should concentrate on the visual display of information rather than recording trades and transactions.
- The trades field could potentially be moved to the Broker class to record the trades made by one particular Broker.
- Violations:
- Separation of concerns
- Single responsibility principle
- The GUI is part of the presentation layer of the program while the trades field belongs in the Business layer. The two should not be mixed.
- The Price class should not be a subclass of Stock. This inheritance relationship makes no sense in the given domain as the two concepts are totally different. The Price class has very different behaviour and data and should therefore not inherit from Stock.
- We can easily see that the inheritance relationship makes no sense in this case because the behaviour of the Stock class (which is inherited by Price) makes little sense for a Price object.
- Violations:
- Liskov substitution principle - A Price object cannot be used where a Stock object is expected.
- Dependency inversion principle - The Stock class knows about it's subclass Price; although this is irrelevant because inheritance shouldn't even be used here.
- Favour composition over inheritance
- Decouple the Broker from the GUI. This could be done using an observer design pattern.
- At the moment, the Broker calls on the GUI to display itself. Broker contains a display method which knows about different ways of displaying a Broker. This behaviour does not belong in the Broker class but in the GUI. The GUI is the only part of the system which should be concerned with displaying information.
- Violations:
- Separation of concerns
- Interface should be dependent on model
- Beware value switches/Eliminate case analysis - the "what" parameter of the display method will require a value switch
- The getters in the Stock class should be separated into getters and setters. They currently fulfill the role of both accessors and mutators.
- Make the fields of Broker, GUI and StockerHolder private, rather than protected
- Violations
- Make the customers field of Broker depend on abstract class rather than implementation. E.g. change ArrayList to List.
Question 2
a) A maxim that contrasts with the culture of software reuse
- I think that You ain't gonna need it potentially conflicts with the culture of software reuse. YAGNI basically says not to worry about functionality or a fancy design that you don't really need yet. On the other hand, in order to achieve software reuse you need to develop modules in a very general way, keeping in mind possible future needs. -- Janina
b) A maxim that might be measured automatically by software tools
- There are a lot of maxims like this that can be measured automatically. I think essentially any maxim which can be evaluated by simply looking at the syntax of a program or the relationships between parts of a program can be easily measured. Examples include:
- Acyclic dependencies principle - This is easy to measure by looking at which parts of the program depend on each other. In some languages, cycles of include statements won't compile, so this maxim already can be measured in some cases.
- Avoid downcasting - This can be easily measured by looking for casts that go down the inheritance hierarchy.
- Law of Demeter - This can be measured by looking at method calls to check that they all comply with the law
- ...
c) A maxim that can never be measured automatically be software tools
- Again, there are a number of principles that we will probably never be able to measure properly. Especially maxims which require an understanding of the domain semantics will be very hard to measure directly, though it is possible that we may be able to measure them indirectly through some metric. Examples include:
- Model the real world - Evaluating whether this maxim has been followed requires domain knowledge.
- Single responsibility principle - Again, domain knowledge is required to measure this one though we may be able to indirectly measure the effects that not following this rule has on our program.
- Premature optimization - This would require knowledge about when a system is fully working, what constitutes optimization etc which is likely to be hard to automate.
- ...
d) A maxim that is violated by the GoF Strategy pattern
- I think that the Strategy pattern violates Riel's heuristic Keep related data and behavior in one place because it deliberately separates the behaviour to be performed from the data it operates on. -- Janina
- For a very similar reason it also violates Behavioral completeness
- The Visitor pattern violates the Acyclic dependencies principle
e) A GoF pattern that allows a single object to do the job of multiple similar objects by externalising their changeable properties.
- Flyweight: "A flyweight is a shared object that can be used in multiple contexts simultaneously. The flyweight acts as an independent object in each context - it's indistinguishable from an instance of the object that's not shared." (From GoF Design Patterns book) This decreases the overall number of object instances.
f) A pattern that encourages the use of abstract getters and setters instead of accessing attributes.
- Encapsulate concrete state pattern - This pattern tells developers to never access data directly but to instead go through getters and setters, both from within and from outside a class. This improves the maintainability of the system since the data can be changed without affecting the code accessing it.
g) A pattern likely to be most helpful when developing a system to record the contents of a warehouse and the associated income and expenditure.
- The Inventory and Accounting analysis pattern.
Question 3
A well known quote from Kent Beck says: “Make it work. Make it right. Make it fast.” What does he mean, and why?
- He means that before doing any optimisations, you should make sure that your system works as it should. Once it does, you can refactor, change the design and make the code clearer to make it "right". Only once you have the functionality and design in place should you try to optimise it to make it faster. This is in line with the Premature optimization maxim.
- It can be very tempting to write a piece of code and then spend lots of time making it "right" and optimising it. This is generally not a good idea because this code may be changed later. If instead we leave the optimisation to the end, it is less likely that the code will need to be changed later. In addition, optimisation and refactoring can often introduce bugs so it's best to make sure everything works and passes the tests before messing around with it.
- This quote probably refers to test-driven design. For test-driven design you first write tests. While the new tests don't pass, you then write the simplest possible code to make the test pass (Do the simplest thing that could possibly work). This can involve committing all sorts of serious "sins" but the aim while tests aren't passing is to "make it work". Then, when the tests pass, you go into refactoring mode and "repent from your sins" to improve the design and "make it right".
Question 4
What is the principal message of Foote and Yoder’s Big Ball of Mud paper? Write as concise a synopsis as possible.
In the paper, "Big Ball of Mud", Foote and Yoder state that the most commonly used software architecture is the "big ball of mud", a "casually, even haphazardly, structured system". They say that big balls of mud occur for example when throw-away code becomes permanent or when the design of a system and its requirements continually changed but they also concede that big balls of mud are created because they work. Foote and Yoder present a total of seven patterns (including "Big ball of mud") either to explain where big balls of mud originate from or to show how they can be improved.
Question 5
Cohesion and coupling are influential software design concepts. Identify maxims (as many as you can) that encourage high cohesion and low coupling. Explain your answers.
- Keep related data and behavior in one place - By following this principle, the behaviour is close to the data it needs rather than having to get data from other modules, which reduces coupling between modules. In addition, it obviously encourages us to keep together parts of the system that belong together, which leads to high cohesion of modules.
- Separation of concerns - This maxim encourages developers to separate parts of the system that don't belong together, thus avoiding coincidental cohesion, the lowest form of cohesion.
- Single responsibility principle / One key abstraction - These maxims encourage high cohesion because it tells us to group together behaviour that concerns one particular task.
- Behavioral completeness - This principle says that any behavior that is related to an object and that is needed by the system should be implemented as part of the object rather than elsewhere in the system. In this way, related data and behavior is kept in one place, increasing the cohesion of the object in question and reducing coupling with other parts of the system which may have otherwise implemented behavior associated with the object's data.
- Encapsulate that which varies - By encapsulating parts of the system that are likely to change, we reduce the coupling between those parts of the system and the rest of the system, meaning that the rest of the system will not need to change if the encapsulated part changes.
- Separate methods that do not communicate / Separate non-communicating behaviour - These principles encourage high cohesion because it separates parts of the system that have nothing to do with each other.
- Don't expose mutable attributes - If one object changes another by altering it's attributes directly then we cannot change how this attribute is represented without requiring changes to the object that accesses it. The coupling between these two objects is very high. By instead providing methods which alter an objects state we are free to change how this state is represented internally.
- Information hiding / Hide your decisions - By hiding the decisions we've made we are free to change them later. Similarly, by hiding information we are free to change how it is organised and represented and so on. This reduces coupling.
- Program to the interface not the implementation / Stable abstractions principle - If we only depend upon abstract interfaces then we are free to change the implementations. Obviously, this depends on these abstractions being very stable. This reduces coupling.
Question 6a
The following questions refer to the UML class diagram in Figure 3 and explanatory notes in Figure 4. Some getters and setters and other details are omitted from the diagram, but may be assumed where necessary. (Recall that a # in UML means protected, and an underline means static.) Document any non-trivial assumptions you need to make.
Find as many Gang of Four design patterns as you can in the Irrigation design. Name each pattern and describe where and how it is used in this design. Provide just enough information to make it clear how and why the pattern is applied here. Note any important variations from the standard pattern. There is no need to comment on the value of the pattern.
- Singleton: The two subclasses of HoseType, RigidHoseType and FlexHoseType are both singletons. They have a static getType() / getHoseType() method which returns an instance of the class. They also each contain a single static instance, the singleton object. The constructors of HoseType, RigidHoseType and FlexHoseType are all protected to stop clients outside the class hierarchy creating new instances. This is a deviation from the normal singleton pattern, where the constructors are usually made private.
- Bridge: The Hose, HoseType and HoseType subclasses are an instance of the Bridge design pattern. The Hose represents the abstraction while the HoseType and its subclasses are the implementors of the abstraction. The Hose class implements its methods by mostly forwarding requests to its hose type. This differs from the GoF pattern in that there is no abstract Abstraction class with a concrete RefinedAbstraction class; there is just a single abstraction class (Hose).
- Adapter: The Outlet, OpenJoiner and Joiner form an adapter pattern. The OpenJoiner acts like an outlet and has the same interface of an outlet but it is really a joiner. As such, it is the adapter in the pattern. It forwards requests to the appropriate Joiner method, which is the adaptee, while extending Outlet to get the interface defined by outlet (the Target). This is an instance of an object based adapter pattern.
- State: The IrrigationComponent, ComponentCondition, Blockage and Puncture classes form an instance of the State pattern. The calculations performed by the IrrigationComponent depend on the state of the component which is represented by the separate state class hierarchy containing the ComponentCondition, Blockage and Puncture classes. IrrigationComponent is the context and forwards any calculations to its current state. ComponentCondition is the usual state of the IrrigationComponent while Blockage and Puncture represent other states. This is slightly different from the normal State pattern where usually only the ConcreteStates (Blockage and Puncture) are used to represent actual states while their superclass (ComponentCondition) is abstract. However, in this design, the superclass ComponentCondition represents the default state and is not abstract.
- Builder: The Plumber class represents an instance of the Builder design pattern. It provides an easy interface to creating a complex irrigation system by building the system up part by part. In this instance of the Builder pattern, there is no concrete builder, only a single builder class. Usually, there would be an abstract builder class and concrete builder subclasses. The getSystem() method returns the resulting irrigation system and corresponds to the getResult() method in the GoF pattern description.
- Facade: The IrrigationSystem class can be seen as an instance of the facade pattern in that it provides a simple interface for testing for a complex subsystem. Using IrrigationSystem, other clients can test the performance of an irrigation model. However, IrrigationSystem only provides an interface for simple operations while clients requiring more sophisticated operations instead use the subsystem directly. Instead, it is only a partial facade which does not entirely hide the subsystem from the rest of the system. This is different from the way the facade pattern is described in GoF, where the intend is to hide the entire subsystem rather than just a small part of it.
Spurious Patterns
These patterns do not actually exist in the design, but might be viewed as patterns by a casual observer.
- Template Method:: Fitting could be viewed as having a template method pressureChanged, which uses the calculateInflow and calculateOutflow methods in subclasses. However, pressureChanged is an abstract method so this wouldn't be possible. Additionally, End has no implementation of calculateOutflow, either in itself or its superclass.
- Strategy: Fitting and its subclasses could be viewed as implementing the Strategy design pattern for Coupling, with the pressureChanged method. Although this fits the structure of the pattern, it does not fit the intent.
- Flyweight: The subclasses of HoseType are singletons and could be viewed as ConcreteFlyweights. Additionally, Hose passes a hose length parameter to methods in HoseType when necessary, which resembles the extrinsicState parameter in the operations of a Flyweight pattern. Other than this, the implementations are quite different and the design is actually an implementation of the Bridge pattern (see above).