#688 – Aggregation, Composition and Containment

The idea of containment in object-oriented programming is the idea that an outer class contains an instance of another class and allows access to the contained object through its own methods.

Aggregation is one form of containment where the contained object can exist independently from the outer object.  For example, a Family object might contain an instance of a Dog object, representing a dog that the family owns.

    public class Family
    {
        List<Dog> OurDog { get; set; }

        void AcquireDog(Dog d)
        {
            OurDog.Add(d);
        }
    }

Composition is a type of containment where the contained object is created and exists entirely within the object that contains it.  It ceases to exist when the containing object is destroyed.  For example a Dog is composed of an instance of a DogName object (among other things).  You can’t have an instance of a DogName without a Dog.

Advertisement

#687 – An Example of Containment

One common strategy is to use inheritance when modeling an is-a type of relationship (e.g. a Terrier is-a Dog) and to use containment when modeling a has-a type of relationship (e.g. a Family has-a Dog).

Here’s an example of containment, where a Dog object contains instances of the DogName and DogAbilities classes.

    internal class DogName
    {
        public DogName(string humanName, string nameInDogWorld)
        {
            HumanName = humanName;
            NameInDogWorld = nameInDogWorld;
        }

        public string HumanName { get; set; }
        public string NameInDogWorld { get; set; }
    }

    internal class DogAbilities
    {
        public DogAbilities(List<string> knownTricks, bool canBark)
        {
            KnownTricks = knownTricks;
            CanBark = canBark;
        }

        public List<string> KnownTricks { get; set; }
        public bool CanBark { get; set; }
    }

    public class Dog
    {
        public Dog(string humanName, string firstTrick, bool canBark)
        {
            Name = new DogName(humanName, GenerateSecretDogWorldName());

            List<string> abilities = new List<string>();
            abilities.Add(firstTrick);

            Abilities = new DogAbilities(abilities, canBark);
        }

        public void Bark()
        {
            if (Abilities.CanBark)
                Console.WriteLine("Woof!");
            else
                Console.WriteLine("Wag");
        }

        public void DoTrick(string trick)
        {
            if (Abilities.KnownTricks.Contains(trick))
                Console.WriteLine("Doing {0} trick", trick);
        }

        public void TeachTrick(string newTrick)
        {
            if (!Abilities.KnownTricks.Contains(newTrick))
                Abilities.KnownTricks.Add(newTrick);
        }

        private DogName Name { get; set; }
        private DogAbilities Abilities { get; set; }

        private string GenerateSecretDogWorldName()
        {
            return "His noble woofiness sir poops-a-lot";
        }
    }

#686 – Inheritance vs. Containment

You can think of inheritance as white box reuse of existing code, meaning that a derived class can see some aspects of the internals of the base class.  This violates the principle of encapsulation, which states that the implementation details of a class are hidden from any code that uses the class.

There is another method of code reuse known as containment, which results in black box reuse of existing code.  Instead of deriving from a base class, the new class just creates an instance of the class internally and accesses only the public members of the instance.  The instance of the base class can be thought of as a black box, because the derived class doesn’t have access to the implementation details of the base class.