Search code examples
pythonoopsingle-responsibility-principle

python 3: how do I create two different classes that operate on the same data?


I have a class that looks something like the following:

# Class violates the Single Responsibility Principle
class Baz:
    data = [42]
    def do_foo_to_data(self):
        # call a dozen functions that do complicated stuff to data

    def do_bar_to_data(self):
        # call other functions that do different stuff to data

I want to break it into two separate classes because it violates the SRP. The functions called by do_foo_to_data() are completely distinct from those called by do_bar_to_data(). Yet they must operate on the same data.

I've come up with a bunch of solutions, but they're all ugly. Is there a way to do this cleanly, preferably in Python 3 (though 2.7 is OK too)?

The best of my "solutions" is below:

# I find this hard to read and understand
class Baz:
    data = [42]

    def create_foo(self):
        return Baz.Foo()

    def create_bar(self):
        return Baz.Bar()


    class Foo:
        def do_foo_to_data(self):
            # call foo functions


    class Bar:
        def do_bar_to_data(self):
            # call bar functions

Note: It's not essential to me that the data member be a class member. I only expect to create one instance of Baz; but I didn't want to ask two questions in one post and start a discussion about singletons.


Solution

  • This is not an elegant solution. You better pass a reference to the object you want them to operate on. So something like:

    class Foo:
    
        def __init__(self,data):
            self.data = data
    
        def do_foo_to_data(self):
            #...
            self.data[0] = 14
            pass
    
    class Bar:
    
        def __init__(self,data):
            self.data = data
    
        def do_bar_to_data(self):
            #...
            self.data.append(15)
            pass

    (I added sample manipulations like self.data[0] = 14 and self.data.append(15))

    And now you construct the data. For instance:

    data = [42]
    

    Next you construct a Foo and a Bar and pass a reference to data like:

    foo = Foo(data)
    bar = Bar(data)
    

    __init__ is what most programming languages call the constructor and as you have seen in the first fragment, it requires an additional parameter data (in this case it is a reference to our constructed data).

    and then you can for instance call:

    foo.do_foo_to_data()
    

    which will set data to [14] and

    bar.do_bar_to_data()
    

    which will result in data being equal to [14,15].

    Mind that you cannot state self.data = ['a','new','list'] or something equivalent in do_foo_to_data or do_bar_to_data because this would change the reference to a new object. Instead you could for instance .clear() the list, and append new elements to it like:

    def do_foo_to_data(self): #alternative version
        #...
        self.data.clear()
        self.data.append('a')
        self.data.append('new')
        self.data.append('list')
    

    Finally to answer your remark:

    preferably in Python 3 (though 2.7 is OK too)?

    The technique demonstrated is almost universal (meaning it is available in nearly every programming language). So this will work in both and .