After studying this code and completing the corresponding exercises, you should be able to,
- Utilize User Stories
[LO-UserStories]
- Utilize use cases
[LO-UseCases]
- Use Non Functional Requirements
[LO-NFR]
- Use Polymorphism
[LO-Polymorphism]
- Use abstract classes/methods
[LO-Abstract]
- Follow Liskov Substitution Principle
[LO-LSP]
- Use Java-FX for GUI programming
[LO-JavaFx]
- Analyze Coupling and Cohesion of designs
[LO-CouplingCohesion]
- Apply Dependency Inversion Principle
[LO-DIP]
- Use Dependency Injection
[LO-DI]
- Apply Open-Closed Principle
[LO-OCP]
- Work in a 2KLoC code base
[LO-2KLoC]
- Assume you are planing to expand the functionality of the AddressBook (but keep it as a CLI application).
What other user stories do you think AddressBook should support? Add those user stories to the
DeveloperGuide.md
.
- Add a use case to the
DeveloperGuide.md
to cover the case of renaming of an existing tag.
e.g. rename the tagfriends
tobuddies
(i.e. all persons who had thefriends
tag will now have abuddies
tag instead)
Assume that AddressBook confirms the change with the user before carrying out the operation.
- Add some more NFRs to the
DeveloperGuide.md
Note how the Command::execute()
method shows polymorphic behavior.
- Add an
abstract
methodboolean isMutating()
to theCommand
class. This method will returntrue
for command types that mutate the data. e.g.AddCommand
- Currently, AddressBook data are saved to the file after every command.
Take advantage of the the new method you added to limit file saving to only for command types that mutate data.
i.e.add
command should always save the data whilelist
command should never save data to the file.
Note: There may be better ways to limit file saving to commands that mutate data. The above approach, while not optimal, will give you chance to implement a polymorphic behavior.
Covered by [LO-Polymorphism]
- Add a
throws Exception
clause to theAddCommand::execute
method. Notice how Java compiler will not allow it, unless you add the samethrows
clause to the parent class method. This is because if a child class throws an exception that is not specified by the Parent's contract, the child class is no longer substitutable in place of the parent class. - Also note that while in the above example the compiler enforces LSP, there are other situations where it is up to
the programmer to enforce it. For example, if the method in the parent class works for
null
input, the overridden method in the child class should not rejectnull
inputs. This will not be enforced by the compiler.
Resources
- If you are new to JavaFX, follow our tutorial given above.
- Do some enhancements to the AddressBook GUI. e.g. add an application icon, change size/style
- Ensure you know the answers to these questions:
- What is the Coupling?
- What is Cohesion?
- As you saw above, DIP helps us to avoid coupling from higher-level classes to lower-level classes.
- Notice how having a separate
Formattter
class (an application of SIP) improves the cohesion of theMainWindow
class as well as theFormatter
class.
- Where else in the design coupling can be reduced further, or cohesion can be increased further?
- Ensure you know the answers to these questions:
- What is the Dependency Inversion Principle?
- How does DIP help?
- Note how
Logic
class depends on theStorageFile
class. This is a violation of DIP. - Modify the implementation as follows so that both
Logic
andStorageFile
now depend on theabstract
classStorage
.
- Where else in the code do you notice the application of DIP?
Note how Logic
class depends on the StorageFile
class. This means when testing the Logic
class,
our test cases execute the StorageFile
class as well. What if we want to test the Logic
class without
getting the StorageFile
class involved? That is a situation where we can use Dependency Injection.
-
Ensure you know the answers to these questions:
- What is the Dependency Injection?
- How does DI help?
-
Change the implementation as follows so that we can inject a
StorageStub
when testing theLogic
class.
If you did the exercise in
LO-DIP
already but those changes are in a different branch, you may be able to reuse some of those commits by cherry picking them from that branch to the branch you created for this exercise.
Note: cherry picking is simply copy-pasting a commit from one branch to another. In SourceTree, you can right-click on the commit your want to copy to the current branch, and choose 'Cherry pick' -
Implement the
StorageStub
such that calls to thesave
method do nothing (i.e. empty method body). -
Update the
LogicTest
to work with theStorageStub
instead of the actualStorageFile
object.
i.e.Logic
injects aStorageStub
object to replace the dependency ofLogic
onStorageFile
before testingLogic
. -
The example above uses DIP as a means to achieve DI. Note that there is another way to inject a
StorageStub
object, as shown below. In this case we do not apply the DIP but we still achieve DI.
- Ensure you know the answers to these questions:
- What is the Open Closed Principle?
- How does OCP help?
- Consider adding a new command to the Address Book. e.g. an
edit
command. - Notice how little you need to change in the
Logic
class to extend its behavior so that it can execute the new command. That is becauseLogic
follows the OCP i.e.Logic
is open to be extended with more commands but closed for modifications. - Is it possible to make the
Parser
class more OCP-compliant in terms of extending it to handle more command types? - In terms of how it saves data, does
Logic
become more OCP-compliant after applying DIP as given inLO-DIP
? How can you improveLogic
's OCP-compliance further so that it can not only work with different types of storages, but different number of storages (e.g. save to both a text file and a database).
- Enhance AddressBook in some way. e.g. add a new command