I've a question about DIP Principle. One of the guidelines says that we should not hold references to a concrete class (if it changes then I'll have to modify all clients that use it). So, what can I follow this guideline when I use POJOs ? For Example:
I have a Bean 'Foo' with some attributes (it could represent a Domain object)
class Foo {
private String one;
private String two;
//getters and setters
}
Multiple clients instantiate this object, for example, to persist it in the Database
class Client1 {
private FooDao dao;
Client1(FooDao dao){
this.dao = dao;
}
public void persist() {
//hard coding
Foo foo = new Foo();
foo.setOne("something...");
dao.save(foo); }
}
class Client2 {
private FooDao dao;
Client2(FooDao dao){
this.dao = dao;
}
public void persist() {
Foo foo = new Foo();
foo.setOne("something...");
foo.setTwo("something...")
dao.save(foo);
}
}
If I add or change any attribute to 'Foo' class every client would have to change, so follow this guideline how can I avoid that?
Thanks!
The comment from @chrylis is spot on. Robert Martin covers this in chapter 6 of Clean Code: Objects and Data Structures.
Objects hide their data behind abstractions and expose functions that operate on that data. Data structures expose their data and have no meaningful functions. (page 95)
The definition of OOP where everything is an object, and there are no data structures, is naive.
Mature programmers know that the idea that everything is an object is a myth. Sometimes you really do want simple data structures with procedures operating on them. (page 97)
So what about classes that expose both data and behavior?
Confusion sometimes leads to unfortunate hybrid structures that are half object and half data structure. They have functions that do significant things, and they also have either public variables or public accessors and mutators that, for all intents and purposes, make the private variables public, tempting other external functions to use those variables the way a procedural program would use a data structure.
Such hybrids make it hard to add new functions but also make it hard to add new data structures. They are the worst of both worlds. Avoid creating them. (page 99)
To the original question: the Dependency Inversion Principle applies to objects, not to data structures like Java Beans.
I think you're taking this a little too literally.
I had the pleasure of attending a talk given by Venkat Subramaniam, which talked about DIP.
You're right when you say that you should be relying on abstractions, not concretions, but in my notes from that talk, I have the footnote, "take this with a grain of salt."
In your case, you're going to want to take this with a grain of salt, since there's a fairly strong code smell here - you're exposing the use of this bean to all consumers who need it, which implicitly create a dependency on it. This violates the Single Responsibility Principle since this bean is being used in more places than it probably should be.
Since it seems like you're talking about a database abstraction, perhaps you would want to look into a DTO which would be exposed between services to carry information between them, and let your bean handle the internals.
To your point...
if it change[s] then I'll have to modify all clients that use it
...this is true if you remove functionality. If you add new functionality, you can let your downstream clients just ignore that functionality. If you want to change existing functionality, you have to allow the clients a path to migration.
You need to define the functionality of the method you would like to add.
interface Functionality {
public void persist();
}
Each class except the manager need to implement the interface:
class Client1 implements Functionality{
//Your code..
}
Add a high level class high level classes that not working directly with low level classes:
Class ManageClients{
Functionality func;
public void setClient(Functionality f) {
func= f;
}
public void manage() {
func.persist();
}
};
ManageClients class doesn't require changes when adding Clients.
Minimized risk to affect old functionality present in ManageClients class since we don't change it.
No need to redo the unit testing for ManageClients class.