Reputation: 563
I can't figure out the correct way to model this problem. Here I give you a minimalistic version of my code:
# -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod
class AreaCalculator():
__metaclass__ = ABCMeta
def __init__(self):
pass
@abstractmethod
def getArea(self):
pass
def compute(self):
self.getArea()
class PerimeterCalculator():
__metaclass__ = ABCMeta
def __init__(self):
pass
@abstractmethod
def getPerimeter(self):
pass
def compute(self):
self.getPerimeter()
class TriangleAreaCalculator(AreaCalculator):
def __init__(self):
AreaCalculator.__init__(self)
def getArea(self):
return area
class TrianglePerimeterCalculator(PerimeterCalculator):
def __init__(self):
PerimeterCalculator.__init__(self)
def getPerimeter(self):
return perimeter
a = TriangleAreaCalculator()
b = TrianglePerimeterCalculator()
Is there an elegant way to merge "TrianglePerimeterCalculator" and "TriangleAreaCalculator" classes into one, but keeping "PerimeterCalculator" and "AreaCalculator" separated?
[edit] As Kyle suggested in the comments, I can create a new class (let's call it "Triangle") that inherits from "PerimeterCalculator" and "AreaCalculator" at the same time, but what I want is to be able to tell a new instance of "Triangle" to behave as "PerimeterCalculator" or "AreaCalculator", but not both at the same time.
Upvotes: 1
Views: 1499
Reputation: 123521
Here's another answer, following the editing and clarification of your question. It allows creation of a single Triangle
instance that can behave like either an AreaCalculator
or PerimeterCalculator
, as needed.
This programming pattern is called "delegation" and is used where the responsibility for implementing a particular operation is handed off to a different object—in this case an internally held instance of some other class. A common way to do this in Python is by overriding the class's default __getattr__()
method.
Since you've never responded to the comment under my other answer about exactly what it is that controls which behavior is used, I added a set_behavior()
method to allow it to be specified explicitly.
from abc import ABCMeta, abstractmethod
class AreaCalculator:
__metaclass__ = ABCMeta
def __init__(self):
pass
@abstractmethod
def getArea(self):
pass
def compute(self):
return self.getArea()
class PerimeterCalculator:
__metaclass__ = ABCMeta
def __init__(self):
pass
@abstractmethod
def getPerimeter(self):
pass
def compute(self):
return self.getPerimeter()
class TriangleAreaCalculator(AreaCalculator):
def __init__(self):
super(TriangleAreaCalculator, self).__init__()
def getArea(self):
print('TriangleAreaCalculator.getArea() called')
area = 13
return area
class TrianglePerimeterCalculator(PerimeterCalculator):
def __init__(self):
super(TrianglePerimeterCalculator, self).__init__()
def getPerimeter(self):
print('TrianglePerimeterCalculator.getPerimeter() called')
perimeter = 42
return perimeter
class Triangle:
def __init__(self):
delegate_classes = TriangleAreaCalculator, TrianglePerimeterCalculator
# Map delegate classes to instances of themselves.
self._delegates = {delegate_class: delegate_class()
for delegate_class in delegate_classes}
self.set_behavior(TriangleAreaCalculator) # Set default delegate.
def __getattr__(self, attrname):
# Called only for attributes not defined by this class (or its bases).
# Retrieve attribute from current behavior delegate class instance.
return getattr(self._behavior, attrname)
def set_behavior(self, delegate_class):
try:
self._behavior = self._delegates[delegate_class]
except KeyError:
raise TypeError("{} isn't a valid {} behavior delegate class"
.format(delegate_class, self.__class__.__name__))
if __name__ == '__main__':
triangle = Triangle()
# Uses instance's default behavior.
print('triangle.compute() -> {}'.format(triangle.compute()))
triangle.set_behavior(TrianglePerimeterCalculator) # Change behavior.
print('triangle.compute() -> {}'.format(triangle.compute()))
Output:
TriangleAreaCalculator.getArea() called
triangle.compute() -> 13
TrianglePerimeterCalculator.getPerimeter() called
triangle.compute() -> 42
Upvotes: 1
Reputation: 563
I figured it out myself, with inspiration on the commentas/answers of Kyle and martineau.
I can create a merged class "Triangle" as follows:
class Triangle():
def __init__(self):
pass
def getTriangleArea(self):
print 'Triangle area'
def getTrianglePerimeter(self):
print 'Triangle perimeter'
And then modify TriangleAreaCalculator and TrianglePerimeterCalculator as follows:
class TriangleAreaCalculator(AreaCalculator, Triangle):
def __init__(self):
TriangleCalculator.__init__(self)
AreaCalculator.__init__(self)
def getArea(self):
super(TriangleAreaCalculator, self).getTriangleArea()
class TrianglePerimeterCalculator(PerimeterCalculator, Triangle):
def __init__(self):
TriangleCalculator.__init__(self)
PerimeterCalculator.__init__(self)
def getPerimeter(self):
super(TrianglePerimeterCalculator, self).getTrianglePerimeter()
This way, I can create a new Triangle-like instance that behaves as "PerimeterCalculator" or "AreaCalculator" (but not both at the same time):
a = TriangleAreaCalculator()
b = TrianglePerimeterCalculator()
a.compute() # correctly prints "Triangle area"
b.compute() # correctly prints "Triangle perimeter"
Upvotes: 0
Reputation: 123521
I think the "design pattern" you should use is multiple inheritance. Below is a modified version of your code demonstrating how do it (plus a few other changes to make it actually runnable and all classes new-style).
from abc import ABCMeta, abstractmethod
class AreaCalculator(object):
__metaclass__ = ABCMeta
def __init__(self):
pass
@abstractmethod
def getArea(self):
pass
def compute(self):
self.getArea()
class PerimeterCalculator(object):
__metaclass__ = ABCMeta
def __init__(self):
pass
@abstractmethod
def getPerimeter(self):
pass
def compute(self):
self.getPerimeter()
class TriangleAreaCalculator(AreaCalculator):
def __init__(self):
super(TriangleAreaCalculator, self).__init__()
def getArea(self):
print('TriangleAreaCalculator.getArea() called on instance of {}'.format(
self.__class__.__name__))
# return area
return 13
class TrianglePerimeterCalculator(PerimeterCalculator):
def __init__(self):
super(TrianglePerimeterCalculator, self).__init__()
def getPerimeter(self):
print('TrianglePerimeterCalculator.getPerimeter() called on instance of {}'.format(
self.__class__.__name__))
# return perimeter
return 42
class MergedCalculator(TriangleAreaCalculator, TrianglePerimeterCalculator):
def __init__(self):
super(MergedCalculator, self).__init__()
merged = MergedCalculator()
print('merged.getArea() -> {}'.format(merged.getArea()))
print('merged.getPerimeter() -> {}'.format(merged.getPerimeter()))
Output:
TriangleAreaCalculator.getArea() called on instance of MergedCalculator
merged.getArea() -> 13
TrianglePerimeterCalculator.getPerimeter() called on instance of MergedCalculator
merged.getPerimeter() -> 42
Upvotes: 2