We know that tightly coupled code is not a good thing, so try to avoid it in the design--but the question is how to avoid tight coupling. This month, we'll learn how to identify whether a system has a tightly coupled problem, and then use the dependency inversion principle to solve this tight coupling.
While code metrics and developer testing are important to ensure code quality throughout the development process (as I have often said, testing is done in a timely and regular manner), they are basically only responsive to code quality. You determine and quantify the quality of your code by testing and measuring the code, but the code itself is already written. No matter how hard you try, you will be stuck with the original design.
|
Improve code Quality Don't miss Andrew's code quality discussion forum, which can help you troubleshoot code metrics and test framework issues, and how to write quality-centric code. |
|
Of course, different methods of the design of the software system will have good and bad, mixed. One of the key factors of excellent design is to maintain the maintainability of the system. Poorly designed and executable systems may be easy to write, but supporting them is a real challenge. These systems are often fragile, meaning that modifications to one area of the system will affect other seemingly unrelated areas, so it is difficult and time-consuming to refactor them. Adding a developer test to a code library can provide us with a plan for our work, but its progress is still a tough and slow process.
We can refactor to improve the code that has been written, but it's often expensive to make changes after the code is finished. And if the code in the beginning to write the perfect will be more convenient and easy. This month, I'll introduce a very proactive technique to ensure the quality and maintainability of your software system. The dependency inversion principle is proven to be necessary to write high quality code that can be maintained and testable. The basic idea of the dependency inversion principle is that the object should depend on abstraction rather than implementation.
|
is dependency inversion rather than dependency injection The dependency inversion principle has no direct relationship with dependency injection. Dependency injection, also known as control reversal (inversion of CONTROL,IOC), links object dependencies at run time, rather than at compile time, using frameworks such as Spring. Although dependency inversion and dependency injection do not need to be used at the same time, they are complementary: two techniques strive to use abstraction rather than implementation. See Resources for more information on the dependency inversion principle. |
|
too tightly coupled.
You may have heard at least the term coupling (coupling) used in object-oriented programming. Coupling is the interrelationship between components (or objects) in an application. Loosely coupled applications are more modular than tightly coupled applications. Components in loosely coupled applications rely on a variety of interfaces and abstract classes, while tightly coupled systems rely on a variety of specific classes for their components. In loosely coupled systems, the components are interconnected by using abstractions rather than implementations.
The tightly coupled problem can be easily understood if there are illustrations. For example, the GUI of the software system in Figure 1 is coupled to its database:
Figure 1. A tightly coupled system
A GUI's reliance on an implementation, rather than an abstraction, can impose constraints on the system. The GUI cannot be executed without the database being started and running. The design does not seem to be very bad from a functional point of view-after all, we've been writing apps and there's nothing wrong with it-but the tests are different.
the ' fragile ' system
The system in Figure 1 makes isolation programming particularly difficult, which is necessary for testing and maintaining the system in all its aspects. You will need an active database with the correct lookup data to test the GUI, and a functioning GUI to test the data access logic. You can use Testng-abbot (now named FEST) to test the front end, but this still doesn't tell you anything about database functionality.
Listing 1 shows this bad coupling. A specific button on the GUI defines a actionlistener that communicates directly with the underlying database through a getorderstatus call.
Listing 1. Define the ActionListener as a button in the GUI
Findwidgetbutton.addactionlistener (new ActionListener () {
public void actionperformed (ActionEvent e) {
try {
String value = Widgetvalue.gettext ();
if (value = null | | value.equals ("")) {
Dlabel.settext ("Please enter a valid Widgetid");
} else {
Dlabel.settext (Getorderstatus (value));
}
catch (Exception ex) {
Dlabel.settext ("Widget doesn ' t exist in System");
}
}
More code
});
After you click the GUI's button component, retrieve the state of a particular command directly from the database, as shown in Listing 2:
Listing 2. GUI communicates directly with database through Getorderstatus method
private string Getorderstatus (string value) {
String RetValue = "Widget doesn ' t exist in System";
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try {
con = drivermanager.getconnection ("jdbc:hsqldb:hsql://127.0.0.1", "sa", "");
stmt = Con.createstatement ();
rs = Stmt.executequery ("Select Order.status"
+ "from order, widget where widget.name ="
+ "'" + Value + "'"
+ "and widget.id = order.widget_id;");
StringBuffer buff = new StringBuffer ();
int x = 0;
while (Rs.next ()) {
Buff.append (++x + ":");
Buff.append (rs.getstring (1));
Buff.append ("n");
}
if (buff.length () > 0) {
RetValue = Buff.tostring ();
}else{
RetValue = "Widget doesn ' t exist in System";
}
catch (SQLException E1) {
E1.printstacktrace ();
finally {
try {rs.close ();} catch (Exception E3) {}
try {stmt.close ();} catch (Exception e4) {}
try {con.close ();} catch (Exception e5) {}
}
return retvalue;
}
The code in Listing 2 has a problem, especially when it communicates directly with a hard-coded database through a hard-coded SQL statement. yeeesh! Can you imagine the challenge of developers testing this GUI and related databases (by the way, the test should be as simple as testing a Web page). If any changes to the database would affect the GUI, it would make the situation worse by considering modifying the system.
|
DAO mode The data Access Object (DAO) is a design pattern designed to separate low-level data access operations from high-level transaction logic using interfaces and related implementations. In essence, a specific DAO class implements the logic of accessing data through a particular data source. The DAO pattern makes it possible to define multiple implementations using only one interface for multiple databases, or even a variety of different data sources, such as file systems. |
|
transition to loose coupling!
Now consider the same system that was designed with the dependency inversion principle in mind. As shown in Figure 2, it is possible to disassociate the coupling in the application by adding two components to the application: The two components are an interface and an implementation, respectively:
Figure 2. A loosely coupled system
In the application shown in Figure 2, the GUI relies on an abstraction-a data access object or DAO. The execution of DAO relies directly on the database, but the GUI itself is not caught in it. Adding an abstraction as a DAO can decouple the database from the GUI implementation. An interface replaces the database with GUI code. Listing 3 shows the interface.
Listing 3. Widgetdao is an abstraction that can help decouple the architecture
Public interface Widgetdao {
public string Getorderstatus (String widget);
//....
}
The GUI's ActionListener code refers to the interface type Widgetdao (defined in Listing 3) rather than the actual implementation of the interface. In Listing 4, the GUI's Getorderstatus () method essentially specifies the Widgetdao interface:
Listing 4. The GUI relies on abstractions, not databases
private string Getorderstatus (string value) {
return Dao.getorderstatus (value);
}
The GUI completely hides the actual implementation of this interface because it requests the implementation type through a factory, as shown in Listing 5:
Listing 5. Hides the Widgetdao implementation of the GUI
Private Widgetdao DAO;
//...
private void Initializedao () {
This.dao = Widgetdaofactory.manufacture ();
}
It's going well .
Note that the code in the GUI in Listing 5 refers to the implementation of an interface that is not used (or imported) anywhere in the interface type--gui. This abstraction of implementation detail is the key to flexibility: it allows you to replace the implementation type without affecting the GUI at all.
Also note that the Widgetdaofactory in Listing 5 is how the GUI avoids the creation details of the Widgetdao type. These are the tasks of the factory, as shown in Listing 6:
Listing 6. The factory hides the implementation details from the GUI
public class Widgetdaofactory {
public static Widgetdao manufacture () {
//..
}
}
Making a GUI reference to data retrieval of an interface type can provide flexibility to create different implementations. In this case, the part information is saved in the database, so you can create a Widgetdaoimpl class to communicate directly with the database, as shown in Listing 7:
Listing 7. Widgetdao Type of Task
public class Widgetdaoimpl implements Widgetdao {
public string Getorderstatus (string value) {
//...
}
}
Note that the implementation code is not included in these examples. The code is not important, the real value is the principle. You should not be concerned about how the Widgetdaoimpl getorderstatus () method works. It can get state information from a database or from a file system, but the point is that it doesn't affect you.
now, detach the GUI
Because the GUI now relies on an abstraction and obtains the abstract implementation through a factory, we can easily create a mock class that is not coupled to a database or file system. The mock class is used to detach the GUI, as shown in Listing 8:
Listing 8. Separation becomes simple
public class Mockwidgetdaoimpl implements Widgetdao {
public string Getorderstatus (string value) {
//..
}
}
Adding a mock class is the last step in designing a maintainable system. Separating the GUI from the database means that we can test the GUI without caring for specific data. We can also test the data access logic without caring for the GUI.
Concluding remarks
You may not think about it too much, but the application you are designing and building today can be a long life. You will continue to develop other projects, or work in other companies, but your code (such as COBOL) will stay and may even be used for decades.
What developers agree is that writing good code is easy to maintain, and the dependency inversion principle is a reliable way to design maintainability. Dependency inversion focuses on abstraction rather than implementation, so you can create a lot of flexibility in the same code base. Using a DAO to apply this technique, as you can see this month, not only ensures that you can modify the code base when you need it, but also that other developers can modify the code base.