1

Given the following structure where I want a method with a common name for each class, but each derived class needs an additional piece of information in order to form the appropriate result. Descending the inheritance tree accumulates additional needed arguments, but always needs all parents arguments as well.


class A:
    @classmethod
    def foo(cls, a):
      return a

class B(A):
    @classmethod
    def foo(cls, a, b):
      return '.'.join([super().foo(), b])

class C(B):
    @classmethod
    def foo(cls, a, b, c):
      return '.'.join([super().foo(), c])

>>A.foo('a')
'a'
>>B.foo('a','b')
'a.b'
>>C.foo('a','b','c')
'a.b.c'

Is this a frowned upon structure since the LSP does not hold? Is there a different overall design approach that would allow a common function name to account for additional arguments like this in some way?

In this example I am using class methods, but same question could be asked for normal instance methods as well.

Updated Example:

In my case I have a messaging application with some message classes defined in an inheritance hierarchy. Each message type has a specific topic address that needs zero or more runtime parameters such as the node or workflow name. In addition each message has its own set of instance data field attributes.


class BaseMessage:
    sending_process: str

    @classmethod
    def topic(cls):
      return 'message'

class WorkflowMessage(BaseMessage):

    @classmethod
    def topic(cls, node, workflow_name):
      return '.'.join([super().topic(), 'workflow', node, workflow_name])

@dataclass
class WorkerServiceMessage(WorkflowMessage):
    dataA: str
    dataB: str

    @classmethod
    def topic(cls, node, workflow_name, worker_name):
      return '.'.join([super(node, workflow_name).topic(), worker_name])

class Worker:
    def __init__(node, workflow, name):
        self._node = node
        self._workflow = workflow
        self._name = name
        self.messenger = Messenger()
        self.messager.listen(topic=WorkflowMessage.topic(self._node, self._workflow),
                        callback=self._on_msg)

    def _on_msg(self, msg):
        self.messenger.publish(topic=WorkerServiceMessage.topic(self._node, self._workflow, self._name), 
                          msg=WorkerServiceMessage(dataA='abc', dataB='123')


worker=Worker('mynode', 'workflowA', 'worker1')
worker.messenger.run()

  • Do you have a less contrived example we could talk about? – user214290 Nov 22 '19 at 17:57
  • 1
    I updated with a more concrete example. – majorpain1588 Nov 22 '19 at 18:32
1

I like @Gloweye's point that overridden methods should be performing the same task. But have you considered not making this behaviour be directly part of the subclass at all?

Since this question was tagged with I'll use that set of terms and suggest that you use some dependency inversion: Instead of having the topic be returned from a method, you could pass in either an object with a topic method, which has all the data needed to make the topic as members, or you could pass in a closure that closes over the relevant data which you can call to get the topic as needed. (Note that these two options are nearly functionally identical. This is the "objects are a poor man's closures and vice versa" thing.)

Alternately, if you would like something simpler, and the topic never changes after initial object construction, you might consider just generating it once and making it a simple string constructor parameter.

0

I think you're asking the wrong question about that code. Lets consider the concepts of subclassing. Lets say we have:

class Fruit:
 pass

class Banana(Fruit):
 pass

This is an extremely simple and obvious example of subclassing. It's literally a sub-classification. All banana's are also fruits.

This is traditionally what subclassing is.

Of course, classes have methods. Lets add some:

class Fruit:
 def take_bite(bitesize):
  """Eats part of the fruit."""
  self.size -= bitesize

class Banana(Fruit):
 def take_bite(bitesize):
  """Eats part of the fruit."""
  # banana circumference doesn't change.
  self.length -= bitesize
 pass

We override the method in our subclass. That's simple. However, the method still does the same thing - it lets us eat a bit of the fruit. And for a banana, that works different then for a generic fruit.

And that's the important question you should be asking.

TL:DR;

  1. Is the method performing the same task? Then yes, you should override the method. If it has different arguments, so be it.
  2. Is the method performing a different task? Then no, pick another name.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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