Jonathan Birkholz

Interface Segregation Principle in Practice

I am working with some other team members integrating components to be consumed by different controls. For example someone could use a grid and then opt in to have sorting or filtering etc. It is a rather weird system but was decided upon before I arrived.

Anyway, I am currently looking to implement an adapter for the formatting component. When I looked at the interface, ISP immediately jumped out at me. So I thought, why not do a blog post about identifying the problem and delivering the solution.

Interface Segregation Principle

The Interface Segregation Principle states that clients should not be forced to implement interfaces they don’t use. Instead of one fat interface many small interfaces are preferred based on groups of methods, each one serving one submodule. - http://www.oodesign.com/interface-segregation-principle.html

So instead of one large interface we should have many smaller interfaces with grouped behavior.

When we see an interface like this, the violation of ISP should be readily apparent. (warning.. be prepared to scroll…..)

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/// <summary>
/// An interface defining the contract for anything that has columns that can be formatted.
/// </summary>
public interface IColumnFormattingAdapter
{
#region Methods
/// <summary>
/// Hides the column.
/// </summary>
void HideColumn();
/// <summary>
/// Bests the fit header.
/// </summary>
void BestFitHeader();
/// <summary>
/// Bests the fit column.
/// </summary>
void BestFitColumn();
/// <summary>
/// Bests the fit header column.
/// </summary>
void BestFitHeaderColumn();
/// <summary>
/// Bests the fit all columns/headers.
/// </summary>
void BestFitAllHeaderColumns();
/// <summary>
/// Formats the column.
/// </summary>
void FormatColumn();
/// <summary>
/// Formats all columns.
/// </summary>
void FormatAllColumns();
/// <summary>
/// Shows the advanced formatting.
/// </summary>
void ShowAdvancedFormatting();
/// <summary>
/// Shows the advanced report formatting.
/// </summary>
void ShowAdvancedReportFormatting();
/// <summary>
/// Chooses the column.
/// </summary>
void ChooseColumn();
#endregion
#region Feature Support
/// <summary>
/// Whether or not the adapter supports hiding of columns
/// </summary>
Boolean AllowHideColumn { get; }
/// <summary>
/// Whether or not the adapter supports best fit based on the header text
/// </summary>
Boolean AllowBestFitHeader { get; }
/// <summary>
/// Whether or not the adapter supports best fit based on the column contents
/// </summary>
Boolean AllowBestFitColumn { get; }
/// <summary>
/// Whether or not the adapter supports best fit based on the longer of header text and column contents
/// </summary>
Boolean AllowBestFitHeaderColumn { get; }
/// <summary>
/// Whether or not the adapter supports best fit on all columns based on the longer of header text and column contents
/// </summary>
Boolean AllowBestFitAllHeaderColumns { get; }
/// <summary>
/// Whether or not the adapter supports formatting of a column
/// </summary>
Boolean AllowFormatColumn { get; }
/// <summary>
/// Whether or not the adapter supports formatting of all columns
/// </summary>
Boolean AllowFormatAllColumns { get; }
/// <summary>
/// Whether or not the adapter supports advanced conditional formatting
/// </summary>
Boolean AllowAdvancedFormatting { get; }
/// <summary>
/// Whether or not the adapter supports advanced report property formatting
/// </summary>
Boolean AllowAdvancedReportFormatting { get; }
/// <summary>
/// Whether or not the adapter supports choosing columns
/// </summary>
Boolean AllowChooseColumn { get; }
#endregion
}

Identifying ISP Violation

The first characteristic that the interface violates ISP is the sheer size. This interface weighs in at 117 lines. Granted there are comments, etc but it is still rather large.

The second characteristic that the interface violates ISP (and the biggest eye sore) is all the Allow properties. That should make you stop and scratch your head.

Fixing the interface

Let’s simplify the bad interface and then learn how to fix it.

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
/// <summary>
/// An interface defining the contract for anything that has columns that can be formatted.
/// </summary>
public interface IBadColumnFormattingAdapter
{
/// <summary>
/// Hides the column.
/// </summary>
void HideColumn();
/// <summary>
/// Chooses the column.
/// </summary>
void ChooseColumn();
/// <summary>
/// Whether or not the adapter supports hiding of columns
/// </summary>
Boolean AllowHideColumn { get; }
/// <summary>
/// Whether or not the adapter supports choosing columns
/// </summary>
Boolean AllowChooseColumn { get; }
}

So in this scenario we can hide a column and then choose a column. Instead of having a large interface for these two behaviors, we should package the behaviors into separate interfaces.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// <summary>
/// Interface for supporting Hide Column feature
/// </summary>
public interface IHideColumn
{
/// <summary>
///Hides the column.
/// </summary>
void HideColumn();
}
/// <summary>
/// Interface for supporting Choose Column feature
/// </summary>
public interface IChooseColumn
{
/// <summary>
///Chooses the column.
/// </summary>
void ChooseColumn();
}

Now our functionality is encapsulated into behavior centric interfaces. Our adapters can then opt in to provide the functionality, and we don’t have the superfluous Allow properties.

C#, SOLID