0

Is the dependency inversion principle an instance of orthogonalization? Given the class dependency X -> Y a common solution is to introduce interface Z so that the dependencies become X -> Z <- Y. In the first case a change in Y might cause a change in X. After introduction of Z a change in X will not cause Y to change, and a change in Y will not cause X to change. In this sense X and Y are orthogonal. Does this sound correct?

What I'm interested in is what appears to be a higher scale version of this problem. Imagine a complex system. Component P runs a policy that has various effects on the system. The policy can be swapped out or modified. Component M is a monitor whose job is to observe effects of various policies running in the system and compute for each policy a score on the interval [0,1] where 0 is bad and 1 is good. Component M can't fully monitor the system, so the environment is partially-observable. However, assume the score is accurate enough.

Basically P causes various effects and M attempts to maintain a rating computed from the set of effects caused by P.

Here is the interesting part. What if I want to 'trick' M into maintaining the rating at a constant? The rating of P is not something P owns. It is a perception. The idea is to introduce component Z that has an intuitive idea of how M computes its ratings. Z is not a policy so M is not monitoring it. Z can also engage in behavior that is restricted within policies. Every action by P triggers Z to strategically generate effects in the system. The idea is that all the effects together will trick M into keeping the score constant. P can generate 'bad' effects and Z will cover up for them strategically. M will think P is doing just fine. This might not work perfectly because the environment is partially-observable, the algorithm of M is not exactly known, Z may not know what effects will cover up for P, and many other reasons.

The goal is that actions by P do not effect the rating decisions of M. The introduction of the component Z is vaguely similar to the introduction of interface Z in the first example. The idea is that Z is an attempt to orthogonalize P and M. It might not work perfectly, but it's the attempt to do so that I'm labeling as orthogonalization.

Would it be proper to say Z attempts to orthogonalize P and M? Are P and M orthogonal actually?

The initial dependency is something like P <-> M. P depends on M for its perception, and M depends on P to compute an accurate score. (perhaps P also depends on itself for its perception)

The ideal dependency is P -> Z <- M. P depends on a clever Z for its perception. M depends on Z for its rating. Effectively, P triggers Z and Z controls the decision of M.

However, perhaps the initial dependencies cant be fully removed. In that case a strength can be attached to the dependencies. For its perception P is weakly dependent on M and highly dependent on Z. For its decision M is weakly dependent on P and highly dependent on Z. In this case they are nearly orthogonal?

Now imagine I not only want to keep the score computed by M for P at a constant, but occasionally the score needs to be adjusted up or down slightly. Z will do what is necessary to cause this. Would the word orthogonal still be proper?

Overall, what I'm looking for is a word or phrase that best fits this situation where the effects of P on decisions of M are controlled. (where decisions of M are decided elsewhere and causally transferred into its decision making process)

Some ideas I have are: Policy-Monitor {Orthogonalization, Dependency Inversion, Dependency Separation, Decision Inversion, Decision Control, Inversion of Control}.

Thanks for any help. Also, any links to relevant discussion would be nice.

Edit for clarification: This is abstract for me because right now its only a thought experiment that might lead to something interesting.

M not only monitors direct effects of P but all possible n-th order effects originating from P. Z is introduced to attempt to indirectly control the decisions of M by causing effects such that M attributes them to n-th order effects of P. Z secretly knows how M makes decisions, so its effect can be specially designed to make M compute the target score.

The important part is that it's about perception. M has a perception of P and owns it. P might not like the perception. P can react to M and change its policy details to try to get a better score, but the idea is that P does not want to react to M.

Z is an attempt to allow P to ignore M. The purpose of Z is to indirectly control the perception of P. It works something like this: M is monitoring P, Z creates side effects whose origin can be traced to P, M attributes the effects of Z to P, M updates the score of its perception of P. The point of Z is that its secretly has the same decision process as M without anyone knowing, so it has its own idea of what M's score for P is and can 'control' the score for P indirectly. M effectively just uses the score that Z tricks it into computing. Because Z knows how M works it never has to interact with M, it only needs to know what P is doing and generate proper effects to get the proper score (I suppose that means Z is dependent on P. The other possibility is that Z never interacts with P but instead gets the perception of P from M, so Z would be dependent on M. Or..... The dependencies are getting complicated, and it seems to parallel DIP less than I thought. I'll have to delve more into this.)

Also, no one knows that Z is working on behalf of P. A casual observer would believe M is monitoring P and in control of perception. But really there is a hidden Z that is controlling M's perception of P through tricky manipulations.

The claim of orthogonalization between P and M is because P no longer has to worry about M's perception and can forget M even exists, and M's perception of P is no longer affected by the behavior of P. Z purely determines M's perception of P.

I will try to come up with a diagram and a concrete example because it is not easy to reason about.

Regardless of the particular details, the big goal here is to remove (behavioral?) dependencies between P and M by influencing M's decision context and come up with a phrase that describes it.

Edit 2: I've received some great responses, and it is clear that comparing the P-Z-M relationship to DIP is not a great idea. Upon digging deeper these things work differently than I had first thought.

Additionally, I believe that "Shadows In Rain" was correct when mentioning that this seems like a fit for control theory. After looking into control theory it appears that the concepts from that field can properly model the P-Z-M situation. I'll explain towards the end.

First, I will explain in better detail where I went wrong in thinking about this. I used a "knob turning" perspective. To explain I'll start with a linear algebra example.

Let's say I have the basis e1=(1,0) and e2= (0,1). I also have a targe state (5,7). There is a knob associated with e1 and e2. To enter (5,7) I can turn e1 up by 5 and e2 up by 7. Or I can do it in reverse order. In either case the two knob turns together are 5e1+7e2=(5,7). From the "knob turning" perspective these are orthogonal because the second turn of the knob does not need to take into account the amount the first knob was turned by. The target state can be achieved by conflict-free turns of the knobs. The knobs turns represent updates to the object they are associated to.

In the DIP example I was thinking from the view of code evolution rather then code executtion where the environment is multiple developers and the code. The developers keep updating the code. Let's say I have class X that is doing too much. I separate concerns by creating another class Y. X uses the functionality of Y so the dependency is X->Y (an instance of Y is either passed into X or X directly instantiates an instance). In this an interface is not yet used, but concerns have been separated. The only changes to Y that will force a change to X are basically a class name change, a function name change, or Y being removed or replaced by some other class. A change to the internals of Y can be made and in cases other than the ones mentioned X will not have to change. However, you can abstract Y to get the dependency X->-Z<-Y. At this point a class name change to Y will not force a change to X, a function name change can't be made if it is the implementation from the interface, and Y can be removed or replaced with another implementation of Z without having to touch X. From the knob turning perspective you have two initial items X and Y. Each one has a "knob". A knob turn represents a change in the code. Lets say you have a target code state (X*, Y*) such that aX + bY = (X*, Y*) where a and b represent code changes (knob turns). To me it seems like the linear algebra example where I can turn either knob first and the second knob turn does not need knowledge about the first knob turn to achieve the desired state. The reason I think DIP looks like orthogonalization is because abstracting Y into Z is sort of like an orthogonalization process that allows parallel updates to X and Y to achieve a target code state without conflicts. In linear algebra the Gram-Schmidt process takes two vectors and orthogonalizes them so then to achieve a target vector state you can scale your orthogonal vectors independently without conflicts to achieve the target state. Is it clear why I saw a parallel? Is there a problem with the reasoning?

However, the P-Z-M example breaks down. I'll use the lamp example from Flater but modified. Let P be the blue lamp, M be the white object with a sensor, and Z be a yellow lamp. Let's say the sensor on M will log a negative event if it appears blue for too long.

Lets think about default state (P=off, M=senses itself as white object). Lets have target state (P*=shining blue light onto M, M*=not sensing itself as blue). To achieve this state P can turn itself on to shine blue light and Z can turn on its yellow light and strategically cancel out P so that M does not sense itself as blue. However, there are two major differences: order matters and Z must be acting in the process to achieve the target state. To achieve target (P*, M*) you have to turn on P first and then Z activates to prevent M from sensing itself as blue. So aP + bZ = (P*, M*) where "a" turns on P and "b" turns on Z to cancel the light. You can't do bZ + aP because Z reacts to P so if you turn on Z first it wont know what level of blue it needs to cancel. P then turns on and M will sense itself as blue. However it is possible to make an exception and say that Z is not a single action, it is a continuous process, so when you turn on Z first it continuously and dynamically attempts to cancels the blue light, and in such a case order would not matter again. There are many other target examples to think about here, but it should now be clear that this a quite different situation to the code changes and DIP example. The goal is the same: remove dependencies between two things. However, the dependency removal is different, results in various complications, and Z is not passive.

In the umbrella example let's say you have non-extreme weather, an umbrella, and a state representing if your shirt is wet or not. Lets have default state (P=sunny day without rain, M=dry shirt). Z is the umbrella. With target state (P=rain, M=dry shirt) you can't exactly start the rain, but if it starts to rain the umbrella keeps the shirt dry. So to get to the target state you can wait for rain and then hold up the umrella while outside. The weather will be rainy and the shirt dry. You can hold up the umbrella then wait for rain, or wait for rain hold up the umbrella. Either way the order does not matter, but here again Z is not passive. However, let's look at (P=rain, M=wet shirt). To get to this state without dependencies between P and M you can wait for the rain, hold up an umbrella while outside, and then spill water on your shirt. Or you can reverse the order. Here Z made sure to prevent dependency between P and M, but another component (the water) had to be used to achieve the desired wet shirt state for M. In the umbrella example Z is not passive and additional components need to be introduced. This is also very different. Although, the reason for complication seems to be that M here is a state rather than an object with behavior like the other examples

Overall, it seems the common theme is removing dependencies between two objects X and Y by introducing at least another object or process. In the linear algebra example e1 and e2 already have dependencies removed (perhaps after gram-schmidt process), but in the other examples an object or process has to be added which continuously keeps or attempts to keep the objects dependency free. Sort of like a continuous orthogonalization process that never actually completes.

Is there a good word or a field of study that deals with removing/modifying dependencies between two objects X and Y so that they be used together conflict-free? Decoupling?

Finally, control theory seems to be a good fit for the P-Z-M situation. Here is a picture from Wikipedia.

enter image description here

The controller is Z and the system contains P and M. The sensor gives the controller perception of what is happening with P and M. The controller has its own reference value of what score it wants M to have in regard to P. It looks at its perception to see what P is doing and then determines what behavior needs to be input into the system to keep the score at the intended level. Although, if M never publishes its scores then Z only knows how M works but not its score for P. The sensor never gives perception of M. Z will have to guess from its knowledge of M what M's score is. In this sense, Z controls the perception of M with only observing P. However, in my real case Z can observe both P and M, so I think it will work.

Overall, Flater gave the best examples and explanations that made me see the errors in thinking. Shadows In Rain mentioned control theory which appears to have the concepts and terminology to model the P-Z-M relationship as intended.

3
  • 3
    This question is IMHO a little bit hard to read because it is very abstract, a more concrete example of what P and M might look like would make it easier to understand. Also, your defintion of orthogonality sounds strange to me. I would call two components "orthogonal" when they don't influence each other. When I understand your case correctly, introducing a component "Z" does exactly the opposite - it makes P reacting to the output of M. Can you please clarify this?
    – Doc Brown
    Commented Jan 21, 2021 at 6:44
  • 1
    Sorry, I was under the impression you had a real system in mind, holding back details for reasons of confidentiality But since it seems to be a purely speculative "thought experiment ", I am now convinced this is the kind of question which cannot be answered sensibly because it lacks way-too-much realistic context.
    – Doc Brown
    Commented Jan 22, 2021 at 6:21
  • Well, to be honest there is a real system where Z is an experimental component. I just have to come up with a different example to be concrete because the real thing is way too complex and as you said "confidential".
    – vergilvsyn
    Commented Jan 22, 2021 at 6:35

3 Answers 3

2

Dependency inversion.

No, DI is not an instance of "orthogonalization" that you're describing. DI has no business in knowing what components do or what are their functional/semantical relations. The scope of DI is clearly defined: once component receives reference to it's dependency, DI's job is done.

Orthogonalization.

When using some term as an analogy, it must retain most relevant properties, in this case:

  1. Orthogonalized features must be uncorrelated. That is, "movement" along one feature must not cause "movement" along another feature.

  2. Orthogonalized features may be used to fully project contents of the original subspace without a loss of the details.

Following this definition, one example I can give is taking two components and refactoring common behavior (such as logging to same log file) into another facility. This way, original functionality is fully retained, and no component encapsulates same effect (a file write).

In other words, (in the domain of software design) orthogonalization is just a synonym for separation of concerns.

Facade.

In the first example, the interface Z add nothing useful to the system in regards to the dependency management. Changes in the implementations of X and Y must already be uncorrelated. On other hand, changes in Y's interface (the parts that X actually uses) must propagate to X, that's only natural considering dependency relation; introducing Z can not prevent that.

Now, your definition of interface may disagree with mainstream OOP languages. If that's the case, you may be looking for a facade. But then again, if Z is added for sake of hiding dependency between X and Y, then there is no value added, that's just moving the problem from the left pocket to the right pocket, while introducing more code and complexity.

Opposing controller.

In the second example, Z is not used to separate behaviors. Rather, Z is countering or complementing P's behavior. I would call Z an "adversarial controller" or an "{effect name} governor", but only because there is clear intent of countering some other feature. Overall I don't think introducing new term here is more useful than just naming features descriptively.

So what it is?

The two examples of "orthogonalization" that you provided seem unrelated to me, and grouping them together only makes things more confusing. My answer remains inconclusive.

Note the way you describe relationships between Z, P and M — with effects and observations — aligns well with the control theory. Perhaps it's domain will provide better framework for your goals.

1

There's an unexpected irony in the answer to your question, which I hope you'll appreciate. I'm borrowing your own phrasing for this specific purpose.

Given the class dependency X -> Y a common solution is to introduce interface Z so that the dependencies become X -> Z <- Y

While orthogonalization tries to find a compatible medium, and DI tends to introduce a medium to enhance compatibility, you'd think that DI -> orthogonalization. However, it's actually much broader. How you're describing the purpose of orthogonalization is really just abstraction, which DI is one example of (among many, many ways to abstract).

So a more correct answer is that DI -> abstraction <- orthogonalization.

So far the whimsical repetition of your phrasing. Back to being serious now.

In the first case a change in Y might cause a change in X. After introduction of Z a change in X will not cause Y to change, and a change in Y will not cause X to change. In this sense X and Y are orthogonal.

A developer would use the word "abstracted", whereas a mathematician would use the word "orthogonal", but they are thematically equivalent. In both cases, it means "able to change one without necessarily affecting the other".

What I'm interested in is what appears to be a higher scale version of this problem. Imagine a complex system. Component P runs a policy that has various effects on the system. The policy can be swapped out or modified. Component M is a monitor whose job

"Component" is a very apt term to use here. In software development terms, a component is a thing with a described boundary but an underscribed internal size. Think of it like Gabriel's horn. Strictly delineated, but it can run very deep within those boundaries.

Maybe a practical example works better. Let's say I have a notification component. You tell it which person should be notified of which message, and that is precisely what will happen.
You know exactly what the purpose (i.e. boundary) of this component is. However, you have no idea on the scale or complexity. Maybe this is a trivial five line mail sender. Maybe it's a one-line console print. Maybe it's an enterprise-grade application that manages contact details, rights, permissions, and complex state of the art technology to find this person anywhere on the planet and getting the message across by any means. I'm being silly here, but the point is that you have no idea how complex the notification component is.

Because of this inherently unknown complexity in components, this means that most development patterns apply equivalently regardless of scale. This means that you don't need to consider a larger scale to work out if a system works or not. You could build it using tiny test components and simply find out, rather than being stuck theorizing about it because building things on a large scale takes too much time.

The idea is to introduce component Z that has an intuitive idea of how M computes its ratings. Z is not a policy so M is not monitoring it. Z can also engage in behavior that is restricted within policies. Every action by P triggers Z to strategically generate effects in the system. The idea is that all the effects together will trick M into keeping the score constant. P can generate 'bad' effects and Z will cover up for them strategically. M will think P is doing just fine.

If you have me coin a term for this, I'd go with either cooking the books (I'd call Z a dirty accountant) or noise cancellation. That's just as an aside.

The main problem with this is not its viability, but rather that Z knowing precisely how M operates is orthogonal (pun intended) to one of the most essential software development pillars: encapsulation.

In short, encapsulation means that everyone has their own knowledge and it shouldn't be spread around to others. In practical terms, you don't teach your warehouse worker how to do part of the accounting work. The warehouse worker is the sole source of knowing how to do the warehouse management, and the accountant is the sole source of knowing how to do accounting.

Because of this, it's somewhat of a lost cause to try and express your goal using software development principles, as you're flying in the face of one of its most basic tenets.
Trying to find a mathematical analogy, this is like rounding pi to be exactly 3 and then trying to rely on more advanced trigonometric principles. It just won't make sense anymore.

The goal is that actions by P do not effect the rating decisions of M. The introduction of the component Z is vaguely similar to the introduction of interface Z in the first example. The idea is that Z is an attempt to orthogonalize P and M.

No, I don't agree with this. Both in that they are two completely unrelated concepts, and that this isn't even what's happening in the way you're thinking about it.

Z is not abstracting the connection between P and M. Z is obfuscating P to M. Those are two very different things. You've swapped a P-M relationship for a P-Z-M relationship. That is an additional dependency. You've not removed a dependency, you've introduced a second one.

Think of it this way: A lamp is shining a blue light onto a white object. But you don't want the object to turn blue, so you create a yellow lamp to cancel out the blue hue. This lamp is smart enough to always match the blueness of the blue lamp, to perfectly cancel out.

Is the white object now no longer being lit by the blue lamp? No, it still is. You haven't disconnected the two. You've just introduced another lamp which also lights the white object. That doesn't change the interaction between the blue lamp and white object.

I think the root of this question is a confusion between the dependency and the actual practical outcome. Yes, you've noise-cancelled P, but P is still there, and is still acting on the system. Just because you hold up an umbrella does not mean that it has stopped raining.

1
  • Although I don't fully agree with everything in this answer it has helped clarify the errors in reasoning. See edit 2
    – vergilvsyn
    Commented Aug 30, 2021 at 1:23
-2

M cannot "know" P and that is the point. M can only observe the behavior of P which it must regard as a black box. Its "perceptions" are the only thing that it is allowed to know and to react to.

1
  • Correct. The edit has some more details. The idea is that M owns it's perception of P but P (or whoever controls P) may not like the score. Z is introduced to manipulate M's perception. P can ignore M. P believes its score is dependent on it's perception of P, but the reality is that M's perception is controlled by Z, so P's behavior has no effect on M's decision.
    – vergilvsyn
    Commented Jan 22, 2021 at 4:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.