views:

74

answers:

1

Does this facade code look like a good idea, or is there anything inherently flawed about the design? More importantly, is there a problem I will likely run into down the road with this code? Any help is much appreciated.

I'm trying to build this so that I can have a Payment class as my facade, which accepts cc numbers, etc. and a PayPal class as my implementation so that I can charge the card and store the info regarding that, etc.

class MyFacadeClass(models.Model):
    account = models.ForeignKey('Account')  # Account omitted from example for brevity.
    implementation = CharField(max_length=255, choices=IMPL_CHOICES)  # IMPL_CHOICES omitted for brevity
    some_field = models.CharField(max_length=255)

    def __init__(self, *args, **kwargs):
        super(MyFacadeClass, self).__init__(*args, **kwargs)
        if self.implementation == 'PAYPAL':
            from somewhere import MyPayPalImplementationModelClass
            self.impl = MyPayPalImplementationModelClass(my_facade_instance=self, some_field=self.some_field, account=self.account)
            # Then MyPayPalImplementationModelClass does stuff with PayPal and has its own attributes such as ack, and datetime and fee_amount behind the scenes.

    def save(self, force_insert=False, force_update=False)
        if self.impl.is_valid():
            self.impl.save()
        super(MyFacadeClass, self).save(force_insert, force_update)
+2  A: 

It's not exactly clear what your "implementation" classes aim to achieve just by looking at the code above. It's unclear whether the implementation classes are yet another ORM model, or just a custom class which provides a save() method.

A couple of gotchas just from skimming what's above.

The line:

self.impl = MyImplementationClass(my_facade_class_instance=self, some_field=self.some_field, account=self.account)

comes before the call to super(), which means it is highly likely self.some_field and self.account are not properly initialized at the time you pass it to the other model.

The second gotcha comes in the fact that as written above, the two instances will (likely, depends on how MyImplementationClass is written) contain cyclic references to each other. This means that the reference count will not be 0 when the objects go out of scope. The cyclic GC will (probably) eventually garbage collect these objects, but you lose out on deterministic garbage collection, which (in my opinion) is a really powerful feature of Python.


It appears you are trying to implement what's known as a "generic relation", which is a feature already provided by django's contrib.contenttypes app: http://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#id1 If you just want an object which can refer to one of many model types, you can do so by using the generic relations from "contenttypes".

Crast
Can you clarify on the object reference count not going to 0, and how that will cause cyclic GC not doing it's job soon enough. Does that mean that if I have a reference in the facade's self.impl to the implementation model that the implementation model's mem will not be GC after I'm done using the facade?
orokusaki
The MyImplemenationClass is an ORM model.
orokusaki
It depends on what MyPaypalImplementation does with its first argument, the my_facade_instance argument. If you do something like `self.my_facade = my_facade_instance` inside `MyPaypalImplementation.__init__`, then when both objects go out of scope (without doing a manual cycle break) `.my_facade` will point to the instance of your facade, and the facade's `.impl` property will point back to the implementation. Both will then have a reference count of 1. This cycle will *eventually* be broken by the gc, but I'm not a fan of "eventually".
Crast
That's exactly what I'm going to do, so I can create a link to the facade class as payment=OneToOneField(Payment) (my real facade class is Payment), and then set self.payment=my_facade_instance
orokusaki
What would you do different in this case. Only thing I can think of is create a field called paypal_implementation=ForeignKey(PayPal, null=True) and then set the value to the instance I create in Facade.__init__(). That seems kind of backwards to me though, because later I'd have to create two_checkout_implementation=ForeignKey(TwoCheckout, null=True)
orokusaki
the the django.contrib.contenttypes registry should be able to register your various implementation models, and then all you need to do is use a generic foreign key. This means you don't need to manage the initializing or importing yourself, and let django do it for you. All you have to do is assign the property and voila. http://docs.djangoproject.com/en/dev/ref/contrib/contenttypes
Crast