Is it better to expose or hide dependency in OOP?
It's best to do both.
Before I explain that let me explore the problems with your proposals.
Let's say I have an object A, which is too big(having too many methods and variables).
So, I break it down to smaller objects.
This is good. Always break objects down until they have only one responsibility, that is, only one reason they would ever change. That doesn't mean they can have only one method or dependency, but they should center around one idea.
- create an Object C in Class B, and create an Object B in Class A. Then, create Object A in main method.
Depending on something isn't the same as knowing how to create it. This idea requires that A (and B) do both. Didn't we break up A because it was doing to much in the first place?
I'll make exceptions for value objects (Strings and such) but generally I like to separate use from construction.
- create Object A, B, and C through dependency injection in main method.
This sounds fine, A uses B but doesn't build it. B uses C but doesn't build it. What about applications so big they need more than 26 letters of the alphabet to give them unique names? Main can suddenly become a busy place.
There are a host of dependency injection frameworks that would love to solve this problem for you if you'll just sell your soul to them. That is, become dependant on them.
If you're a bit stubborn you can use creational patterns. The GoF ones still work. You can also use the newer creational patterns, The Josh builder works well in Java and other named parameter impaired languages. There are other builders that let you have amazing levels of control over creation if you're willing to put in the effort.
This goes as far as internal Domain Specific Languages (iDSLs). These let you essentially define your own language inside Java (or most any other) and Java's compiler will compile it for you. This gives you complete control the creation and injection of objects. But it can demand considerable work. Tread lightly here because this rabbit hole is deep.
This brings me to my "do both" recommendation.
A very successful habit I've developed is to accept everything as an injection in my behavior objects, but build them with specialized injectors that know good default values that take few or no injections themselves. You can have, say, 5 such injectors for the same class that build it in 5 different ways. You can have one injector that builds 5 behavior objects all nicely wired together. You can even inject injectors into injectors and have them weave together a behavoir object graph.
Identify good default values if they exist and use those to make your basic injector. Some tend to use static methods as injectors but they can be full blown objects themselves. That's actually more powerful.
That's how you "do both". Dependencies are exposed for behavior objects. Creational objects hide dependencies, doing the work that would otherwise be spread all over main.
What this resolves is a fundamental tension between good machine API and human usability. What makes for a flexible injectable class doesn't make for a good human usable class constructor. When I find myself wanting to make building an object easier I reach for another object to do that work. I don't distract the behavior object with it's own creational details.
I've used different dependency injection frameworks and came away feeling that all they really ended up doing for me was expressing object creation in another language (be that xml or @annotations). Turns out you can do it all in java. Pick good names so people know which kind of class they're in and it's just as maintainable.
Done this way, a look in main will show the linking together of a few canned object graphs and one call to a method on one of those objects to start the whole thing ticking. That's the one place I mix creation and use. You have to do it somewhere.
Like anything this can be overdone, but with the right balance it makes coding a bit simpler.