newbie question: help with polymorphism and/or programming style

Steve Holden sholden at holdenweb.com
Mon Oct 1 17:46:41 CEST 2001


<adina_levin at mindspring.com> wrote in message
news:9p7o8l$4q5$1 at slb2.atl.mindspring.net...
> Hello, Pythonites.
>
> I am teaching myself python programming, and have created a toy storefront
> program.
>
> If the customer wants to pay with cash, the program should return change.
If
> the customer wants to pay using a credit card, the program should validate
> the customer's ability to pay, and return an approval.
>
> Several questions regarding programming style:
>
> a) Is it desirable to replace the "if" statement in paymenthandler with
> subclasses, PayCash(Payment) and PayCredit(Payment).  If so, doesn't that
> just push the "if" statement upstream, where you PayCash() if the customer
> says she wants to pay cash?  I am clearly missing something!! :-(
>
The customer shouldn't need to say whether she will pay cash! See remarks
below...

> b) Is it bad style to return different kinds of information, depending on
> whether the customer pays with cash or credit? If so, how should this be
> handled cleanly?
>
It is difficult to see how you are planning to use the return values without
the calling code, so this is a tricky one. Generally speaking, however, yes,
it's bad style to have to decode the return type of a function or method to
decide what happened inside the function or method. Frankly I'm having
difficulty deciding why payment *is* an object; more context would help.

> c) Any other programming style problems?
>
> This is a toy, so no reason to comment on lack of full detail in the
> simulation.

Except that that context it would provide would enable fuller answers, but I
doubt anyone will try to pick nits about that. We're here to help!

>
> class Payment:
>     def __init__(self):
>         self.payobject=Customer().getpaymentinput()
>
Will a newly-created Customer object do here, or do you later plan to
repalce it with a customer object passed as an argument to __init__()? Or is
Customer not a class at all? The context doesn't make it terribly clear. It
seems that when you create a Payment, it creates (or locates) a Customer,
and sets its own payobject to the result of the cutomer's getpaymentinput()
method. Is this correct?

>     def payhandler(self,sum):
>         self.sum=sum
>         if self.payobject.paytype == "cash":
>             result=self.payobject.cashamount-self.sum
>             print result
>
Is there a particular reason why you print here but not for credit cutomers?

>         elif self.payobject.paytype == "credit":
>             self.crednum=self.payobject.crednum
>             result=Creditcheck().creditapprove(self)
>
>         return result
>
>
So you create a Payment, and then you call the Payemnt's payhandler() method
with the amount of the payment? This seems a little odd, but perhaps makes
sense in the larger scheme of the object model you have designed.

The main problem is that your code doesn't use "polymorphism", one of the
principal principles (;-) of object orientation. Because you don't give much
detail about your approach, all I can do is suggest one possible
alternative. I am sure that other readers will have other, equally valid,
suggestions. You also violate encapsulation, the principle that you
shouldn't have to look inside an object to use it, but we do that all the
time in Python so I wouldn't worry too much about that. We aren't purists
here...

The idea behind polymorphism is that different types of object implement the
same set of operations (methods), so that code which manipulates them
doesn't need to distinguish among the various types of object it must deal
with. You are using an attribute of payobject, which you obtained as the
result of calling a customer method, to determine whether you should process
a cash or a credit payment.

Had you thought of having two different subclasses of Customer, call them
CashCustomer and CreditCustomer? The Customer class would implement all
methods common to both types. Both CashCustomer and CreditCustomer would
inherit these common method from Customer, and each would provide an
appropriate ProcessPayment(amount) method. E.g.:

class Customer:
    pass # a simple implementation!

class CashCustomer(Customer):

    ...

    def ProcessPament(self, amt):
        if self.cashonhand >= amt:
            self.cashonhand -= amt
            return amt
        else:
            raise CannotPay, "customer only has %f" % self.cashonhand

class CreditCustomer(Customer):

    ...

    def ProcessPayment(self, amt):
        if self.CardIssuer.ValidatePayment(amt):
            self.CardIssuer.MakePayment(amt)
            return amt
        else:
            raise CannotPay, "card issuer will not allow payment of %f" %
amt

The advantage of such an approach is that anywhere in your code where you
are dealing with a Customer (of either type) c, you can call
c.ProcessPayment(x) to resuest a payment of amount x, and expect to raise an
exception of the payment cannot go through for whatever reason. Hence my
assertion that your code "shouldn't need to know the type of customer".

If you're new to OO this may be confusing, and I emphasise it's only one of
many possible approaches. I hope it helps you to realise that Python code
can be extremely clean when you do things right. Exceptions can help
simplify logivc when you want to concentrate primarily of the case where
everything goes right, but nevertheless have to accept that sometimes things
will go wrong.

regards
 Steve
--
http://www.holdenweb.com/



>
>





More information about the Python-list mailing list