Association, aggregation, dependency all are developed relationship between objects. The relationship is attributed on the basis of the interaction of the objects. In this article, we will explore these relationships.
Association relationship develops between objects when one object uses another object. Technically, it is a reference based relationship between two classes. One class holds a class level reference to the other class.
In the above example, a
Teacher list is referred in the
student class. The system interprets a
student has multiple teachers. In this
student teacher relationship, one is being used by another. But there is no part-whole relationship between them. This means neither of them is the part of another. Hence they have their own lifetime.
Aggregation is the same as association. Like association, this relationship develops while one object uses the other. Additionally, the objects develop a part-whole relationship and the lifetime of part does not depend on life time of whole.
void CellPhone (CellBattery battery)
_battery = battery;
In real life, cellphone battery is a part of cellphone. If for some reason (other than battery damage), my cellphone became non-functional, still I can use the battery in another phone. This kind of relationship implies aggregation. The lifetime of Part (cellphone battery), doesn’t depend on the lifetime of Whole (cellphone).
This real life scenario has been captured through the above coding. We are setting a
CellBattery instance from outside the
CellPhone class. This ensures, while the
CellPhone gets destroyed (garbage collected),
CellBattery survives. This
CellBattery could be reused to some other
Difference between Association and Aggregation
Aggregation is the same as association and is often seen as a redundant relationship. Technically, aggregation doesn’t convey anything more effective about a software design than an association. The difference lies from the conceptual point of view. When the objects develop a Part-Whole relationship and the lifetime of Part doesn’t depend on the lifetime of Whole, the relationship is attributed as aggregation. But in case of association, there is no Part-Whole relationship.
In composition also, one object uses another object and they develop a Part-Whole relationship. But here the lifetime of Part depends on the lifetime of Whole.
List<Room> rooms = new List<Room> ();
In real life,
rooms are part of
Hotel. If the
Hotel is destroyed, obviously the rooms of the hotels will meet the same fate. This kind of relationship implies composition. The lifetime of Part (hotel’s room depends on the lifetime of Whole (hotel).
This real life scenario has been captured through the above coding. We are creating Rooms inside the
Hotel class. This kind of implementation ensures, while the
Hotel get destroyed (garbage collected),
Room is also destroyed.
Dependency can develop between objects while they are associated, aggregated or composed. This kind of relationship develops while one object invokes another object’s functionality in order to accomplish some task. Any change in the called object may break the functionality of the caller.
void SendGreetings(EmailSender emailSender)
_emailSender = emailSender;
public void SendEmail()
In the above example,
Greetings are being sent through
GreetingSender object is using the
SendEmail () method of
EmailSender object to accomplish it’s task. Now, if any modification ((e.g. introduction of a parameter) is made to the
SendEmail() method, the
SendGreetings() method of
GreetingSender class will break. Also, as per the implementation, we cannot send greetings by any other mode but only through
Email. That way, my
GreetingSender object’s functionality is dependent on
EmailSender object’s functionality. This kind of relationship is termed as dependency.
Dependency Injection is a practice about injecting functional dependency in the object. In the above example, we have injected a dependency into the
GreetingSender object. We can understand the injected dependency has developed a tight coupling between
EmailSender objects. We cannot send greetings by any other mode but only through email. So our injected dependency is violating the design principle. As a best practice, we should inject dependency in such a way that objects will be decoupled and re-usable.
IoC (Inversion of Control)
The term Inversion of Control (IoC) refers to a programming style where a framework or runtime controls the program flow. Inversion of control means we are changing the control from normal way.
If we analyze our above dependency example, we can see the
EmailSender object is controlling the
SendGreetings functionality. We cannot send greetings by any other mode but only through email. But this behavior is not desired. The implementation results to tight coupling. As a solution, we can invert the control flow and let the runtime control its
SendGreetings functionality. The runtime will take the decision, which mode of communication (Email/SMS) should be used for the functionality. This kind of solution followed a principle termed as IoC. We can implement the principle using the dependency injection. We can say, IoC is the strategy and DI is the implementation.
Let’s see how we can solve our dependency problem using IOC principle.
Solution of the Dependency Problem
GreetingSender class aggregating the
EmailSender class should not depend on the direct implementation of the class.
GreetingSender class should depend on the abstraction of the greeting sending functionality, rather than the concrete implementation of the functionality (sending greetings through email).
Let’s define the abstraction of “
send” functionality on defining an
EmailSender class from this interface. Implement email sending functionality in the
public class EmailSender: ISender
Aggregate the abstraction of “
send“ functionality in the
public GreetingSender(ISender sender)
_sender = sender;
In the above implementation, we are injecting abstraction of the functionality, not the concrete functionality. With this kind of injected dependency, we made the objects loosely coupled. Here, I am not defining anywhere, what kind of communication mode (Email or SMS) , I will be used for sending greetings. In future, if I want to send Greetings through SMS, I will simply define a
SMSSender class and implement the
send method for sending SMSes.
public class SMSSender : ISender
Now, let’s provide the concrete instances from some other module, for sending greetings using email or SMS:
EmailSender emailSender = new EmailSender();
SMSSender smsSender = new SMSSender();
GreetingSender greetingsEmailSender = new GreetingSender(emailSender);
GreetingSender greetingsSMSSender = new GreetingSender(smsSender);
greetings class is not controlled by anyone in terms of communication mode for greetings sending. The class has become loosely coupled with
SMSsender class. The control has been inverted. The runtime has taken the control.