Search code examples
pythondesign-patternssingle-responsibility-principle

Single Responsibility Principle when method can accept various types of data?


Best practice recommends a method or function should do one thing, and do it well, so how can I apply SRP in the following scenario:

Summary: I have an API wrapper which sends a HTTP Post request, however in order to provide the json I want to allow the user multiple options, Lets say my function can accept any of the following:

def function_for_srp(jsonable_data: Union[Entity, Domain, str]):
    # Pseudo code (Violation of SRP?)
    if jsonable_data is instance of entity or domain:
        jsonable_data = json.dumps(jsonable_data) 
    else do nothing, as its a json encoded string of data already
    some_api_wrapper.post_data(jsonable_data) 

This function is doing multiple things depending on the data type passed to it, so its in violation of SRP? How do I overcome this design problem in a clean manner, ideally I am thinking something like this:

def function_for_srp_using_entity(entity: Entity): pass
def function_for_srp_using_domain(domain: Domain): pass
def function_for_srp(json_encoded_data: str): pass

Is the above 'pythonic' ? Is there a better way to do it?

# possible alternative?
def function_for_srp(jsonable_data: Union[Entity, Domain, str]):
    json = some_other_function(jsonable_data)
    some_api_wrapper.post_something(json)
    # Is this still a violation?

def some_other_function(jsonable_data: Union[Entity, Domain, str]):
    # Figure out the type and return a json encoded string that is suitable
    if isinstance of entity/domain, json dump and return
    else check if is valid json encoded string, if not make it valid and return it

Solution

  • SRP is definetely a great principle to follow but in practice you need to know when to draw the line otherwise you'll add too much complexity to your code.

    In your particular case, I'd start from what's better for your user and decide if you keep this

    def function_for_srp(jsonable_data: Union[Entity, Domain, str]):
    

    or

    def function_for_srp_using_entity(entity: Entity): pass
    def function_for_srp_using_domain(domain: Domain): pass
    

    Second option is clear, you're not violating any principle :)

    If you'd like to keep the first option you can do (also pseudo-code):

    def function_for_srp(jsonable_data: Union[Entity, Domain, str]):
    jsonableData = jsonable_data.getjson(); 
    some_api_wrapper.post_data(jsonable_data) 
    

    You can implement the getJson however you wish (either as a function on Entity and Domain either as a separate function outside. This would give you cleaner unit tests, but in your particular case you can decide to keep it as is instead of over-engineering it. KISS is also a good principle to follow