Following Toby's Spring 3.1 in Spring Boot : Chapter 1 Objects and Dependencies - 1.3 Extending the DAO
Dev environment - OS : mac - STS : 4.0.1 - MySQL : Server version: 8.0.13 Homebrew - Framework : proceeding with a jar only, keeping dependencies as minimal as possible Related links - Korean Spring User Group homepage http://www.ksug.org/ - Korean Spring User Group Q&A https://groups.google.com/forum/#!topic/ksug/13vB4tCFqrI - 2017 Spring Camp https://www.youtube.com/playlist?list=PLdHtZnJh1KdZ6NDO9zc9hF4tONDLTSEUV |
Chapter 1 Objects and Dependencies
1.3 Extending the DAO
Separation of concerns. You can think of it as gathering things with the same concern into a single object or closely related objects, and keeping things with different concerns as far apart as possible so they don't influence each other.
1.3.0 Review so far
1. We took the awful DAO and pulled its logic into independent methods to separate them.
2. We split it into a parent/child class pair.
1) Parent class — abstract class
2) Child class — a subclass that inherits from the abstract class
3. The development methodologies that embody this kind of process
1) Design pattern
2) Template Method pattern
3) Factory Method pattern
1.3.1 Separating the class — making SimpleConnectionMaker a standalone class
1. Instead of a subclass, create a new separate class to hold the DB connection info.
2. Use this class inside UserDao's add() and get() methods by creating instances of it with the new keyword.
To avoid creating the object every single time
-> create it with 'new' in the constructor and declare it as a private field (instance variable).
3. Result when run: success
export_1.3.1 Separating the class:
study_spring190220_1.zip
Download
1) In the project list panel on the left -> right-click in empty space -> import -> Existing Projects into Workspace -> select the downloaded file 2) If you already imported files from a previous post, please delete or rename the previous project before importing again. |
+ But (new issue!)
The picture I wanted at first was
but what actually came out is more like the highlighted block in the screenshot below.
Even when we split the class this way, two problems still need to be solved to enable free extension, just as we saw with inheritance.
First, the makeNewConnection() method is a problem.
If company N or company D uses a different method name, the code inside UserDao's constructor — and inside add() and get() — has to be changed every single time.
Second, UserDao has to know concretely which class provides the DB connection.
UserDao even defines an instance variable of the SimpleConnectionMaker type, so if company N or company D implements a class with a different name, UserDao itself has to be modified.
The root cause is that UserDao knows too much about the volatile piece — the class that pulls in the DB connection.
It has to know which class will be used (what its name is) and what the name of the method that fetches the connection info is. In other words, UserDao ends up tied to a specific way of getting the DB connection.
1.3.2 Introducing an interface
1. Structure <figure 1-4> p.75 The real structure is actually <figure 1-5>.
2. To keep the two classes from being tightly coupled, we create an abstract, loose link in between.
3. Abstraction is the act of pulling out the common nature of things and separating it off on its own.
Java gives us the 'interface' for exactly this. An interface hides all the concrete information about the class that implements it.
-> So that's why we've been connecting classes through an interface in between instead of directly!
In the end, where you used to have to pick a specific concrete class to create an object, now when you approach through the minimal passage abstracted by the interface, you don't need to know which class is being used to create the object. Once you go through an interface, it doesn't matter if the concrete implementation class changes.
4. Process
1) Create the interface
2) Create a dedicated class to hold the client's DB connection
3) Create the client's DB-connection class that implements the interface and write the DB connection code
4) Declare an interface-typed object in UserDao and connect via the client's DB-connection class method
5) Define the UserDao object in User.class's main() and adjust the PK value of the inserted data so the project can run
export_1.3.2 Introducing an interface :
study_spring190220_2.zip
Download
2) If you already imported files from a previous post, please delete or rename the previous project before importing again. |
+ But (new issue!)
If you look at the current project structure as shown below
Even after applying the interface, UserDao's constructor still ends up with the concrete class name declared by company N or D.
connectionMaker = new DConnectionMaker();
The code is short, but it means deciding which ConnectionMaker implementation class's object UserDao will use. In other words, it's a concern about the relationship between UserDao and the particular ConnectionMaker implementation it's going to use.
1.3.3 Separating the relationship-setup responsibility — splitting client and service
1. A relationship existing between classes means one class uses another directly, without any interface in between. So instead of a class-to-class relationship, we need to set up an object-to-object relationship.
2. A relationship between objects is formed at runtime, with one side holding a reference to another object.
connectionMaker = new DConnectionMaker();
= By putting the reference of the DConnectionMaker object into UserDao's connectionMaker field and using it, the two objects form a 'uses' relationship.
For a relationship to form between objects, the objects first have to exist — you can directly call a constructor, or use an object created externally.
Since objects can be passed around via method parameters and such, you can bring in an externally created one. To receive an externally created object, use a method parameter or a constructor parameter.
3. You must be able to clearly distinguish between a class-to-class relationship and a dynamic object-to-object relationship.
A class-to-class relationship is formed because another class's name appears in the code.
But an object-to-object relationship is different. Even if the code knows nothing at all about a specific class, as long as it uses the interface that class implements it can take the object of that class via the interface type and use it. This is precisely because object-oriented programs have the trait called polymorphism.
So, to make a UserDao object use that other object, all you need is to set up a runtime use-relationship — also called a link or dependency — between the two class objects.
4. Conclusion — define the client role
1) Structural change
Actually, it was the client's responsibility that took the class structure in the earlier <figure 1-4> and turned it into the runtime object-relationship structure of <figure 1-6>.
2) Reflecting this back into the composition
Since the client is the one that needs to use UserDao, let's offload the concern UserDao's constructor has been carrying — 'creating the object I'm going to use and setting up my own relationship with it' — onto the client. To do that, move the main() that was in UserDao into a new UserDaoTest.class.
And modify the constructor in UserDao so the client can pass in a pre-made ConnectionMaker object — add a single parameter. Starting from a structure like <figure 1-4>, we've finally arrived at a structure like <figure 1-7>.
-> The upshot: we've created a client that handles the relationship setup.
3) Final code
export_1.3.3 Separating the relationship-setup responsibility:
study_spring190220_3.zip
Download
1) In the project list panel on the left -> right-click in empty space -> import -> Existing Projects into Workspace -> select the downloaded file 2) If you already imported files from a previous post, please delete or rename the previous project before importing again. |
1.3.4 Principles and patterns
1. SOLID — object-oriented design principles
Single Responsibility SRP The Single Responsibility Principle Open/Closed OCP Open Close Principle Liskov Substitution LSP The Liskov Substitution Principle Interface Segregation ISP The Interface Segregation Principle Dependency Inversion DIP The Dependency Inversion Principle |
2. Here the author goes into detail about the Open/Closed Principle, which we've been applying unknowingly through the process of improving the awful DAO.
1) A class or module should be open for extension and closed for modification.
2) Most APIs that define extensions via interfaces can be seen as following this principle.
3) It should have high cohesion and low coupling.
- Cohesion: a single module or class is focused on a single responsibility or concern.
- Coupling: the same principle can be applied at different scales — class, package, component, module.
