Open wxxedu opened 1 year ago
Thanks for sharing this @wxxedu Probably a bit too much for most classmates at this time, but as the codebase gets bigger over the next few weeks, they will be in a better position to reflect on these aspects.
On a side note, overengineering is not a good thing in real projects but is useful in learning. For example, by pushing into the realm of overengineering one can learn where the boundary of overengineering is.
This is really insightful and interesting. Thanks for sharing! @wxxedu
Hey guys I am back, keep over-engineering things.
Building on top of my response to issue #23, there are a few things that I wish could be better, one of them is that currently for my output, I used
System.out.println
. While there is no real issue with such an approach, it would not be as flexible as I would like it to be, in that:What if I were to change the output? Say, if I were to change the output to writing to a file? Or how about a gui?
With
System.out.println
, it is quite hard to conduct unit tests, at least I don’t know how. Maybe I will have to mess with the output streams? (I am not a Java expert, and my past experiences have mostly been working with Dart / Flutter) Also, I hate bash scripts. You cannot have empty spaces around the assignment operator, what?Writable
Clearly, the way to solve this problem is through abstraction. Hence, I have created a new Interface called
Writable
.Now, with this, I can put a field called Writable in the classes that I wish to write. For example, the
Echo
class from [here](https://www.notion.so/On-my-design-of-Project-Duke-9860c58274714de3a50a076250a21e59).Now, I can replace all occurrences of
System.out.println
withwritable.writeln
.SystemOut
Since
Writable
is only an interface, I do still need concrete implementations. I would want to create an implementation that wraps around theSystem.out.println
. Hence I just call itSystemOut
:This way, in the instantiation of my
Echo
class, I could just pass in a newSystemOut
instance.Clean Architecture
Now, with the
System.out.println
removed from our code, I have successful abstracted away a very “specific” feature from the core of my application logic. With that, I can do some reorganization of the files according to the clean architecture proposed by uncle bob:Clean Coder Blog
Essentially, I want to put the application logic at the core of our app, and make sure that no dependencies of the outer rings can be found inside the app. More specifically, I created a folder called presentation, which shall contain the class
SystemOut
and theDukeEventLoop
. The other classes, such asWritable
,Executable
,EventLoop
(super class ofDukeEventLoop
), shall be placed inside the entities folder, and the implementations ofExecutable
's, i.e.Echo
,Bye
,TaskManager
, got renamed toEchoUsecase
,ByeUsecase
, andTaskManagerUsecase
, and shall be put inside the usecases folder. I grouped the usecases and the entites folder into a larger folder called domain. Ideally, regardless of how you changed the presentation or the database, you should not change the domain folder.Factories
Using the Writable interface has mostly solved the second problem,
because now I can write a mock writable class that verifies the behaviors of my code.
Factory Methods for Instantiation
However, it still failed to fully addresss the first problem. The matter is that if I pass in a new
SystemOut
instance every time I instantiate a new class, if I were to switch to a different implementation of the Writable interface, it would still require me to go out and search for all theSystemOut
occurences, and swap them by hand. The most ideal way, however, is to use a factory to generate newSystemOut
instances. This way, if I were to change the SystemOut to a new implementation, sayWriteToFile
, then I would only need to change the instantiation in the factory. All the other code gets kept!However, there are two issues with this approach:
getWritable
method. To me, theWritable
class seems to be a place where this makes the most logical sense. However, sinceWritable
is considered an entity, as described in the clean architecture, we cannot have dependencies outside of it. (For the Writable class to create a newSystemOut
instance, we will have to import the file forSystemOut
, which violates the dependency rule of clean architecture).Singletons
Also, observing from the characteristics of the
Writable
, we can also find one thing: we do not need many instantiations of theWritable
object: having only one object would be good enough for us, and having many could potentially waste some resources. Therefore, in the end, we would want:There are a few notes here:
RuntimeException
instead of a checked exception in the last method is that this could only be wrong if the developer forgot to register the corresponding dependency, and it can be fixed programmatically. Hence, it should not be checked.This way, in the Duke class, I could write a static method called
configureDependencies()
:and call this line before executing the rest of the code.
Therefore, I could replace all the occurrences of instantiating a new
Writable
with this: