CSC/ECE 517 Fall 2009/wiki3 8 agummad: Difference between revisions
No edit summary |
mNo edit summary |
||
(12 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
<h1>Interface Segregation Principle vs. Principle of Small Interfaces</h1> | |||
<h2>Introduction</h2> | |||
Software changes. To enable software to age gracefully, a lot of time is spent in designing an implementation that can be extended, modified and maintained with efficacy. In this article, we present two specific design rules that deal in different levels of abstractions in the design phase, the Interface Segregation Principle suggested by Bob Martin and the Principle of Small Interfaces, postulated by Bertrand Meyer. We present an explanation of both these principles and provide a comparison of the strategies. | |||
The | |||
However, as the scope and the size of these projects grew, researchers studied ways to maintain the quality of the software written. They | <h2>Background</h2> | ||
<h3>Object Oriented programming and Design patterns</h3> | |||
The object oriented principles of programming and their associated design patterns have long been studied heavily by a computer programmer. Ever since object oriented programming replaced the functional type programming style of the early 70s, it has been used extensively to develop commonly used software. | |||
However, as the scope and the size of these projects grew, researchers studied ways to maintain the quality of the software written. They introduced patterns of programming within the Object paradigm to address some very common problems occurring while programming and maintaining a large project. This study of object oriented design programming patterns and related principles is a vast field, and its understanding is considered vital for developers. | |||
A modular system is one that is made of autonomous elements which are | |||
<h3>Modularity</h3> | |||
A modular system is one that is made of autonomous elements which are connected and inter-operate in a simple yet coherent structure. However to formally define modularity, one needs to look at the concept from different view points. We may define modularity along the following concepts, as defined in [1]. | |||
software elements which may then be freely used as building blocks for newer systems. Each of these elements may be combined with each other to produce new systems,quite often in an environment very different from the ones in which they were initially developed. | <ul> | ||
<li>'''Decompose-ability:''' A software development method is Decomposable if it helps in the task of decomposing functionality into a small number of less complex subproblems. These smaller units of functionality are then connected by a simple structure, allowing them to be independent from each other in that each of them is extensible without affecting the others.</li> | |||
<li> '''Composability''':A software development method is Modular composable if it allows the production of | |||
just one module, or a small number of modules. | software elements which may then be freely used as building blocks for newer systems. Each of these elements may be combined with each other to produce new systems,quite often in an environment very different from the ones in which they were initially developed.</li> | ||
<li>'''<b>Understandability</b>''': A software development method should produce software, which is non trivial enough in that a human can understand the functionality and working of each module without having to learn about others or, at worst, by having to examine only a few of the others.</li> | |||
<li>''' Continuity''': A software development method satisfies Modular Continuity if, in the software architectures that it yields, a small change in a problem specification will trigger a change of just one module, or a small number of modules.</li> | |||
<li>Protection: A method satisfies Modular Protection if it yields architectures in which the | |||
effect of an abnormal condition occurring at run time in a module will remain | effect of an abnormal condition occurring at run time in a module will remain | ||
confined to that module, or at worst will only propagate to a few neighboring | confined to that module, or at worst will only propagate to a few neighboring | ||
modules. | modules.</li> | ||
From the preceding criteria, five rules follow which we must observe to ensure | </ul> | ||
modularity: | <h3>Rules for Modularity</h3> | ||
From the preceding criteria, five rules follow which we must observe to ensure modularity in a given system. These rules were collectively postulated by Bertrand Meyer: | |||
<ul> | |||
<li>'''Rule of Direct Mapping''': The modular structure devised in the process of building a software system | |||
should remain compatible with any modular structure devised in the process | |||
of modeling the problem domain.</li> | |||
<li>'''Rule of Few Interfaces''': Every module should communicate with as few others as possible. The effect of a change or of an error may propagate to a large number of modules as a result of too many relations between modules of an implementation. It</li> | |||
<li>'''Rule of Small interfaces (weak coupling)''' : If two modules communicate, they should exchange as little information as | |||
possible. This rule relates to the size of inter-module connections rather than to their number. This rule corresponds directly with our topic and will be dealt in more depth.</li> | |||
<li>'''Rule of Explicit Interfaces''': When modules interact, we expect them to do so "loudly" or "in public", in full view of all the other modules of the system. </li> | |||
<li>'''Rule of Information Hiding''': The designer of every module must select a subset of the module’s properties | |||
as the official information about the module, to be made available to authors | |||
of client modules. </li> | |||
</ul> | |||
<br /><br /> | |||
SOLID Design Principles | <h3>SOLID Design Principles</h3> | ||
The SOLID principles of Object Oriented Design are five basic class design principles. These were first stated by Bob Martin and are outlined extensively on his | The SOLID principles of Object Oriented Design are five "best practices" basic class design principles. These were first stated by Bob Martin and are outlined extensively on his website[2]. Collectively, they provide a set of guidelines that help design better object oriented systems. The SOLID principles are most concerned with dependency management, or the ability to keep code loosely coupled and flexible. The 5 principles are given below : | ||
<ul> | |||
For example, an Invoice class might have the responsibility of calculating various amounts based on it's data. In that case it probably shouldn't know about how to retrieve this data from a database, or how to format an invoice for print or display. | <li>'''SRP - The Single Responsibility Principle''' - A class should have one, and only one, reason to change. The SRP says a class should focus on doing one thing. This doesn't mean it should only have one method, but instead all the methods should relate to a single purpose. | ||
For example, an Invoice class might have the responsibility of calculating various amounts based on it's data. In that case it probably shouldn't know about how to retrieve this data from a database, or how to format an invoice for print or display. </li> | |||
Bob Martin's initial paper on the OCP linked from The Principles of OOD attributes the idea to Bertrand Meyer, who wrote that classes should be "open for extension, but closed for modification"[2]. The idea is that we can use OO techniques like inheritance and composition to change (or extend) the | <li>'''OCP - The Open Closed Principle''' - You should be able to extend a classes behavior, without modifying it. Change a class' behaviour using inheritance and composition. | ||
Bob Martin's initial paper on the OCP linked from The Principles of OOD attributes the idea to Bertrand Meyer, who wrote that classes should be "open for extension, but closed for modification"[2]. The idea is that we can use OO techniques like inheritance and composition to change (or extend) the behavior of a class, without modifying the class itself.</li> | |||
<li>'''LSP - The Liskov Substitution Principle''' - Derived classes must be substitutable for their base classes. Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it. Ensuring a base class works in any situation the parent does is really hard work, and whenever you use inheritance its a good idea to keep the LSP firmly in mind.</li> | |||
<li>'''ISP - The Interface Segregation Principle''' - Make fine grained interfaces that are client specific. This priciple deals with keeping interfaces small and limited only to a very specific need. A fat interface imposes a huge implementation burden on anyone that wants to stick to that contract </li> | |||
<li>'''DIP - The Dependency Inversion Principle''' - Depend on abstractions, not on concretions. This means that if a class has dependencies on other classes, it should rely on the dependencies' interfaces rather than their concrete types. This helps keep coupling low and makes our design easier to change.</li> | |||
</ul> | |||
<h2>The Interface Segregation Principle</h2> | |||
" | <h3>Statement</h3> | ||
The Interface Segregation Principle (ISP)[2] is one of the five SOLID Principle, first advocated by Bob Martin dealing with heavy interfaces. It states the following: | |||
<pre> "Make fine grained interfaces that are client specific." </pre> | |||
<h3>Motivation</h3> | |||
This principle deals with the disadvantages of polluted interfaces -"fat" interfaces and unnecessary recompilation. | |||
<h3>Explanation</h3> | |||
In simpler terms, consider a client that depends upon a class that contains interfaces that our client does not use, but that other clients of that class do use. The problems that exist are as follows: | |||
<ol> | |||
<li>The class now has to provide a null or a valid implementation for all those extra interfaces that our client does not use. Any change to any part in this hierarchy needs a complete recompilation. </li> | |||
<li>Our client now will be affected by the changes that those other clients force upon the class.</li> | |||
</ol> | |||
We would like to avoid such couplings where possible, and so we want to separate the interfaces where possible. | We would like to avoid such couplings where possible, and so we want to separate the interfaces where possible. | ||
<h3>Example of a Violation</h3> | |||
We present an example given in [2]. Consider a security system with Door objects that can be locked and unlocked. | |||
Example of a Violation | <pre> | ||
Consider a | |||
class Door | class Door | ||
{ | { | ||
Line 61: | Line 75: | ||
virtual bool IsDoorOpen() = 0; | virtual bool IsDoorOpen() = 0; | ||
}; | }; | ||
We make this an abstract class and each type of door that we wish to create use this class and provides its own implementation of Door. | </pre> | ||
Consider a TimedDoor, which sounds an alarm when a when a door is left open for too long. For this, it communicates with a Timer object. | |||
We make this an abstract class and each type of door that we wish to create use this class and provides its own implementation of Door.Consider a TimedDoor, which sounds an alarm when a when a door is left open for too long. For this, it communicates with a Timer object. | |||
<pre> | |||
class Timer | class Timer | ||
{ | { | ||
Line 74: | Line 90: | ||
virtual void TimeOut() = 0; | virtual void TimeOut() = 0; | ||
}; | }; | ||
</pre> | |||
Thus, when a TimedDoor object wishes to be informed about a timeout, it calls the Register function of the Timer. We force Door, and therefore TimedDoor, to inherit from TimerClient. This ensures that TimerClient can register itself with the Timer and receive the TimeOut message. | Thus, when a TimedDoor object wishes to be informed about a timeout, it calls the Register function of the Timer. We force Door, and therefore TimedDoor, to inherit from TimerClient. This ensures that TimerClient can register itself with the Timer and receive the TimeOut message. | ||
The problem with this implementation is that Door class now depends on TimerClient. For Door types that dont need timing, we still need to provide nil implementations for Timeout method. Also all these derivatives need a #include for TimerClient, even though, they don't use it. Thus, our TimedDoor has forced a change in all the | The problem with this implementation is that Door class now depends on TimerClient. For Door types that dont need timing, we still need to provide nil implementations for Timeout method. Also all these derivatives need a #include for TimerClient, even though, they don't use it. Thus, our TimedDoor has forced a change in all the types of Doors that we have. | ||
We create an adapter object that derives from TimerClient and delegates to | <h3>Resolution</h3> | ||
the TimedDoor. That is, when the TimedDoor wants to register a timeout request with the Timer, it creates a | Possible ways to ensure that ISP is enforced is to use the Adapter Pattern. this makes sure that the interfaces of the class can be broken up into groups of member functions. Each group serves a different set of clients. Thus some clients use one group of member functions, and other clients use the other groups. The other solution is to use Multiple Inheritance. | ||
DoorTimerAdapter and registers it with the Timer. When the Timer sends the TimeOut | |||
message to the DoorTimerAdapter, the DoorTimerAdapter delegates the message back to | The problem is resolved using the ADAPTER pattern as follows: | ||
the TimedDoor. Thus DoorTimerAdapter | We create an adapter object that derives from TimerClient and delegates to the TimedDoor. That is, when the TimedDoor wants to register a timeout request with the Timer, it creates a DoorTimerAdapter and registers it with the Timer. When the Timer sends the TimeOut message to the DoorTimerAdapter, the DoorTimerAdapter delegates the message back to the TimedDoor. Thus DoorTimerAdapter TimedDoor does not have to have the exact same interface as TimerClient. The DoorTimerAdapter can translate the TimerClient interface into the TimedDoor interface. | ||
TimedDoor does not have to have the exact same interface | <pre> | ||
as TimerClient. The DoorTimerAdapter can | |||
translate | |||
the TimerClient interface into the | |||
TimedDoor interface. | |||
Object Form of Adapter Pattern | Object Form of Adapter Pattern | ||
class TimedDoor : public Door | class TimedDoor : public Door | ||
Line 109: | Line 121: | ||
TimedDoor& itsTimedDoor; | TimedDoor& itsTimedDoor; | ||
}; | }; | ||
</pre> | |||
<h2>Principle of Small Interfaces</h2> | |||
Statement | <h3>Statement</h3> | ||
possible | This rule was suggested by Bertrand Meyer as part of 5 other rules that encourage modularity. This rule states that | ||
Motivation | <pre> If two modules communicate, they should exchange as little information as possible</pre>[1] | ||
connections rather than to their number or their size. One would want to reduce the size of the | <h3>Motivation</h3> The Small Interfaces or “Weak Coupling” rule relates to the size of inter-module connections rather than to their number or their size. One would want to reduce the size of the inter-module connections to the very minimum, thus enabling them to communicate effectively exchanging the very least amount of information. | ||
Explanation | <h3>Explanation</h3> | ||
The rule for smaller interfaces is desirable in most engineering fields. For example, in the field of electrical sciences, one would paraphrase to say that the communication channels between any two modules. This requirement follows from the criteria of continuity and protection (see discussion of modular systems). Once the modules are tightly coupled with each other, they are | The rule for smaller interfaces is desirable in most engineering fields. For example, in the field of electrical sciences, one would paraphrase to say that the communication channels between any two modules[1]. This requirement follows from the criteria of continuity and protection (see discussion of modular systems). Once the modules are tightly coupled with each other, they are harder to change and harder to protect. | ||
This is because by having larger fields of communication, every module might increase the risk of misusing the common data | This is because by having larger fields of communication, every module might increase the risk of misusing the common data. | ||
Example of a Violation | <h3>Example of a Violation</h3> An example of violation is an established Fortran practice of using COMMON. | ||
A common block in Fortran shows up the following directive form: | A common block in Fortran shows up the following directive form[5]: | ||
<pre> | |||
COMMON /common_name/ variable1... variablen | COMMON /common_name/ variable1... variablen | ||
Example: | |||
>> COMMON block1 A[75], B[25] | |||
>> COMMON block1 C[50], D[50] | |||
</pre> | |||
This indicates that the variables listed are accessible not just to the enclosing module but also to any other module which includes a COMMON directive with the same common_name. | This indicates that the variables listed are accessible not just to the enclosing module but also to any other module which includes a COMMON directive with the same common_name. | ||
It is not infrequent to see Fortran systems whose every module includes an identical | It is not infrequent to see Fortran systems whose every module includes an identical | ||
Line 128: | Line 144: | ||
module may directly use every piece of data. This runs the risk of data misuse as discussed above. | module may directly use every piece of data. This runs the risk of data misuse as discussed above. | ||
Developers using nested structures also face similar problems. | Developers using nested structures also face similar problems. | ||
<h3>Resolution</h3> | |||
Block structure were introduced by Algol and retained in a more restricted form by | |||
Pascal. Every block has a well defined scope for the variables that were declared in that block. That is, every block may introduce its own variables, which are only meaningful within the syntactic scope of the block[1]. For example: | |||
<pre> | |||
local-- Beginning of block B1 | local-- Beginning of block B1 | ||
x, y: INTEGER | x, y: INTEGER | ||
Line 150: | Line 165: | ||
Instructions of B1 (continued) | Instructions of B1 (continued) | ||
end -- of block B1 | end -- of block B1 | ||
</pre> | |||
Variable x is accessible throughout the block, whereas the two variables called z (one BOOLEAN, the other INTEGER) have scopes limited to B2 and B3 respectively. Like x, variable y is declared at the level of B1, but its scope does not include B3, where another variable of the same name (and also of type INTEGER) locally takes precedence over the outermost y. | Variable x is accessible throughout the block, whereas the two variables called z (one BOOLEAN, the other INTEGER) have scopes limited to B2 and B3 respectively. Like x, variable y is declared at the level of B1, but its scope does not include B3, where another variable of the same name (and also of type INTEGER) locally takes precedence over the outermost y. | ||
Block | Block structure. However blocks may still violate the Small Interfaces rule. The architecture | ||
of object-oriented software will involve three levels: a system is a set of clusters; a cluster | of object-oriented software will involve three levels: a system is a set of clusters; a cluster | ||
is a set of classes; a class is a set of features (attributes and routines). | is a set of classes; a class is a set of features (attributes and routines). | ||
<h2> Comparison summary</h2> | |||
The following table summarizes the contrasts and similarities between the two principles. | |||
<table border = 1 > | |||
<tr><th width = 10%>Criteria</th> <th width = 45%>Interface Segregation Principle</th><th width = 45%> Principle of Small Interfaces</th></tr> | |||
<tr> | |||
<td> Statement</td> | |||
<td>Make fine grained interfaces that are client specific.</td><td> If two modules communicate, they should exchange as little information as | |||
possible. | |||
</td> | |||
</tr> | |||
<tr><td>History</td><td>Postulated by Bob martin in early 2000s, as part of SOLID framework, to aid in designing better structured Object Oriented Systems.</td><td>These were postulated by Bertrand Meyer, together with the rules for ideal modules. </td></tr> | |||
<tr><td>Scope</td><td>This is a class-design principle, largely dealing with the size of the interfaces - eliminating fat interfaces as part of better design. Thus this rule serves to promote coherence and cohesion of the classes involved</td> | |||
<td>This principle largely deals with the size of communication channel between modules, minimizing the data that is transferred between the different modules involved. Thus this promote loose coupling between the modules of a system</td></tr> | |||
<tr><td>How to Fix Violations</td><td> By using the Adapter Design Principle, basically breaking down interfaces along the lines of responsibilities and apply the SRP principle. </td> | |||
<td>Use of block structure instead of global variables largely reduces stress on unintended data manipulations between different modules. </td></tr> | |||
<tr><td>Benefits of Use</td><td>Better structured code that lends itself more freely to the benefits of using Object Oriented Programming. With the disappearance of fat clients and the use of the Adapter pattern, code is more flexible to extensibility.</td> | |||
<td>Conforming to this rule along with others listed in the section above results in the architecting truly modular code, exhibiting the different features of true modularity as defined by Meyer </td></tr> | |||
</table> | |||
<h2>Conclusion</h2> | |||
The principle of small interfaces and principle of interface separation subtly differ from each other in that one deals with the size of the interface and the other deals with the size of the communication channels between the interfaces. Therefore these are used in different abstraction levels. One would benefit by using the SOLID principles to design better structured OO software. Using the rules of modularity one would develop software that lends itself to better architect-ed software solutions. | |||
<h2> Appendix</h2> | |||
<ol><li>Adapter pattern: is a Design Pattern that translates an interface for a class into a compatible interface. It's main goal is decoupling of classes so as to make clients less dependent upon interfaces they do not use, which causes them to conform to the Interface Segregation Pattern. </li> | |||
<li> Object Oriented Design Patterns are design patterns used in languages such as Java and C++ to better exploit the advantages of object Oriented Design and associated principles and solve common problems associated with the same</li> | |||
<h2> References</h2> | |||
<ol> | |||
<li> Object Oriented Software Construction, Second Edition, Bertrand Mayer</li> | |||
<li> http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod </li> | |||
<li> http://www.oodesign.com/design-principles.html</li> | |||
<li> http://wikipedia.org</li> | |||
<li>http://www.cse.yorku.ca/course/3311/slides/14-Modularity.pdf</li> | |||
</ol> |
Latest revision as of 08:43, 19 November 2009
Interface Segregation Principle vs. Principle of Small Interfaces
Introduction
Software changes. To enable software to age gracefully, a lot of time is spent in designing an implementation that can be extended, modified and maintained with efficacy. In this article, we present two specific design rules that deal in different levels of abstractions in the design phase, the Interface Segregation Principle suggested by Bob Martin and the Principle of Small Interfaces, postulated by Bertrand Meyer. We present an explanation of both these principles and provide a comparison of the strategies.
Background
Object Oriented programming and Design patterns
The object oriented principles of programming and their associated design patterns have long been studied heavily by a computer programmer. Ever since object oriented programming replaced the functional type programming style of the early 70s, it has been used extensively to develop commonly used software. However, as the scope and the size of these projects grew, researchers studied ways to maintain the quality of the software written. They introduced patterns of programming within the Object paradigm to address some very common problems occurring while programming and maintaining a large project. This study of object oriented design programming patterns and related principles is a vast field, and its understanding is considered vital for developers.
Modularity
A modular system is one that is made of autonomous elements which are connected and inter-operate in a simple yet coherent structure. However to formally define modularity, one needs to look at the concept from different view points. We may define modularity along the following concepts, as defined in [1].
- Decompose-ability: A software development method is Decomposable if it helps in the task of decomposing functionality into a small number of less complex subproblems. These smaller units of functionality are then connected by a simple structure, allowing them to be independent from each other in that each of them is extensible without affecting the others.
- Composability:A software development method is Modular composable if it allows the production of software elements which may then be freely used as building blocks for newer systems. Each of these elements may be combined with each other to produce new systems,quite often in an environment very different from the ones in which they were initially developed.
- Understandability: A software development method should produce software, which is non trivial enough in that a human can understand the functionality and working of each module without having to learn about others or, at worst, by having to examine only a few of the others.
- Continuity: A software development method satisfies Modular Continuity if, in the software architectures that it yields, a small change in a problem specification will trigger a change of just one module, or a small number of modules.
- Protection: A method satisfies Modular Protection if it yields architectures in which the effect of an abnormal condition occurring at run time in a module will remain confined to that module, or at worst will only propagate to a few neighboring modules.
Rules for Modularity
From the preceding criteria, five rules follow which we must observe to ensure modularity in a given system. These rules were collectively postulated by Bertrand Meyer:
- Rule of Direct Mapping: The modular structure devised in the process of building a software system should remain compatible with any modular structure devised in the process of modeling the problem domain.
- Rule of Few Interfaces: Every module should communicate with as few others as possible. The effect of a change or of an error may propagate to a large number of modules as a result of too many relations between modules of an implementation. It
- Rule of Small interfaces (weak coupling) : If two modules communicate, they should exchange as little information as possible. This rule relates to the size of inter-module connections rather than to their number. This rule corresponds directly with our topic and will be dealt in more depth.
- Rule of Explicit Interfaces: When modules interact, we expect them to do so "loudly" or "in public", in full view of all the other modules of the system.
- Rule of Information Hiding: The designer of every module must select a subset of the module’s properties as the official information about the module, to be made available to authors of client modules.
SOLID Design Principles
The SOLID principles of Object Oriented Design are five "best practices" basic class design principles. These were first stated by Bob Martin and are outlined extensively on his website[2]. Collectively, they provide a set of guidelines that help design better object oriented systems. The SOLID principles are most concerned with dependency management, or the ability to keep code loosely coupled and flexible. The 5 principles are given below :
- SRP - The Single Responsibility Principle - A class should have one, and only one, reason to change. The SRP says a class should focus on doing one thing. This doesn't mean it should only have one method, but instead all the methods should relate to a single purpose. For example, an Invoice class might have the responsibility of calculating various amounts based on it's data. In that case it probably shouldn't know about how to retrieve this data from a database, or how to format an invoice for print or display.
- OCP - The Open Closed Principle - You should be able to extend a classes behavior, without modifying it. Change a class' behaviour using inheritance and composition. Bob Martin's initial paper on the OCP linked from The Principles of OOD attributes the idea to Bertrand Meyer, who wrote that classes should be "open for extension, but closed for modification"[2]. The idea is that we can use OO techniques like inheritance and composition to change (or extend) the behavior of a class, without modifying the class itself.
- LSP - The Liskov Substitution Principle - Derived classes must be substitutable for their base classes. Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it. Ensuring a base class works in any situation the parent does is really hard work, and whenever you use inheritance its a good idea to keep the LSP firmly in mind.
- ISP - The Interface Segregation Principle - Make fine grained interfaces that are client specific. This priciple deals with keeping interfaces small and limited only to a very specific need. A fat interface imposes a huge implementation burden on anyone that wants to stick to that contract
- DIP - The Dependency Inversion Principle - Depend on abstractions, not on concretions. This means that if a class has dependencies on other classes, it should rely on the dependencies' interfaces rather than their concrete types. This helps keep coupling low and makes our design easier to change.
The Interface Segregation Principle
Statement
The Interface Segregation Principle (ISP)[2] is one of the five SOLID Principle, first advocated by Bob Martin dealing with heavy interfaces. It states the following:
"Make fine grained interfaces that are client specific."
Motivation
This principle deals with the disadvantages of polluted interfaces -"fat" interfaces and unnecessary recompilation.
Explanation
In simpler terms, consider a client that depends upon a class that contains interfaces that our client does not use, but that other clients of that class do use. The problems that exist are as follows:
- The class now has to provide a null or a valid implementation for all those extra interfaces that our client does not use. Any change to any part in this hierarchy needs a complete recompilation.
- Our client now will be affected by the changes that those other clients force upon the class.
We would like to avoid such couplings where possible, and so we want to separate the interfaces where possible.
Example of a Violation
We present an example given in [2]. Consider a security system with Door objects that can be locked and unlocked.
class Door { public: virtual void Lock() = 0; virtual void Unlock() = 0; virtual bool IsDoorOpen() = 0; };
We make this an abstract class and each type of door that we wish to create use this class and provides its own implementation of Door.Consider a TimedDoor, which sounds an alarm when a when a door is left open for too long. For this, it communicates with a Timer object.
class Timer { public: void Regsiter(int timeout, TimerClient* client); }; class TimerClient { public: virtual void TimeOut() = 0; };
Thus, when a TimedDoor object wishes to be informed about a timeout, it calls the Register function of the Timer. We force Door, and therefore TimedDoor, to inherit from TimerClient. This ensures that TimerClient can register itself with the Timer and receive the TimeOut message.
The problem with this implementation is that Door class now depends on TimerClient. For Door types that dont need timing, we still need to provide nil implementations for Timeout method. Also all these derivatives need a #include for TimerClient, even though, they don't use it. Thus, our TimedDoor has forced a change in all the types of Doors that we have.
Resolution
Possible ways to ensure that ISP is enforced is to use the Adapter Pattern. this makes sure that the interfaces of the class can be broken up into groups of member functions. Each group serves a different set of clients. Thus some clients use one group of member functions, and other clients use the other groups. The other solution is to use Multiple Inheritance.
The problem is resolved using the ADAPTER pattern as follows: We create an adapter object that derives from TimerClient and delegates to the TimedDoor. That is, when the TimedDoor wants to register a timeout request with the Timer, it creates a DoorTimerAdapter and registers it with the Timer. When the Timer sends the TimeOut message to the DoorTimerAdapter, the DoorTimerAdapter delegates the message back to the TimedDoor. Thus DoorTimerAdapter TimedDoor does not have to have the exact same interface as TimerClient. The DoorTimerAdapter can translate the TimerClient interface into the TimedDoor interface.
Object Form of Adapter Pattern class TimedDoor : public Door { public: virtual void DoorTimeOut(int timeOutId); }; class DoorTimerAdapter : public TimerClient { public: DoorTimerAdapter(TimedDoor& theDoor) : itsTimedDoor(theDoor) {} virtual void TimeOut(int timeOutId) {itsTimedDoor.DoorTimeOut(timeOutId);} private: TimedDoor& itsTimedDoor; };
Principle of Small Interfaces
Statement
This rule was suggested by Bertrand Meyer as part of 5 other rules that encourage modularity. This rule states that
If two modules communicate, they should exchange as little information as possible
[1]
Motivation
The Small Interfaces or “Weak Coupling” rule relates to the size of inter-module connections rather than to their number or their size. One would want to reduce the size of the inter-module connections to the very minimum, thus enabling them to communicate effectively exchanging the very least amount of information.
Explanation
The rule for smaller interfaces is desirable in most engineering fields. For example, in the field of electrical sciences, one would paraphrase to say that the communication channels between any two modules[1]. This requirement follows from the criteria of continuity and protection (see discussion of modular systems). Once the modules are tightly coupled with each other, they are harder to change and harder to protect. This is because by having larger fields of communication, every module might increase the risk of misusing the common data.
Example of a Violation
An example of violation is an established Fortran practice of using COMMON.
A common block in Fortran shows up the following directive form[5]:
COMMON /common_name/ variable1... variablen Example: >> COMMON block1 A[75], B[25] >> COMMON block1 C[50], D[50]
This indicates that the variables listed are accessible not just to the enclosing module but also to any other module which includes a COMMON directive with the same common_name. It is not infrequent to see Fortran systems whose every module includes an identical gigantic COMMON directive, listing all significant variables and arrays so that every module may directly use every piece of data. This runs the risk of data misuse as discussed above. Developers using nested structures also face similar problems.
Resolution
Block structure were introduced by Algol and retained in a more restricted form by Pascal. Every block has a well defined scope for the variables that were declared in that block. That is, every block may introduce its own variables, which are only meaningful within the syntactic scope of the block[1]. For example:
local-- Beginning of block B1 x, y: INTEGER do Instructions of B1 local -- Beginning of block B2 z: BOOLEAN do Instructions of B2 end --- of block B2 local -- Beginning of block B3 y, z: INTEGER do Instructions of B3 end -- of block B3 Instructions of B1 (continued) end -- of block B1
Variable x is accessible throughout the block, whereas the two variables called z (one BOOLEAN, the other INTEGER) have scopes limited to B2 and B3 respectively. Like x, variable y is declared at the level of B1, but its scope does not include B3, where another variable of the same name (and also of type INTEGER) locally takes precedence over the outermost y. Block structure. However blocks may still violate the Small Interfaces rule. The architecture of object-oriented software will involve three levels: a system is a set of clusters; a cluster is a set of classes; a class is a set of features (attributes and routines).
Comparison summary
The following table summarizes the contrasts and similarities between the two principles.
Criteria | Interface Segregation Principle | Principle of Small Interfaces |
---|---|---|
Statement | Make fine grained interfaces that are client specific. | If two modules communicate, they should exchange as little information as
possible. |
History | Postulated by Bob martin in early 2000s, as part of SOLID framework, to aid in designing better structured Object Oriented Systems. | These were postulated by Bertrand Meyer, together with the rules for ideal modules. |
Scope | This is a class-design principle, largely dealing with the size of the interfaces - eliminating fat interfaces as part of better design. Thus this rule serves to promote coherence and cohesion of the classes involved | This principle largely deals with the size of communication channel between modules, minimizing the data that is transferred between the different modules involved. Thus this promote loose coupling between the modules of a system |
How to Fix Violations | By using the Adapter Design Principle, basically breaking down interfaces along the lines of responsibilities and apply the SRP principle. | Use of block structure instead of global variables largely reduces stress on unintended data manipulations between different modules. |
Benefits of Use | Better structured code that lends itself more freely to the benefits of using Object Oriented Programming. With the disappearance of fat clients and the use of the Adapter pattern, code is more flexible to extensibility. | Conforming to this rule along with others listed in the section above results in the architecting truly modular code, exhibiting the different features of true modularity as defined by Meyer |
Conclusion
The principle of small interfaces and principle of interface separation subtly differ from each other in that one deals with the size of the interface and the other deals with the size of the communication channels between the interfaces. Therefore these are used in different abstraction levels. One would benefit by using the SOLID principles to design better structured OO software. Using the rules of modularity one would develop software that lends itself to better architect-ed software solutions.
Appendix
- Adapter pattern: is a Design Pattern that translates an interface for a class into a compatible interface. It's main goal is decoupling of classes so as to make clients less dependent upon interfaces they do not use, which causes them to conform to the Interface Segregation Pattern.
- Object Oriented Design Patterns are design patterns used in languages such as Java and C++ to better exploit the advantages of object Oriented Design and associated principles and solve common problems associated with the same
- Object Oriented Software Construction, Second Edition, Bertrand Mayer
- http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
- http://www.oodesign.com/design-principles.html
- http://wikipedia.org
- http://www.cse.yorku.ca/course/3311/slides/14-Modularity.pdf