Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting rid of 'new' operators for subcomponents objects

I've been reading Misko Hevery's classic articles about Dependency injection, and basically 'separating the object graph creation code from the code logic'.

The main idea seems to be "get rid of the 'new' operators", put them in dedicated objects ('Factories') and inject everything you depend on."

Now, I can't seem to wrap my head about how to make this works with objects that are composed of several other components, and whose job is to isolate those components to the outerworld.

Lame example

A View class to represent a combination of a few fields, and a button. All the components depend on a graphical ui context, but you want to hide it behind the interfaces of each sub-component.

So something like (in pseudo-code, language does not really matter I guess):


class CustomView() {

   public CustomView(UIContext ui) {
        this.ui = ui
   }

   public void start() {

      this.field = new Field(this.ui);
      this.button = new Button(this.ui, "ClickMe");

      this.button.addEventListener(function () {
           if (field.getText().isEmtpy()) {
              alert("Field should not be empty");
           } else {
              this.fireValueEntered(this.field.getText());
           }
      });
   }

   // The interface of this component is that callers
   // subscribe to "addValueEnteredListener"..)
   public void addValueEnteredListener(Callback ...) {
   }

   public void fireValueEnteredListener(text) {
     // Would call each listeners in turn
   }
}

The callers would do something like :



// Assuming a UIContext comes from somewhere...
ui = // Wherever you get UI Context from ?
v = new CustomView(ui);
v.addValueEnteredListener(function (text) {
 // whatever...
});

Now, this code has three 'new' operators, and I'm not sure which one Misko (and other DI proponents) are advocating to rid off, or how.

Getting rid of new Field() and new Button()

Just Inject it

I don't think the idea here is to actually inject the instances of Field and Button , which could be done this way :


class CustomView() {

   public CustomView(Field field, Button button) {
        this.field = field;
        this.button = button;
   }

   public void start() {

      this.button.addEventListener(function () {
           if (field.getText().isEmtpy()) {
              alert("Field should not be empty");
           } else {
              this.fireValueEntered(this.field.getText());
           }
      });
   }

   // ... etc ... 

This makes the code of the component lighter, surely, and it actually hides the notion of UI, so the MetaForm component has clearly been improved in terms of readability and testability.

However, the burden is now on the client to create those things :



// Assuming a UIContext comes from somewhere...

ui = // wherever ui gets injected from 

form = new Form(ui);
button = new Button(ui);

v = new CustomView(form, button);
v.addValueEnteredListener(function (text) {
 // whatever...
});

That sounds really troubling to me, espacially since the client know has to all the inners of the class, which sounds silly.

Mama knows, inject her instead

What the articles seems to advocate is instead injecting a Factory to create the components elements.


class CustomView() {

   public CustomView(Factory factory) {
      this.factory = factory;
   }

   public void start() {

      this.field = factory.createField();
      this.button = factory.createButton();

      this.button.addEventListener(function () {
           if (field.getText().isEmtpy()) {
              alert("Field should not be empty");
           } else {
              this.fireValueEntered(this.field.getText());
           }
      });
   }

   // ... etc ... 

And then everything gets nice for the caller, because its just has to get the factory from somewhere (and this factory will be the only to know about the UI context, so hurray for decoupling.)



// Assuming a UIContext comes from somewhere...

factory = // wherever factory gets injected from 

v = new CustomView(factory);
v.addValueEnteredListener(function (text) {
 // whatever...
});

A possible drawback is that when testing the MetaForm, you will typically have to use a 'Mock' Factory that ... create Mocks version of the Field & Button classes. But obviously there is another drawback ...

Yo' Factory so fat!!

How big will the Factory get ? If you follow the pattern rigorously, then every single frigging component you ever want to create in you application at runtime (wich is typically the case for UI, right) will have to get its own createXXXXX methods in at least one factory.

Because now you need :

  • Factory.createField to create the field
  • Factory.createButton to create the button
  • Factory.createMetaForm to create the field, the button and the MetaForm when a client (say the MetaEditPage wants to use one)
  • And obviously a Factory.createMetaEditPage for the client..
  • ... and its turtles all the way.

I can see some strategies to simplify this :

  • As much as possible, separate the parts of the graph that are created at "startup" time from the parts that are created at runtime (using an DI framework like Spring for the former, and factories for the latter)
  • Layer the factories, or collocate related objects in the same factories (a UIWidgetFactory would make sense for Field and Button, but where would you put the other ones ? In a Factory linked to the Application ? To some other logical level ?)

I can almost hear all the jokes from C guys that no Java app can do anything without calling a ApplicationProcessFactoryCreator.createFactory().createProcess().startApplication() chain of nonsense...

So my questions are :

  • I am completely missing a point here ?
  • If not, which strategy would you suggest to make the things bearable ?

Addendum : why I'm not sure dependency injection would help

Assume I decide to use dependency injection, with a guice-like framework. I would end up writing code like this :


class CustomView

    @Inject
    private Field fiedl;

    @Inject
    private Button button;

    public void start() {
      this.button.addEventListener(....

// etc...

And then what ?

How would my "Composition Root" make use of that ? I can certainely not configure a "singleton" (with a lowercase 's', as in 'the single instance of a class) for the Field and the Button (since I want to create as many instances of them as instances of MetaForm ?

It would not make sense to use a Provider, since my problem is not which instance of buttons I want to create, but just that I want to create it lately, with some configuration (for example its text) that only makes sense for this form.

To me DI is not going to help because I am new-ing parts of my component rather than Dependencies. And I suppose I could turn any subcomponent into a dependency, and let a framework inject them. It's just that injecting subcomponents looks really artificial and couter-intuitive to me, in this case... so again, I must be missing something ;)

Edit

In particular, my issue is that I can't seem to understand how you would test the following scenario :

"when I click on the button, if the Field is empty, there should be an error".

This is doable if I inject the button, so that I can call its "fireClicked" event manually - but it feels a bit silly.

The alternative is to just do view.getButton().fireClicked() , but that looks a bit ugly...

like image 867
phtrivier Avatar asked Nov 13 '22 22:11

phtrivier


1 Answers

Well, you can use some DI Framework (Spring or Guice) and get rid of factory method completely. Just put some annotation on the field/constructor/set method and DI Framework will do the work. At unit-test use mock framework.

like image 90
alexsmail Avatar answered Dec 01 '22 10:12

alexsmail