TL;DR
This phrase would help remind one not to apply SRP prematurely:
A good separation of responsibilities is done only when the full picture of how the application should work is well understand.
Source: http://www.oodesign.com/single-responsibility-principle.html
API Requirements Phase
These are given as examples. Cross out ones that are not required for your application.
The rest of the answer assumes all are required. If your requirements are different, you will have to re-apply the principles in order to fit your unique requirements.
- As a library user, given that I have an instance of
Edge
, I should be able to query the two Node
s connected by the edge. This should take O(1)
time.
- As a library user, given that I have an instance of
Node
, I should be able to query the list of Edge
s that connect to the node. This should take O(num_edges_of_node)
time, the number of edges that are connected to that one node.
- As a library user, given that I have an instance of
Node
and an instance of Edge
, I should be able to check whether the Edge
and the Node
are in direct contact. This should take O(1)
time.
- Add any requirements as needed.
School of thoughts: between Naive-OO versus Library-Design-OO
In Naive OO design, a method is put on the object wherever the user wants to use that functionality. Examples:
Node[] theTwoNodes = someEdge.getNodes()
(returned array size must be 2)
Edge[] allConnectedEdges = someNode.getEdges()
(returned array size can be 0 or above; unbounded.)
bool isThisConnectedToThat = thisNode.isConnectedTo(thatEdge)
bool isThatConnectedToThis = thatEdge.isConnectedTo(thisNode)
Implement the first one, the second one, or both.
When implementing the methods in a Naive OO design, one is constrained by Information Expert - refer to Amy Blankenship's answer.
- Namely, the API Requirements says the user would like to do this, but to implement the required functionality, the instance will have to ask another instance for information.
- "Information Expert" is a rule-of-thumb that you can write less code if you associate the method with that other instance - the instance that has the most information ready to serve that functionality.
- In your sample code, the location of the Information Expert depends on your choice of data structure and algorithms.
Typically, it is either centralized in the Graph
object, or is distributed among a large number of Node
and Edge
instances.
Nevertheless, nothing in my answer would be invalidated even if the Information Expert is located differently.
However, in Pythonic-OO, the library writer has to remove one of the two isConnectedTo
methods, because of this:
There should be one --- and preferably only one --- obvious way to do it.
Source: http://c2.com/cgi/wiki?PythonPhilosophy
Finally, in Library-design-OO (which is the school of thought that permeates SOLID), the library writer will:
- Give convenience to the user, i.e. implement all of the methods identified in the Naive-OO stage;
- But internally, if one method can be implemented in terms of another method, then the first method will simply call (i.e. "delegate to") the other method to do the task.
- If Naive-OO puts a method into a class that is not the Information Expert, in Library-design-OO the method will simply call some other methods on the relevant Information Experts to do the task.
- Suppress your Pythonic thoughts. Pythonic thoughts make the code writer's life easier. As a library writer, you take care of all of the seemingly duplicated implementation work so as to make your library's users' lives easier.
"You haven't answered my question: does it violate SRP?"
Answer: It is not a conclusive violation if you, as a library writer, hasn't released your code to your users.
Four of the five guidelines in SOLID, namely excepting Liskov (LSP), are pre-release design checklist items for software libraries. (LSP is a correctness requirement that is basically logic (related to property (in philosophy)), and is inescapable unless you develop illogical software.)
You, as a library writer, are allowed to keep refactoring (www.refactoring.com) your code as you continuously improve its design and implementation.
Of course you are allowed to change your code, including the objects, interfaces, methods, and implementations. That is true until you release your library, and your library acquires its users. Once there are library users, the SOLID principles help you avoid breaking those library users' code.
Therefore, given that you are still in the library design stage of the process, asking whether it is a SRP violation can only give you an inconclusive answer.
Of course we should pay attention to whether some prototype code will lapse into a violation when it is released. However, at the design stage, the very tongue-in-cheek phrase often associated with SRP, namely,
A class should have only one reason to change.
is unfortunately irrelevant.
Source (same as above): http://www.oodesign.com/single-responsibility-principle.html
To fix that, one could say this instead:
To satisfy SRP, a library class should be designed to have as few reasons to change as possible, after the library has been released.
"Can you answer the second part of my question: Why?"
Please scroll up to the top and re-read every part of my answer.