1

I am trying to model an external system in Java and running in to some issues.
I have a handful of related types that I have mapped together through abstract (and sometimes concrete if it made sense) classes.

In a few cases some of the SubClasses turned out to be only implementing a caller method that just picked an external URL to use, while letting the ABC build the payload for it.

Sample below in Python (because I don't have Eclipse installed on this machine):

from abc import ABC,abstractmethod
class Parent(ABC):
    @abstractmethod
    def callExternal(self):
        ''' Each concrete child knows which external URL it needs to call '''
        pass

    def doSomething(self):
        ''' Represents the entry point 
        if this was Java, this would be the public method
        the rest would probably be protected '''
        urlOutput = self.callExternal()
        return ' '.join(['I am',urlOutput])

    def buildPayload(self):
        ''' Payloads for children are exactly the same
        In reality we are using builders to set members
        but the structure of the payload between children
        is the same '''
        return 'some payload'

class ChildOne(Parent):
    def callExternal(self):
        payload = super(ChildOne,self).buildPayload()
        apiOutput = 'ChildOne'#hit the api specific to child 1 here
        self.childOneSpecificMember = apiOutput
        return ' '.join([payload,apiOutput])

class ChildTwo(Parent):
    def callExternal(self):
        payload = super(ChildTwo,self).buildPayload()
        apiOutput = 'ChildTwo'#hit the api specific to child 2 here
        return ' '.join([payload, apiOutput])

if __name__ == '__main__':
    print(ChildOne().doSomething())
    print(ChildTwo().doSomething())

The consumer of the API will pick which concrete class it needs (usually through DI) and then call the public doSomething method to send data to the external API and get the output.
We are also using builders build the concrete instances, since a single concrete instance could model multiple logically different types of objects, and we only create a subclass if there is a real difference.

I am worried I am running in to a few design issues here. I think we sidestepped Liskov Substitution in the above example (though if the inheritance tree went another level down we would have to worry about it), but the child call to the parent class to generate a payload is worrying as well as the child essentially configuring the parent by calling a specific URL.

2
  • Is deciding the external URL a "behavior", with some complex decision making, or is it just returning a "value", e.g. a String "mysite.com/api/users"? If it is just a value, you probably shouldn't sub class.
    – user949300
    May 29, 2018 at 1:08
  • In a few small cases, in addition to calling the api (which could be abstracted to setting a String) we are using the output to set child specific members.
    – Chris
    May 29, 2018 at 16:17

2 Answers 2

2

Here the problem is that building the "payload" is the same for every use case, but interacting with the external API is what changes.

Encapsulate the thing that changes.

You don't need sub classes, though you don't need fewer classes. Composition is your friend here. What you are calling sub classes should all be concrete classes without an explicit parent, They each should adhere to the same public interface. Then the Parent class just needs the concrete API class as a constructor argument (first time writing Python, so it might be a little wonky):

class Parent:
    apiClient = None

    def __init__(self, apiClient)
        self.apiClient = apiClient

    def doSomething(self):
        ''' Represents the entry point 
        if this was Java, this would be the public method
        the rest would probably be protected '''
        urlOutput = self.apiClient.call(self.buildPayload())
        return ' '.join(['I am',urlOutput])

    def buildPayload(self):
        ''' Payloads for children are exactly the same
        In reality we are using builders to set members
        but the structure of the payload between children
        is the same '''
        return 'some payload'

class ApiClientOne:
    def call(self, payload):
        apiOutput = 'ChildOne'#hit the api specific to child 1 here
        self.childOneSpecificMember = apiOutput
        return ' '.join([payload,apiOutput])

class ApiClientTwo:
    def call(self, payload):
        apiOutput = 'ChildTwo'#hit the api specific to child 2 here
        self.childOneSpecificMember = apiOutput
        return ' '.join([payload,apiOutput])
3
  • I would also tend towards this design. (1) In Python, It wouldn't even be necessary to create the API client classes, as free functions can be passed around. However, this code is a stand-in for Java, where we'd have to use classes and define an ApiClient interface instead (basically, a Strategy Pattern). (2) It is unclear how those child specific members should be treated. If they are not accessed outside of the API clients, then things get difficult.
    – amon
    May 30, 2018 at 6:47
  • This looks great! One small question though, how would I handle the self.childOneSpecificMember if in each child class (in the original) it has different extended members? So for instance if Parent represents a Vehicle, maybe a Child represents a Truck and I need to set the contents of the bed. I could turn Parent in to a Role like you are doing above and have each child implement it, but how would I reuse the code for common attributes like wheels?
    – Chris
    May 30, 2018 at 12:44
  • 1
    @Chris: At this point you are coupling your data access logic to the entity. They should be separated, due to the question you just asked in your comment. May 30, 2018 at 13:23
0

You don't provide enough information to determine if you are violating anything. Liskov's Substitution Principle does not address construction of objects, only the ability to substitute one for another. Also, an abstract base class is also not addressed, since there can be no instance of the base class that has to maintain the contract.

If you have a factory that constructs the right class in the right manner, there is little indication that you are violating anything.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.