OO principles – composition over inheritance

It's probable that the most obvious of those advantages is that the structure is easily understood. An Artisan instance will have an address property that is another object, and that object has its own relevant properties. At the Artisan level, where there is only one address of any importance, that might not seem significant. Other objects, however, such as Customer and Order, might have more than one associated address (billing and shipping addresses, for example), or even several: Customer might have several shipping addresses that need to be held on to and available.

As a system's object library becomes larger and more complex, using a purely inheritance based design approach will inevitably result in large trees of classes, many of which may do nothing more than provide functionality solely for the purpose of being inherited. A composition based design will reduce that complexity, probably significantly more so in larger and more complex libraries, since the functionality will be encapsulated in single classes, instances of which become properties themselves.

This sort of composition does have some potential drawbacks too, though: deeply nested objects, properties of properties of properties ad nauseam, can result in long chains of data structure. For example, if an order in the context of hms_sys has a customer that in turn has a shipping_address, finding the postal_code of that address from the Order would look something like order.customer.shipping_address.postal_code. That's not a terribly deep or complex path to get the data involved, and because the property names are easily understood it's not difficult to understand the entire path. At the same time, it's not hard to imagine this sort of nesting getting out of control, or relying on names that aren't as easily understood.

It's also possible (perhaps likely) that a need will arise for a class to provide a local implementation of some composed property class methods, which adds to the complexity of the parent object's class. By way of example, assume that the address class of the shipping_address just mentioned has a method that checks various shipping APIs and returns a list of them sorted from lowest to highest cost—call it find_best_shipping. If there is a requirement that the order objects be able to use that functionality, that will probably end up with a find_best_shipping method being defined at the order class level that calls the address-level method and returns the relevant data.

Neither of those are significant drawbacks, however. Provided that there is some discipline exercised in making sure that the design is logical and easily understood, with meaningful member names, they will probably be no worse than tedious.

From a more pure, object oriented standpoint, a more significant concern is the diamond problem. Consider the following code:

class Root:
    def method(self, arg, *args, **kwargs):
        print('Root.method(%s, %s, %s)' % (arg, str(args), kwargs))

class Left(Root):
    def method(self, arg, *args, **kwargs):
        print('Left.method(%s, %s, %s)' % (arg, str(args), kwargs))

class Right(Root):
    def method(self, arg, *args, **kwargs):
        print('Right.method(%s, %s, %s)' % (arg, str(args), kwargs))

class Bottom(Left, Right):
    pass

b = Bottom()

Diagrammed, these classes form a diamond shape, hence the diamond problem's name:

What happens upon the execution of the following:

b.method('arg', 'args1', 'args2', keyword='value')

Which method will be called? Unless the language itself defines how to resolve the ambiguity, the only thing that is probably safe to assume is that the method of Root will not be called, since both the Left and Right classes override it.

Python resolves ambiguities of this nature by using the order of inheritance specified in the class' definition as a Method Resolution Order (MRO). In this case, because Bottom is defined as inheriting from Left and Rightclass Bottom(Left, Right)—that is the order that will be used to determine which method of the several available will actually be executed:

# Outputs "Left.method(arg, ('args1', 'args2'), {'keyword': 'value'})"

Although it seems unlikely that any of the installable hms_sys components will ever reach a level of complexity where inheritance issues would be a significant concern, there is no guarantee that it will never happen. Given that, and that a refactoring effort to move from an inheritance based to a composition based structure would probably be both painful and prone to introducing breaking changes, a composition based approach, even with some of the drawbacks inherent to it, feels like a better design even at this point.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.62.105