Monday, April 21, 2008

Law Of Demeter

The principle states: Each unit should only talk to its friends; Don’t talk to strangers. A method of an object should invoke only the methods of the following kinds of objects:

    1. itself
    2. its parameters
    3. any objects it creates/instantiates
    4. its direct component objects
Below is an example of some code breaking this principle:
Interface: IOrderManager
1
2
3
4
5
6
7
8
9
namespace LOD
{
    public interface IOrderManager
    {
        void AddItemToOrder(IItem item);
        void RemoveItemFromOrder(IItem item);
        void ChangeItemName(IItem item, string name);
    }
}
Interface: IOrder
1
2
3
4
5
6
7
namespace LOD
{
    public interface IOrder
    {
        List<IItem> Items { get; set; }
    }
}
Interface: IItem
1
2
3
4
5
6
7
8
namespace LOD
{
    public interface IItem
    {
        int Id { get; set; }
        string Name { get; set; }
    }
}
Class: Item : IItem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace LOD
{
    public class Item : IItem
    {
        private int _Id;
        private string _Name;

        public int Id
        {
            get { return _Id; }
            set { _Id = value; }
        }
        public string Name
        {
            get { return _Name; }
            set { _Name = value; }
        }
    }
}
Class: Order : IOrder
1
2
3
4
5
6
7
8
9
10
11
12
13
namespace LOD
{
    public class Order : IOrder
    {
        private List<IItem> _Items;

        public List<IItem> Items
        {
            get { return _Items; }
            set { _Items = value; }
        }
    }
}
Class: OrderManager : IOrderManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
namespace LOD
{
    public class OrderManager : IOrderManager
    {
        private readonly IOrder _Order;

        public OrderManager()
        {
            _Order = new Order();
            _Order.Items = new List<IItem>();
        }

        public void AddItemToOrder(IItem item)
        {
            if (_Order.Items != null)
            {
                if ( ! _Order.Items.Contains(item))
                {
                    _Order.Items.Add(item);
                }
            }
        }
        public void RemoveItemFromOrder(IItem item)
        {
            if (_Order.Items != null)
            {
                if (_Order.Items.Contains(item))
                {
                    _Order.Items.Remove(item);
                }
            }
        }
        public void ChangeItemName(IItem item, string name)
        {
            if (_Order.Items != null)
            {
                if (_Order.Items.Contains(item))
                {
                    _Order.Items[_Order.Items.IndexOf(item)].Name = name;
                }
            }
        }
    }
}
In the above example the OrderManager has the responsibility of adding and removing Items to and from the List collection in the Order class, and even worse it has the responsibility of changing the name of a particular Item inside that collection. This tightly couples the OrderManager to the Order and Item class, if we would change how the Order class is maintaining her Items than we automatically have to change the OrderManager class. The Order should be responsible for its Items, not the OrderManager. Below is the Law Of Demeter implementation of this example:
Interface: IOrder
1
2
3
4
5
6
7
8
9
namespace LOD
{
    public interface IOrder
    {
        void AddItem(IItem item);
        void RemoveItem(IItem item);
        void ChangeItemName(IItem item, string name);
    }
}
Class: Order : IOrder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
namespace LOD
{
    public class Order : IOrder
    {
        private List<IItem> _Items;

        public Order()
        {
            _Items = new List<IItem>();
        }

        public void AddItem(IItem item)
        {
            if (_Items != null)
            {
                if (!_Items.Contains(item))
                {
                    _Items.Add(item);
                }
            }
        }
        public void RemoveItem(IItem item)
        {
            if (_Items != null)
            {
                if (_Items.Contains(item))
                {
                    _Items.Remove(item);
                }
            }
        }
        public void ChangeItemName(IItem item, string name)
        {
            if (_Items != null)
            {
                if (_Items.Contains(item))
                {
                    _Items[_Items.IndexOf(item)].Name = name;
                }
            }
        }
    }
}
Class: OrderManager : IOrderManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace LOD
{
    public class OrderManager : IOrderManager
    {
        private readonly IOrder _Order;

        public OrderManager()
        {
            _Order = new Order();
        }

        public void AddItemToOrder(IItem item)
        {
            _Order.AddItem(item);
        }
        public void RemoveItemFromOrder(IItem item)
        {
            _Order.RemoveItem(item);
        }
        public void ChangeItemName(IItem item, string name)
        {
            _Order.ChangeItemName(item, name);
        }
    }
}
Now we can change how the Order class is keeping its Items without having to change the OrderManager. One nice rule of thumb is: One dot should be enough.

I hope you found these examples helpful. Also take a look at the following link:

http://www.ccs.neu.edu/research/demeter/demeter-method/LawOfDemeter/paper-boy/demeter.pdf

No comments: