560 likes | 676 Views
Chapter 8: Class Design. APCS. Methods and Classes. If it wasn’t obvious be now: Classes are groups of information and actions. The information is typically stored in Objects (or instances of the Class). Think of Objects as nouns and name them accordingly.
E N D
Methods and Classes • If it wasn’t obvious be now: Classes are groups of information and actions. • The information is typically stored in Objects (or instances of the Class). • Think of Objects as nouns and name them accordingly. • Actions are stored as the methods you can run. • Think of Methods as verbs and name them accordingly. • Good naming conventions will avoid confusion like the problems I have remembering which collection uses size().
What does your Class represent? • Good class design focuses around the general principles of Object Oriented Programming. • Information hiding dictates that we keep as many people as possible from knowing things that they don’t need to know. • With that in mind, Classes should be groupings methods and information that focuses on one single concept. • For example, I suggested that for the poker program you separate out the concept of a single card from a whole hand of cards.
More on Classes • Good class design will allow you to focus on a single goal or task and not allow other considerations to bloat or distract from the main purpose. • Well designed classes can also be reused more easily. • If you make a separate card class rather than a poker game class, wouldn’t it be easier when I ask you to write a solitaire program? • Can your card class also be used for a game of Uno, which doesn’t use a normal 52-card deck.
Classifications of Classes for our APCS Class • Other than our general Java Classes, there are a few specialized Classes: • Actors: • Objects of an actor class do some kind of useful task for us. • Prime examples would be the Scanner Class: it takes information and organizes and processes it for us. How nice of it! • As the book notes, these classes can often be identified by their names’ ending in “er” or “or”
Another Class of Classes • Utility: • Utility classes are the Swiss-Army knives of the Java world. Just like a knife, not all that useful by itself, but once you have something that needs cutting, it comes in handy. • These classes have no non-static methods or fields, and only operate on information that you pass to it. • The Math class for example never seems to require an instance of itself to get great use out of it.
A Disagreement Among Friends • While I don’t personally subscribe to this belief, I have spoken with a friend who is a professional programmer. He claims that comments are unnecessary and generally undesirable. • That’s not an excuse to not comment for the record. Failure to comment can still lead to me getting grumpy and marking you down. • But there is something I do agree with:
Proper Naming Conventions • His claim is not merely “Don’t comment,” his philosophy is that if you name all your methods and variables properly, many comments are not really necessary. • I prefer this philosophy: the more readable your program is so that other may understand your work, the better. Part of this includes comments, but a large portion is in what you choose to name things.
For Example: • I dislike examples where a programmer uses ‘x’ as a variable name. Math is not a great source of variable naming conventions. • I hope everybody knows what π and Σ mean in mathematics, but does anyone know what σ or ρ mean? It’s not just Greek to me, it’s plain Greek. • What if I replace σ with the name standardDeviation? As long as you know any stats, you should have some idea what I’m talking about. • You also know I don’t mean the Stefan–Boltzmann constant. Yay, Wikipedia.
Good Class Naming • When you give something a name in a program, take a moment and ask yourself if you named it properly. • Do you think a casual observer (who knows some Java) will understand immediately what a given Class, method, or variable is supposed to do? • If not… Pick a different name. • My favorite example of pathetic naming comes from the Intro Java book. See if you can decipher it: for(Lot lot: lots)
Keeping Separate Things Separate • As noted earlier, Classes should really have a single goal in mind and focus only on getting that job done. • If you are defining public methods for a Class and they seem to point to several different tasks, consider splitting into two classes.
Splitting things up • As stated earlier in the case of the Poker game, splitting a Card Object away from a Poker Game Object allows you to re-use your code the next time you want to make a card game. • You can have separate accessors for suit and rank to ease checking for pairs, flushes and straights. • Alternatively, you can make those instances fields final, allowing for direct access. • Additionally, internal changes need not affect any of the games that use your cards.
What about non-internal changes? • Splitting the responsibilities of a poker game into separate classes is a good idea, but caution is necessary. • Since the Poker program will no longer function by itself, it is now considered “dependent” on the Card Class. • This is not always a bad thing: most programs have large numbers of Classes that they depend on to work, but keeping track of these dependencies is important.
A note: • Just because the Poker game depends on the Card Class, the Card Class can exist outside of a game of Poker. • The way we notate this relationship is with a dashed line with an arrow pointing from the dependent class to the Class it needs. • This is called a Class diagram, and it uses the UML notation for Objects and Classes. PokerGame Card
Keeping Track of Dependencies • If you use your Card class across many programs, all those programs now depend (to some extent) on your Card class functioning the same way and using the same public interface. • If you make changes to that interface it can make programs depending on that interface completely nonfunctional. • That makes determining your public interface very carefully extremely important.
Not the racy British sitcom. • The amount of dependent classes is often referred to as “Coupling.” • If there are many classes in a program that depend on each other, then we say that the coupling between the classes is high. • Fewer dependencies mean that the coupling is lower. • High coupling can create very frustrating situations, where you end up prevented from making improvements to a given class because too many other things depend on it.
Spaghetti depency Manufacturer Casino PokerGame Card BlackJack Gambler TheWorld GoingRound Bet Money LackOf Inhibitions
You get the idea • A Chance to a class that is depended on by many other classes can cause a lot of problems. • Because of that fact, it’s a very good idea to ensure that unnecessary coupling is avoided. • Minimizing this interdependency not only makes the program simpler and easier to follow, it minimizes the damage if we need to drastically alter a class in some way.
From ages ago • Mutator: Modifies the object upon which it is invoked. • Accessor: Simply accesses information without changing anything. • Since nothing actually changes in the object, you may call it repeatedly and always get the same answer. • You can be assured that a well-written accessor will always be a safe way of handling objects.
You’ve used these before • Some classes, in fact only have accessors. Once they have been constructed, the objects themselves cannot be changed. • These classes are called “immutable.” • A few examples: String, Integer, Double.
Wait a minute… • Think about it: does any method in the String class modify the contents of itself directly? • .toUpperCase() for example just takes the String and returns the letters as all upper case. In the case of: String greeting = “Hello”; String yelling = greeting.toUpperCase(); • greeting will remain “Hello” after the method is run. Only yelling will contain “HELLO”.
Another argument • “What about when I set the variable greeting to another value! I change the value then! Ha! Not so smart now, are you Mr. Teacher!!” I hear you cry. • In actuality when you do the following: String greeting = “Hello!”; Greeting = “Wassup?”; • Java actually creates a new String and changes the reference contained in greeting to point to the new String Object, leaving the Object containing “Hello!” with nothing pointing to it.
Why is this useful? • I’m glad you asked, Mr. Title Bar! • When using an immutable class, since no information can be changed, it is safe to give out references to the object itself, since nothing inside of it can be changed. • As long as we keep holding on to it, nobody can change the information inside of it. • A mutable class has no such guarantees, and giving out references to those can prove dangerous.
May cause sneezing, sore throat, blindness, pinkeye, jaundice, unexpected burping, spontaneous combustion, bruising, elephanti- • Accessors and Mutators return values or change values… But that classification only applies to the object doing the calling. • Sometimes these methods can have consequences outside the given object though. • These are called side-effects and they can be dangerous if not documented carefull.
For Example • If you remove an item from an ArrayList, the remove method has a hidden side effect of reordering the array. • If you were iterating through with a for loop, you would likely skip a value! • In the book on page 342, it gives an example of a BankAccount transfer method that not only removes money from the current account, but it calls a method to a whole other BankAccount method.
They don’t expect the unexpected • When you write a mutator method, you should be careful to minimize the number of side effects that occur beyond modifications to the implicit parameter. • If you do have other changes, be sure to document them carefully. • The more information you give potential users of your methods, the easier their lives will be. • The people who wrote the Java libraries did a pretty good job, right?
Why don’t we do this? • Instead of always having to write something like: System.out.println(“The balance is: “ + myAccount.getBalance()”); • Why don’t we do this instead? public void printBalance() { System.out.println(“The balance is: “ + balance); }
Check your assumptions • What if a user of your BankAccount software would rather the message be different? • They might want the message to support other languages. • You’d still need a getBalance method for other methods to gain access to the information if we don’t just want to print it. • If the software was loaded onto an embedded system, possibly like an ATM, there is no guarantee that System.out will work. It may need a different method. • By not using System.out, and instead focusing on just returning a value, you make the software more flexible.
Dependencies • By using a straight accessor and not a System.out call, we minimize several dependencies: • Dependence on System.out, which may not always be the desired output. • Dependence on our message being suitable for all applications. • By reducing these dependencies, our software can be applied to more situations with much less modification.
Modifying parameters:Primitives • If a primitive is passed as a parameter to a method, a new variable is declared and the value is copied into the parameter variable. • This happens because unlike objects, primitives actually hold the information, rather than a reference. • If we make modifications to the parameter variable, as soon as the method terminates, the variable dies, and all our changes with it. • If you want to change anything like this, you have to return a value and use that to update the original variable.
On the other hand • This may make it seem safe to use the parameter variable directly and make changes to it. • Not a good idea. That parameter variable represents the only hold you have on the passed information. As soon as you change it, the method cannot retrieve the original data. • Instead, make a copy as soon as the method begins and use the copy to affect changes instead. That way, you won’t lose the initial data.
Tips to minimize side effects • Accessor methods should make no changes. They should only return information without any permanent modifications to existing information. • Mutator methods should change the implicit parameter only – the desired side effect of the caller. • Methods that change the explicit parameters should be avoided whenever possible, minimizing potential hazards. When unavoidable, document and use them carefully. • Methods that increase dependence on specific other objects (like System.out) should likewise be avoided.
Value vs. Reference • Passing by value means that the parameters are passed by sending over physical data. • Passing by reference means that instead of actual data, the reference to the object is passed, rather than the object itself. • In Java, it is often said that primitives are passed by value, objects are passed by reference. • This is not strictly true.
A fine distinction • In Java, Objects are not passed as references, rather their references are passed by value. • What does this mean? Well, consider the discussion of immutable classes: • We observed that since there were no mutators, and no access was given to the original holder of the object reference, nothing could break the black box around the original variable.
True Pass by Reference • In languages such as C++ it was possible to pass a variable directly by passing a variable by reference. • Imagine instead of giving someone a chance to peek in the box, just handing them the box completely. • Hopefully we can all see why this can be problematic given our policies about information hiding. • If you pass a variable purely by reference, the method receiving this information can change your variable without you knowing about it.
Kickin’ it Java-Style • In Java, the reference to the object is passed, but since we aren’t passing our box that contains the Object, methods that we pass the Object to may be able to modify the Object using its included methods, they cannot completely replace our Object with another of the same type. • While (with mutable types) we can’t guarantee that the Object will be completely the same, users are limited to changes allowed by the interface methods we provide.
Preconditions and Postconditions • Preconditions and Postconditions are very important aspects of documentation that can save you a lot of work. • A precondition is a set of requirements that a user of your program must obey. • If users do make sure that they are in compliance with all stated preconditions, the Object can not be held responsible for producing correct results. • This only works for your objects if you document it.
You make the rules • Preconditions are used primarily in two ways: • Restrict the parameters of a method to valid inputs. So if you put a precondition of balance >=0, if someone passes you an account with a negative balance, your Object is not on the hook if something goes wrong. • Require that the program is in an appropriate state to run a method. For example, that ArrayList you wanted me to remove duplicates from should have at least two entries for it to run correctly, otherwise it will explode. So we set a precondition of yourArray.size() >= 2.
Setting a Precondition • Since preconditions are not enforced by the program automatically, they exist in comments only. • You can use the @precondition or @requires tags, but that isn’t part of the official javadoc spec so it won’t always work. • You can attach preconditions for specific parameters to the @param tag instead.
Dealing with naughty Users • How your programs handle users who violate preconditions is largely up to you, but there are pretty much two things that can happen: • First, your program just runs anyways and the user gets what he or she deserves for violating your precondition. Satisfying? Sure. Not a great choice from a usability standpoint. • Check to see if the user tried to slip a bad value by you and throw an Exception. See Chapter 11!
Vocab word: Shadenfreude • While it may be satisfying to punish your users for ignoring your warnings, it is often better to give some kind of reaction that lets them know that they’re not following the rules. • At the same time, it is wasteful to be constantly checking to see if every single value remains correct all the way through everything, again and again and again. • Thankfully, Java allows us to use the second option, but still be a short hop from the first.
Assertions • Using the syntax: assert condition; • You demand that the condition you state must be true before the program continues, or else the program will immediately die, screaming Bloody Murder (a.k.a. AssertionError). • If the condition is true, then the program executes normally.
Turning your program into a Drama Queen • The advantage to this method is that you can choose whether or not you want to spend resources checking to make sure that values are valid internally, but still have the finished product run full speed without needing to take out every single precondition enforcement. • While you are testing to see if a program works, though you can go assertion crazy and know that once everything is clean, you can flip them all off at once.
Tough Love • Is it really nice to quit the program immediately just because one lil’ number just happens to be completely illegal? Can’t you just let it slide this once? • What do you think can happen if you allow little bugs to slide?
Just Don’t Do it • If you don’t smack ‘em around a little bit, users wont ever learn! • Let them know that something went wrong, then immediately terminate. That way you ensure that the users can feel safe in the knowledge that if your programs spits out info, that info is reliable.
Time Saving Devices • Did you know you could do the following? I didn’t… import static java.lang.System.*; • This allows you to leave off the “System” part when you want to send output to the console. How nice of Mr. Horstmann to tell us… on page 363! • Try it out!
Freshen your breath • We’ve talked about scope before, so I’ll keep this brief. • A local variable only lives until the block it was declared in ends. • You may use the same name for multiple variables in the same program, so long as their scope doesn’t overlap (e.g. two distinct loops that both declare a variable “counter”).
Wider scope • An instance field is declared at the very outer level of curly braces, so it will live for the entire life of any instance of that Object. • This can lead to some interesting cases…
Another Good Idea – Bad Idea • You may use a local variable that has the same name as an instance field, but be aware that you will be hiding the field behind the local variable. • If you want to still get at the field, you’ll have to explicitly state that you want the field and not the local variable using the reserved word this • While this is legal, I personally find it to be a very bad idea, especially since the only thing you need to do to avoid it is pick better names.
Packages • Java programs typically use many packages. • Since each class should have its own file, that means that there are a lot of files that we have to manage. • There is a way to organize Classes into groupings of related classes to help us keep track of the Classes we want to use. • These are called packages.