Must Dependency Injection come at the expense of Encapsulation?

后端 未结 21 2402
被撕碎了的回忆
被撕碎了的回忆 2020-12-04 05:21

If I understand correctly, the typical mechanism for Dependency Injection is to inject either through a class\' constructor or through a public property (member) of the clas

相关标签:
21条回答
  • 2020-12-04 05:56

    I think it's a matter of scope. When you define encapsulation (not letting know how) you must define what is the encapsuled functionality.

    1. Class as is: what you are encapsulating is the only responsability of the class. What it knows how to do. By example, sorting. If you inject some comparator for ordering, let's say, clients, that's not part of the encapsuled thing: quicksort.

    2. Configured functionality: if you want to provide a ready-to-use functionality then you are not providing QuickSort class, but an instance of QuickSort class configured with a Comparator. In that case the code responsible for creating and configuring that must be hidden from the user code. And that's the encapsulation.

    When you are programming classes, it is, implementing single responsibilities into classes, you are using option 1.

    When you are programming applications, it is, making something that undertakes some useful concrete work then you are repeteadily using option 2.

    This is the implementation of the configured instance:

    <bean id="clientSorter" class="QuickSort">
       <property name="comparator">
          <bean class="ClientComparator"/>
       </property>
    </bean>
    

    This is how some other client code use it:

    <bean id="clientService" class"...">
       <property name="sorter" ref="clientSorter"/>
    </bean>
    

    It is encapsulated because if you change implementation (you change clientSorter bean definition) it doesn't break client use. Maybe, as you use xml files with all written together you are seeing all the details. But believe me, the client code (ClientService) don't know nothing about its sorter.

    0 讨论(0)
  • 2020-12-04 05:56

    It's probably worth mentioning that Encapsulation is somewhat perspective dependent.

    public class A { 
        private B b;
    
        public A() {
            this.b = new B();
        }
    }
    
    
    public class A { 
        private B b;
    
        public A(B b) {
            this.b = b;
        }
    }
    

    From the perspective of someone working on the A class, in the second example A knows a lot less about the nature of this.b

    Whereas without DI

    new A()
    

    vs

    new A(new B())
    

    The person looking at this code knows more about the nature of A in the second example.

    With DI, at least all that leaked knowledge is in one place.

    0 讨论(0)
  • 2020-12-04 05:57

    There is another way of looking at this issue that you might find interesting.

    When we use IoC/dependency injection, we're not using OOP concepts. Admittedly we're using an OO language as the 'host', but the ideas behind IoC come from component-oriented software engineering, not OO.

    Component software is all about managing dependencies - an example in common use is .NET's Assembly mechanism. Each assembly publishes the list of assemblies that it references, and this makes it much easier to pull together (and validate) the pieces needed for a running application.

    By applying similar techniques in our OO programs via IoC, we aim to make programs easier to configure and maintain. Publishing dependencies (as constructor parameters or whatever) is a key part of this. Encapsulation doesn't really apply, as in the component/service oriented world, there is no 'implementation type' for details to leak from.

    Unfortunately our languages don't currently segregate the fine-grained, object-oriented concepts from the coarser-grained component-oriented ones, so this is a distinction that you have to hold in your mind only :)

    0 讨论(0)
  • 2020-12-04 05:59

    This exposes the dependency being injected and violates the OOP principle of encapsulation.

    Well, frankly speaking, everything violates encapsulation. :) It's a kind of a tender principle that must be treated well.

    So, what violates encapsulation?

    Inheritance does.

    "Because inheritance exposes a subclass to details of its parent's implementation, it's often said that 'inheritance breaks encapsulation'". (Gang of Four 1995:19)

    Aspect-oriented programming does. For example, you register onMethodCall() callback and that gives you a great opportunity to inject code to the normal method evaluation, adding strange side-effects etc.

    Friend declaration in C++ does.

    Class extention in Ruby does. Just redefine a string method somewhere after a string class was fully defined.

    Well, a lot of stuff does.

    Encapsulation is a good and important principle. But not the only one.

    switch (principle)
    {
          case encapsulation:
               if (there_is_a_reason)
          break!
    }
    
    0 讨论(0)
  • 2020-12-04 06:02

    Pure encapsulation is an ideal that can never be achieved. If all dependencies were hidden then you wouldn't have the need for DI at all. Think about it this way, if you truly have private values that can be internalized within the object, say for instance the integer value of the speed of a car object, then you have no external dependency and no need to invert or inject that dependency. These sorts of internal state values that are operated on purely by private functions are what you want to encapsulate always.

    But if you're building a car that wants a certain kind of engine object then you have an external dependency. You can either instantiate that engine -- for instance new GMOverHeadCamEngine() -- internally within the car object's constructor, preserving encapsulation but creating a much more insidious coupling to a concrete class GMOverHeadCamEngine, or you can inject it, allowing your Car object to operate agnostically (and much more robustly) on for example an interface IEngine without the concrete dependency. Whether you use an IOC container or simple DI to achieve this is not the point -- the point is that you've got a Car that can use many kinds of engines without being coupled to any of them, thus making your codebase more flexible and less prone to side effects.

    DI is not a violation of encapsulation, it is a way of minimizing the coupling when encapsulation is necessarily broken as a matter of course within virtually every OOP project. Injecting a dependency into an interface externally minimizes coupling side effects and allows your classes to remain agnostic about implementation.

    0 讨论(0)
  • 2020-12-04 06:02

    It depends on whether the dependency is really an implementation detail or something that the client would want/need to know about in some way or another. One thing that is relevant is what level of abstraction the class is targeting. Here are some examples:

    If you have a method that uses caching under the hood to speed up calls, then the cache object should be a Singleton or something and should not be injected. The fact that the cache is being used at all is an implementation detail that the clients of your class should not have to care about.

    If your class needs to output streams of data, it probably makes sense to inject the output stream so that the class can easily output the results to an array, a file, or wherever else someone else might want to send the data.

    For a gray area, let's say you have a class that does some monte carlo simulation. It needs a source of randomness. On the one hand, the fact that it needs this is an implementation detail in that the client really doesn't care exactly where the randomness comes from. On the other hand, since real-world random number generators make tradeoffs between degree of randomness, speed, etc. that the client may want to control, and the client may want to control seeding to get repeatable behavior, injection may make sense. In this case, I'd suggest offering a way of creating the class without specifying a random number generator, and use a thread-local Singleton as the default. If/when the need for finer control arises, provide another constructor that allows for a source of randomness to be injected.

    0 讨论(0)
提交回复
热议问题