2

So I've finally started reading Designing Object-Oriented Software and my needle is stuck at a small but important point. I understand that the example I'm about to present might appear a bit out of context, because it's being developed in the book over the last few chapters, but I believe the overall idea is really simple and I'll do my best to provide context wherever I can. About me, I'm a junior developer teaching myself, so I'm hoping putting this question here will result in some fruitful discussion.

Well, finally we have the preliminaries out of the way.

The system I'm talking of an Automated Teller Machine (ATM), which we need to model in terms of object-oriented software. The discussion is about input classes that will gather numeric input from user. Two possible cases are identified - gathering account numbers, amounts, etc., and gather the PIN, which are to be rendered differently (the PIN being displayed like a password field).

So the authors defined two different classes - Form and SecureForm, the latter deriving from the former.

Now here's a section from the book (debating the utility of SecureForm) that's causing me trouble:

The intended distinction between Form and SecureForm is whether user input is echoed literally or symbolically (perhaps with an X). This can be done by invoking two different messages: getValue() and getSecureValue(), but the sender [the class making the request] would have to test to determine which message to send. This could lead to maintenance difficulties. It is better to use polymorphism; that is, to have one message that invokes different methods depending on the class of object to which it is sent. For this reason we keep SecureForm [that is, don't discard it from our design].

The part in italics doesn't make sense to me. What would the calling object have to test? Perhaps for the type of input required, but then to me it doesn't seem to make a difference whether you use polymorphism or not. Consider the following two hypothetical examples:

//inside doTransaction function of WithdrawalTransaction

//example 1
form = new Form();
pin = form->getSecureValue();

//example 2
form = new SecureForm();
pin = form->getValue();

Hardly seems different to me, and not a "maintenance nightmare" by any measure.

I feel like there's some subtlety I'm missing here, but it eludes me. I'll be very grateful if someone can help me decipher the author's intent, and even better if some examples can be provided.

I hope there isn't any critical information missing from my post. If there is, please point out and I'll add more details.

5

I interpret that to mean that while you could have a SecureForm which implements both a getValue and a getSecureValue, you shouldn't because it becomes a maintenance headache.

In other words, like that, the caller would have to make a distinction in the code on how to call the instance of SecureForm, being careful not to call getValue but getSecureValue if that is what was desired.

You'd end up with code like this everywhere in your program:

String value;
if (form instanceof SecureForm) {
  value = ((SecureForm)form).getSecureValue();
} else {
  value = form.getValue();
}

Having to know what type it is is very much an anti-pattern. True polymorphism means not caring how getValue is implemented. If you need to call getValue AND getSecureValue in SecureForm, then you're not doing something right (and you should probably not be structuring your code in this fashion in that case). SecureForm can call getSecureValue internally, but one instance of Form should be interchangeable with any other instance though Listkov's principle.

  • Actually the point is whether to have two specialized classes for input or have only one (Form) handle both, but if we substitute Form for SecureForm in your example, your understanding is correct. But I'm not sure I agree on the explanation. Why check for the instance of a global (possible singleton) form when you can create an object of your own? My feeling as of now is that the discussion is too generic or maybe I need to sharpen my understanding to steer it in a more conclusive direction. Let me think and revisit it after some time. Meanwhile, thanks for your inputs! :) – ankush981 Nov 19 '15 at 16:59
  • @dotslash Hopefully I can address some of your questions. You're right. Why use Form when you're only going to use a single derived class of Form? In of itself, there is no reason to do so. However sometimes a library requires a class that implements an interface, and so you need to implement it before you can pass that instance to the method. In this way the library can use your object without knowing how it works. If I were implementing this, I would not ever use a derived class like SecureForm for input if it meant adding more methods to call, in my humble opinion. – Neil Nov 20 '15 at 7:15
  • Hmmm . . . I get the idea that client classes shouldn't even know that specialized base classes exist, let alone specialized methods. So, someone passes the "correct" Form instance to the Transaction class, which then asks for PIN/password input, right? Does that mean intelligence lies with this "someone" (say, manager) class? And does the manager class create an instance of SecureForm and pass it to Transaction, which is expecting Form and never gets to know the difference? Is that how it's supposed to be? – ankush981 Nov 21 '15 at 11:13
  • @dotslash Ideally, creation logic (how it gets created) should be separate from behavioral logic (how it is used). If a class doesn't care how an instance is implemented, then it shouldn't be the one instantiating that instance, since it would have to care which implementation it is. Usage of Form as a polymorphic instance should be a parameter to a class or method. Presumably your manager class creates the instance of Form, and passes it to Transaction which knows the instance only as a Form object. The manager shouldn't need to use it if it creates it. – Neil Nov 23 '15 at 8:26
  • Nice! I think I've added a vital piece to my OOAD knowledge. – ankush981 Nov 23 '15 at 16:53
1

Just in that 4 line example there is a lot of things that are subtly bad practice. The caller knows too much about the underlying implementation of the callee and the callee knows a bit too much on how things are calling in to it. Ideally these two things should not know about each other.

A big strength of polymorphism is that the details of how or what are hidden away from the caller because they don't care or need to know about it. Also enforcing implementations to obey a contract (interface) will make it easier on the caller. Unneeded intimate knowledge leads to unnecessary tight coupling which can lead to a maintenance nightmare.

  • I understand that point, and it was emphasized over and over in the book, but at this juncture I'm at a loss to see how this could turn into a nightmare. Certain transactions need input in a secure way. That means some entity in the system will have the knowledge that secure input can be handled by SecureForm. I'm not sure how this is worse than having a class that knows it can call another specialized method of Form for secure input. Both the cases seem like 'intimate knowledge' to me. :( I think I need to read and explore more real-life examples to understand it better. – ankush981 Nov 19 '15 at 16:49
  • @dotslash Well it is actually quite subtle. getSecureValue seems mundane now but what if there are some new security features added to to getting the value in the future? Maybe one takes a picture of the user when the pin is entered or validates over http that the debit card isn't marked as stolen. The caller shouldn't have to call takePictureAndGetValue or validateIfStolenAndGetValue. Just one line of code needs to change and the contract of the interface makes sure that value is what it is intended to be. For better understanding you can look up loose coupling and encapsulation. – pllee Nov 19 '15 at 17:25
  • 1
    The important distinction is that the difference between getting secure input and getting normal input is localized at exactly one point: the getValue method. All callers simply call getValue and the right thing will happen magically. Whereas with your suggestion, every single place where you want to get input, you have to decide which method to call. So, the knowledge about the difference between secure and insecure input is scattered throughout the entire system. If, all of a sudden, you decide that the birthdate is sensitive information, you need to change every single place that … – Jörg W Mittag Nov 19 '15 at 18:19
  • … asks for a birthdate, whereas with the polymorphic solution, you just make the birthdate input form a SecureForm. And the Liskov Substitution Principle will even guarantee that the callers cannot even tell the difference and will continue working just as they did before. – Jörg W Mittag Nov 19 '15 at 18:19
  • @JörgWMittag I think your comment is making sense to me. It would've been a lot better if we had a concrete example to talk about, but I think this explanation will suffice for now. – ankush981 Nov 21 '15 at 10:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.