Reputation: 38899
Say I have these 3 layers of my code:
1. Database layer (ORM)
2. BusinessLogic
3. Application
Now, I write my code as follows:
Database layer:
This mainly has CURD operations over database.
class MyDatabaseLayer {
public int findValue(int k) {
// find v
}
public void insertValue(int k, int v) {
// Insert v
}
}
BusinessLogic:
This holds the actual logic to call database layer and do stuff.
class MyBusinessLogic {
private MyDatabaseLayer dbLayer;
public MyBusinessLogic(MyDatabaseLayer dbLayer) {
this.dbLayer = dbLayer;
}
public int manipulateValue(int k) {
dbLayer.findValue(k);
//do stuff with value
}
}
Application layer:
This calls the business logic and displays the data
MyBusinessLogic logic = new MyBusinessLogic(new MyDatabaseLayer ()); //The problem
logic.manipulateValue(5);
Now if you see in the application layer, it knows about the database layer, which is wrong. It knows too much.
Misko Hevery says: Constructor injection is good. But if I follow that, how will I achieve abstraction? And how can Google Guice help me here?
Upvotes: 0
Views: 230
Reputation: 110054
Note that for the testability Misko refers to, you ideally want to make interfaces for MyDatabaseLayer
, MyBusinessLogic
, etc. and have the constructors take those interfaces rather than concrete classes so that in testing you can easily pass in fake implementations that don't actually use the database, etc.
With Guice, you would bind the interfaces to concrete classes in a Module
or Module
s. You would then create an Injector
using those Module
s and get some root object (for example, your application object) from the Injector
.
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(MyDatabaseLayer.class).to(MyDatabaseLayerImplementation.class);
// etc.
});
MyApplicationLayer applicationLayer = injector.getInstance(MyApplicationLayer.class);
In MyApplicationLayer
, you'd inject the business logic:
@Inject
public MyApplicationLayer(MyBusinessLogic logic) {
this.logic = logic;
}
This is of course a very simple example and there are much more complex things you can do. In a web application, for example, you can use constructor injection on servlets using Guice Servlet rather than getting an object directly out of the Injector
after creating it.
Upvotes: 1
Reputation: 462
The part you are missing with inversion-of-control is that the application layer does not call the constructor directly.. It uses a factory (the IoC container) to populate the constructor parameter.
Whatever tool you use, guice / spring / picocontainer / singleton-factories, your application code should look something like:
@Controller
class MyController {
@Resource // Some container knows about this annotation and wires you in
MyBusinessLogic myBusinessLogic;
@RequestMethod("/foo/bar.*")
public MyWebResponse doService(Response resp, long id, String val) {
boolean worked = myBusinessLogic.manipulatevalue(id, val);
return new MyWebResponse(worked);
}
}
Note, the myBusinessLogic could be registered in several ways - java's @Resource, MyBusinessLogicFactory.getMyBusinessLogic(), guice.get(MyBusinessLogic.class), etc.
A poor-mans solution would be:
package foo;
class MyBusinessLogicFactory {
static volatile MyBusinessLogic instance; // package-scoped so unit tests can override
public static MyBusinessLogic getInstance() {
if (instance == null) {
synchronized(MyBusinessLogicFactory.class) {
instance = new MyBusinessLogic(MyDatabaseLayerFactory.getInstance());
}
}
return instance;
}
}
// repeat with MyDatabaseLayerFactory
Note the above singleton model is highly discouraged, since it has no scope. You COULD wrap the above inside a context - soething like
class Context {
Map<Class,Object> class2Instance = new ConcurrentHashMap<>();
public <T> T getInstance(Class<T> clazz) {
Object o = class2Instance.get(clazz);
if (o == null) {
synchronized(this) {
o = class2Instance.get(clazz);
if (o != null) return (T)o;
o = transitivelyLoadInstance(clazz); // details not shown
for (Class c : loadClassTree(clazz)) { // details not shown
class2Instance.put(c, o);
}
}
}
return (T)o;
}
...
}
But at that point, picocontainer, guice and spring can solve the complexities of the above SOOO much better.
Further, things like spring that honor the java 6 annotations mean you can do things other than constructor injection, which is VERY useful if you have multiple configuration items of the same base data-type (e.g. strings).
Upvotes: 1