简体   繁体   中英

Classes versus parameterized instances

Let's say I have a class hierarchy. I could theoretically maintain its functionality with a single class, by providing extra parameters to the instances. Here's an example (super-contrived, but I wanted to keep it simple):

class Base:
  def __init__(self, a):
    self.a = a
  def f(self, x):
    raise NotImplemented # needs to be defined in subclass

class Mult(Base)
  def f(self, x):
    return self.a * x

class Add(Base):
  def f(self, x):
    return self.a + x

m = Mult(5)
a = Add(7)
m.f(10)
a.f(20)

The above code can be refactored as:

class Compute:
  def __init__(self, a, func):
    self.a = a
    self.func = func
  def f(self, x)
    return self.func(self.a, x)

m = Compute(5, operator.mult)
a = Compute(7, operator.add)

I understand that for this silly example, it makes no difference. So please don't think about it except to understand my point.

I want to know what factors I should think about when making this choice for the variety of situations I encounter in real life? In other words, what are the pros / cons of using classes versus parameterized instances?

In the first example, you supply the client code with canned ways of doing a fixed set of operations. It's easy to multiply, but if you want to divide instead, well, tough luck.

In the second example, you push implementation details up to the client. You now have an entire operation-agnostic computation framework . This requires more knowledge in the client code.

So, what really differs is the level of abstraction—how knowledgeable you want your client to be. If it is important for the client that operator.mult be used, not some other logic (like, XML-RPC to a multiplication server), the second option seems appropriate. If the client knows better than you what to do with the two numbers, and you want to provide only a framework (a wrapper of sorts), the second option is better. If you just want to let people add and multiply stuff, the first option is better.

It really depends on what the classes do. In your example the logic in the classes it so minimal it is obvious you've just supplied wrappers for methods as classes. and it makes more sense to have the compute class.

In real life situations you should ask yourself how cohesive are the different functions you want to group together. Though single responsibility is important you don't need to take that ad absurdum

I guess it depends on whether you'll have all the information about transformations (?) you'll do on the object available at construction time. If you don't, you'd have trouble calling the more complicated constructor.

Further, leaving the option open to do things to the "basic" object, before committing it down a certain path is often helpful, even if not apparent when initially designing the classes.

In general, I've found that creating a constructor with the minimum required parameters and implementing any other functionality via methods to be the best solution.

In the spirit of random examples, consider:

Parameterized Constructor

class Image:
    # Some implementation

class Text:
    def __init__(self, image, whitelistChars):
        # This uses OCR to extract the text from an image then filters out 
        #   non-whitelisted characters
        #
        # image is an Image
        # whitelistChars is an interable containing characters that should 
        #   not be filtered out

im = Image('scan.tif')
t = Text(im, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ134567890');

Basic Constructor

class Text:
    def __init__(self, image):
        # This uses OCR to extract the text from an image
        #
        # image is an Image


    def filter(self, whitelistChars):
        # This filters out non-whitelisted characters
        #
        # whitelistChars is an interable containing characters that should 
        #   not be filtered out

im = Image('scan.tif')
t = Text(im)
# <-- Are you interested in doing anything to the Text object t here?
t.filter('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ134567890')

In the "Basic Constructor" implementation, you have the option to use the Text object t prior to stripping the non-whitelisted characters. Maybe this is useful? Maybe more importantly, maybe this will be useful in the future?

If you can confidently say, "No", it's not useful and never will be then I think the "Parametrized Constructor" is fine. If anything, it eliminates the need for the extra filter call.

But leaving open the option to manipulate t , or even just inspect it, prior to transforming it (in this case filter the text), is often helpful if you hope to reuse the code.

(My $0.02)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM