4

In an effort to code in a more python and OOP-style way, I wonder if anyone could advise me on implementing this concept please.

Let's say I have a base class for fruit, say apple and banana, which contains basic properties for the class, e.g. color. Then I want to class which inherits from fruit called juice, which adds methods and properties, e.g. volume, sugar.

A rough structure might be:

class Fruit(object):
def __init__(self, fruit_name):
    if fruit_name == 'apple': 
        self.color = 'green'
        self.sugar_content = 2
    elif fruit_name == 'banana':
        self.color = 'yellow'
        self.sugar_content = 3

Then I inherit the methods of my Fruit classes:

class Juice(Fruit):
    def __init___(self, fruit_name, volume, additives):
        PERHAPS I NEED TO USE A SUPER STATEMENT HERE TO PASS THE fruit_name parameter (which is 'apple' or 'banana' BACK TO THE FRUIT CLASS? I want this class to be able to access sugar_content and color of the the fruit class.
        self.volume = volume
        self.additives = additives

    def calories(self, sugar_added):
        return sugar_added + 2*self.sugar_content* self.volume # i.e. some arbitrary function using the class parameters of both this juice class and the original fruit class

So ultimately, I can create an object like:

my_juice = Juice(fruit_name='apple', volume=200, additives='sugar,salt')
print 'My juice contains %f calories' % self.calories(sugar_added=5)
print 'The color of my juice, based on the fruit color is %s' % self.color

ALTERNATIVELY, I wonder if it is better NOT to inherit at all and simply call the fruit class from a Juice class. e.g.

class Juice(object):
    def __init__(self, fruit_name, volume, additives):
        self.fruit = Fruit(fruit_name=fruit_name)
        self.volume = volume # ASIDE: is there an easier way to inherit all parameters from a init call and make them into class variables of the same name and accessible by self.variable_name calls?
        self.additives = additives

    def calories(self, sugar_added):
        return sugar_added + 2*self.fruit.sugar_content* self.volume

In some ways, this above feels more natural as self.Fruit.sugar_content directly indicates the sugar_content is a property of the fruit. Whereas if I inherited, then I'd use self.sugar_content, which is a property of the fruit although could be confused with the sugar content of the juice class which is dependent on other factors.

OR, would it better still to have a separate class for each fruit, and put the logic statement to evaluate the fruit_name string passed to the Juice class witin the Juice class init and then use e.g.:

class Apple(object):
   self.color = 'green'

class Banana(object):
    self.color = 'yellow'

class Juice(object):
    def __init__(self, fruit_name, other params):
         if fruit_name == 'apple':
             self.Fruit = Apple
             self.sugar_content=2
         elif fruit_name == 'banana':
             self.Fruit = Banana
             self.sugar_content=3
         # Or some easier way to simply say
             self.Fruit = the class which has the same name as the string parameter fruit_name

I appreciate all of the above would work in theory, although I'm looking for suggestions to develop an efficient coding style. In practice, I want to apply this to a more complex project not involving fruit, although the example encompasses many of the issues I'm facing.

All suggestions / tips / suggest reading links welcomed. Thanks.

2

3 Answers 3

4

I think your first option is closest to a good idea, but here's an improvement:

class Fruit:

    def __init__(self, name, sugar_content):
        self.name = name
        self.sugar_content = sugar_content

Now the logic for determining how much sugar each Fruit contains is moved outside the class definition entirely. Where can it go? One option would be to sub-class:

class Apple(Fruit):

    def __init__(self):
        super().__init__("apple", 2)

Alternatively, if the only differences between the Fruits are name and sugar content, just create instances and feed in the data (e.g. from a file):

apple = Fruit("apple", 2)

Now how about the Juice. This is composed of Fruit, but isn't itself actually Fruit, so inheritance isn't a good fit. Try:

class Juice:

    def __init__(self, fruits):
        self.fruits = fruits

    @property
    def sugar_content(self):
        return sum(fruit.sugar_content for fruit in self.fruits)

You can pass an iterable of Fruit instances to make a Juice:

>>> mixed_fruit = Juice([Fruit("apple", 2), Fruit("banana", 3)])
>>> mixed_fruit.sugar_content
5

You can then extend this to encompass ideas like:

  • How much of each Fruit goes into the Juice;
  • What the volume of the Juice is (another @property calculated from the ingredients?); and
  • What additives (water, sugar, etc.) are present.

It would be sensible to work in consistent proportional units, e.g. sugar_content in grams of sugar per gram of Fruit (i.e. percentage).

Sign up to request clarification or add additional context in comments.

Comments

3

Another way to think about this is with three entities Fruit, Juice, and Juicer. The Juicer is responsible for taking a certain Fruit and creating a Juice of the corresponding type Orange => OrangeJuice. Then you could have two hierarchies of classes with Orange inheriting from Fruit and OrangeJuice inheriting from Juice with Juicer essentially mapping between them.

This gets you around a lot of tricky problems that your proposed solution would generate. For instance, if I had a class called Slicer that consumed some Fruit produced a FruitSalad(Fruit[] => FruitSalad). In your proposed abstraction I could give Slicer an OrangeJuice instance (which you propose as a type of Fruit) and expect it to spit out a delicious FruitSalad but instead all I get is a soggy counter top.

2 Comments

I think juicer and slicer should probably be functions, rather than classes, (or methods of a FoodProcessor!) but I like the idea!
I think I proposed these as a subversive attempt to show how a functional language with a type system would approach this type of problem. So Juicer and Slicer are really just A=>B morphisms mapping between the type sets. You can def get away with making these functions in Python. But nothing stops you from applying the OOP model strictly and getting the same result.
2

Conceptually Juice should not inherit from Fruit but for the sake of your question let's suppose it makes sense to do that. In your first code sample you must call

super(Juice,self).__init__(fruit,name)

In Python 3 that would be

super().__init__(fruit,name)

Now to your second question. Composition is the way yo go here. Juice is composed by Fruits but it is not his place to create them

class Juice(object):
    def __init__(self, fruit, volume, additives):
        self.fruit = fruit
        self.volume = volume
        self.additives = additives

This way you can:

apple = Apple(...)
apple_juice= Juice(apple,2,None)

And I think that answers your third question too.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.