Frage

I have a Business Layer, whose only one class should be visible to outer world. So, I have marked all classes as internal except that class. Since that class requires some internal class to instantiate, I need other classes to be marked as public and other classes depend on some other classes and so on. So ultimately almost all of my internal classes are made public.

How do You handle such scenarios?

Also today there is just one class exposed to outer world but in future there may be two or three, so it means I need three facades?

Thanks

War es hilfreich?

Lösung 2

After doing a lot of research I am writing my findings, so that it may be of some help to newcomers on Dependency Injection


Misunderstandings regarding my current design and Dependency Injection:

Initial approach and problem(s) associated with it:

My business layer was having a composition root inside it, where as it should be outside the business layer and near to the application entry point. In composition root I was essentially having a big factory referred as Poor Man's DI by Mark Seemann. In my application starting point, I was creating an instance of this factory class and then creating my only (intended to be) visible class to outside world. This decision clearly violates Liskov's Principle which says that every dependency should be replaceable. I was having a modular design, but my previous approach was tightly coupled, I wasn't able to reap more benefits out of it, despite only some code cleanliness and code maintainability.

A better approach is:

A very helplful link given by Facio Ratio

The Composition root should have been near the application root, all dependency classes should be made public which I referred initially as a problem; making them public I am introducing low coupling and following Liskov's substitution which is good.

Andere Tipps

Correct, all of your injected dependencies must be visible to your Composition Root. It sounds like you're asking this question: Ioc/DI - Why do I have to reference all layers/assemblies in entry application?

To quote part of that answer from Mark Seeman:

you don't have to add hard references to all required libraries. Instead, you can use late binding either in the form of convention-based assembly-scanning (preferred) or XML configuration.

Also this, from Steven:

If you are very strict about protecting your architectural boundaries using assemblies, you can simply move your Composition Root to a separate assembly.

However, you should ask yourself why doing so would be worth the effort. If it is merely to enforce architectural boundaries, there is no substitute for discipline. My experience is that that discipline is also more easily maintained when following the SOLID principles, for which dependency injection is the "glue".

You can change the public class to the interface and all other parts of the program will only know about the interface. Here's some sample code to illustrate this:

public interface IFacade
{
    void DoSomething();
}

internal class FacadeImpl : IFacade
{
    public FacadeImpl(Alfa alfa, Bravo bravo)
    {
    }

    public void DoSomething()
    {
    }
}

internal class Alfa
{

}

internal class Bravo
{

}

I can see three solutions, none real good. You might want to combine them in someway. But...

First, put some simple parameters (numeric, perhaps) in the constructor that let the caller say what he wants to do, and that the new public class instance can use to grab internal class objects (to self-inject). (You could use special public classes/interfaces used solely to convey information here too.) This makes for an awkward and limited interface, but is great for encapsulation. If the caller prefers adding a few quick parameters to constructing complex injectable objects anyway this might work out well. (It's always a drag when a method wants five objects of classes you never heard of before when the only option you need, or even want, is "read-only" vs "editable".)

Second, you could make your internal classes public. Now the caller has immense power and can do anything. This is good if the calling code is really the heart of the system, but bad if you don't quite trust that code or if the caller really doesn't want to be bothered with all the picky details.

Third, you might find you can pull some classes from the calling code into your assembly. If you're really lucky, the class making the call might work better on the inside (hopefully without reintroducing this problem one level up).

Response to comments:

As I understand it, you have a service calling a method in a public class in your business layer. To make the call, it needs objects of other classes in the business layer. These other classes are and should be internal. For example, you want to call a method called GetAverage and pass it an instance of the (internal) class RoundingPolicy so it knows how to round. My first answer is that you should take an integer value instead of a class: a constant value such as ROUND_UP, ROUND_DOWN, NEAREST_INTEGER, etc. GetAverage would then use this number to generate the proper RoundingPolicy instance inside the business layer, keeping RoundingPolicy internal.

My first answer is the one I'm suggesting. However, it gives the service a rather primitive interface, so my second two answers suggest alternatives.

The second answer is actually what you are trying to avoid. My thinking was that if all those internal classes were needed by the service, maybe there was no way around the problem. In my example above, if the service is using 30 lines of code to construct just the right RoundingPolicy instance before passing it, you're not going to fix the problem with just a few integer parameters. You'd need to give the overall design a lot of thought.

The third answer is a forlorn hope, but you might find that the calling code is doing work that could just as easily be done inside the business layer. This is actually similar to my first answer. Here, however, the interface might be more elegant. My first answer limits what the service can do. This answer suggests the service doesn't want to do much anyway; it's always using one identical RoundingPolicy instance, so you don't even need to pass a parameter.

I may not fully understand your question, but I hope there's an idea here somewhere that you can use.

Still more: Forth Answer:

I considered this a sort of part of my first answer, but I've thought it through and think I should state it explicitly.

I don't think the class you're making the call to needs an interface, but you could make interfaces for all the classes you don't want to expose to the service. IRoundingPolicy, for instance. You will need some way to get real instances of these interfaces, because new IRoundingPolicy() isn't going to work. Now the service is exposed to all the complexities of the classes you were trying to hide (down side) but they can't see inside the classes (up side). You can control exactly what the service gets to work with--the original classes are still encapsulated. This perhaps makes a workable version of my second answer. This might be useful in one or two places where the service needs more elaborate options than my first answer allows.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top