49 design patterns

Upload: manojkumar

Post on 09-Mar-2016

214 views

Category:

Documents


0 download

DESCRIPTION

Design patterns

TRANSCRIPT

  • Design Patterns and Refactoring

  • HistoryA Pattern Language: Towns, Buildings, Construction, Christopher Alexander, 1977 The Timeless Way of Building, Christopher Alexander, 1979 Using Pattern Languages for Object-Oriented Programs (a paper at the OOPSLA-87 conference), Ward Cunningham and Kent Beck, 1987 Design Patterns, Erich Gamma, Richard Helm, John Vlissides, and Ralph Johnson (known as the Gang of Four, or GoF), 1994 Refactoring: Improving the Design of Existing Code, Martin Fowler, 2000

  • BuzzwordsDesign Patterns describe the higher-level organization of solutions to common problemsUML is a diagramming language designed for Object-Oriented programmingDesign Patterns are always described in UML notationRefactoring is restructuring code in a series of small, semantics-preserving transformations (i.e. the code keeps working) in order to make the code easier to maintain and modifyRefactoring often modifies or introduces Design PatternsExtreme Programming is a form of Agile Programming that emphasizes refactoringUnit testing is testing classes in isolationUnit testing is an essential component of Extreme ProgrammingUnit testing is supported by JUnit

  • Design PatternsDesign Patterns describe the higher-level organization of solutions to common problemsDesign Patterns are a current hot topic in O-O designUML is always used for describing Design PatternsDesign Patterns are used to describe refactorings

  • UMLUML stands for Unified Modeling LanguageUML is a big, complicated diagramming language designed for Object-Oriented programmingUML comprises at least seven or eight different kinds of diagrams that can be used to describe:the organization of a programhow a program executeshow a program is usedhow a program is deployed over a networkand moreThis talk will cover just a tiny bit of one kind of diagram, the class diagram

  • UML class diagramsKey:+ means public visibility# means protected visibility- means private visibility means default (package) visibilitystatic variables are underlined

  • UML relationships

  • Sample Design PatternsThere are a few dozen Design Patterns described in the GoF bookIll only talk about a couple, to give some of the flavor of what they are all aboutIll try to use examples that are relevant to problems you have dealt with in this class (plus some Im particularly fond of)Im using UML diagrams in only a few examples, because they just take too much time to draw in PowerPointSpecialized tools, such as Rational Rose, Together, and ArgoUML are much better for this

  • Problem: Uncertain delegationMuch of the point of polymorphism is that you can just send a message to an object, and the object does the right thing, depending on its typeHowever, if the object might be null, you have to be careful not to send it any messageif (myObject != null) myObject.doSomething();Examples:You have an Ocean, represented by a sparse array containing a few FishYou have a TrafficGrid, some of which contains Cars and TrucksYou want to send output to somewhere, possibly to /dev/nullIf you do a lot with this object, you code can end up cluttered with tests for null

  • Solution: Null ObjectCreate another kind of object, a null object, representing the absence of any other kind of objectExample: An Ocean might contain Inhabitants, where Inhabitant is subclassed by BigFish, LittleFish, Algae, and NothingButWaterThis way, no location in the Ocean is nullIf Inhabitant contains a method reproduce(), the subclass NothingButWater could implement this method with an empty method bodyIf appropriate, some methods of the null object could throw an ExceptionIdeally, the superclass (Inhabitant, in this example) should be abstract

  • Refactoring: Introduce Null ObjectThe general idea is simple: Instead of having some variables (locations in the array) be null, have them be null objectsHowever, this requires numerous changes in the codeIts hazardous to change working codeyou introduce bugs that it can take days to findRefactoring is all about:doing an operation like this in small steps,having an automated set of unit tests, andrunning unit tests frequently, so that if an error occurs you can pinpoint it immediatelyThis approach makes refactoring much safer and protects against hard-to-find bugsAs a result, programmers are far more willing to refactor

  • Introduce Null Object: In detail, ICreate a subclass of the source class to act as a null version of the class. Create an isNull operation on the source class and the null class. For the source class it should return false, for the null class it should return true.You may find it useful to create an explicitly nullable interface for the isNull method.As an alternative you can use a testing interface to test for nullnessCompile.Find all places that can give out a null when asked for a source object. Replace them to give out a null object instead.

  • Introduce Null Object: In detail, IIFind all places that compare a variable of the source type with null and replace them with a call to isNull.You may be able to do this by replacing one source and its clients at a time and compiling and testing between working on sources.A few assertions that check for null in places where you should no longer see it can be useful.Compile and test.Look for cases in which clients invoke an operation if not null and do some alternative behavior if null.For each of these cases override the operation in the null class with the alternative behavior.Remove the condition check for those that use the overridden behavior, compile, and test.

  • Refactoring detailsThe details of Introduce Null Object were copied directly from Fowler, pages 261-262I am not going into this much detail in any of the remaining examplesNotice, however, that with this list of baby steps in front of you, you can do the refactoring a little at a time, with well-marked places to do testing, so that its very easy to catch and correct errorsNote also that to do this, you need a good set of totally automated testsotherwise the testing you have to do is just too much work, and you wont do itUnless, that is, you have a superhuman amount of disciplineJUnit (now built in to BlueJ) is a great start

  • Scenario: Big fish and little fishThe scenario: big fish and little fish move around in an oceanFish move about randomlyA big fish can move to where a little fish is (and eat it)A little fish will not move to where a big fish is

  • Problem: Similar methods in subclassesHere we have a Fish class with two subclasses, BigFish and LittleFishThe two kinds move the same wayTo avoid code duplication, the move method ought to be in the superclass FishHowever, a LittleFish wont move to some locations where a BigFish will moveThe test for whether it is OK to move really ought to be in the move methodMore generally, you want to have almost the same method in two or more sibling classes

  • Solution: Template methodNote: The Design Pattern is called Template Method; the refactoring is called Form Template MethodWe wont bother making this distinction in the remainder of the lectureIn the superclass, write the common method, but call an auxiliary method (such as okToMove) to perform the part of the logic that needs to differWrite the auxiliary method as an abstract methodThis in turn requires that the superclass be abstractIn each subclass, implement the auxiliary method according to the needs of that subclassWhen a subclass instance executes the common method, it will use its own auxiliary method as needed

  • The move() methodGeneral outline of the method:public void move() { choose a random direction; // same for both find the location in that direction; // same for both check if its ok to move there; // different if its ok, make the move; // same for both }To refactor:Extract the check on whether its ok to moveIn the Fish class, put the actual (template) move() methodCreate an abstract okToMove() method in the Fish classImplement okToMove() in each subclass

  • The Fish refactoringNote how this works: When a BigFish tries to move, it uses the move() method in FishBut the move() method in Fish uses the okToMove(locn) method in BigFishAnd similarly for LittleFish

  • Problem: Constructors create objectsConstructors make objects. Only constructors can make objects. When you call a constructor of a class, you will get an instance of that class.Sometimes you want more flexibility than thatYou may want to guarantee that you can never have more than one object of a given classYou may want to create an object only if you dont already have an equivalent objectYou may want to create an object without being sure exactly what kind of object you wantThe key insight is that, although only constructors make objects, you dont have to call constructors directlyyou can call a method that calls the constructor for youSeveral creational Design Patterns are based on this observation

  • SingletonA Singleton is a class that can have only one instanceYou may want just one instance of a null object, which you use in many placesYou may want to create just one AudioStream, so you can only play one tune at a timeclass Singleton { private static Singleton instance = new Singleton(); // dont let Java give you a default public constructor private Singleton() { } Singleton getInstance() { return instance; } ... }

  • The Factory Method Design PatternSuppose you write a class that works with several different kinds of objectsYou can do this if the classes all have a common interfaceYou may want to be able to create objects, without being dependent on the kind of objectA factory methods can create instances of different classes, depending (say) on its parametersExample:Image createImage (String ext) { if (ext.equals("gif")) return new GIFImage(); if (ext.equals("jpg")) return new JPEGImage(); ... }

  • Problem: Reducing interdependenciesSuppose you have an application that provides multiple servicesSuppose further that the application consists of a large number of classesYou want to provide access to those services, without requiring the user to know all the internal detailsFor example, you have a simulation of an ocean, containing assorted kinds of fish, and you want to provide access to that simulationJust to make the problem interesting, suppose that you have two or more such applications (say, Oracle, MySql, and Access 2000) and you want to write a program that works with any of themSolution: Use the Faade Design Pattern

  • The Faade Design PatternCreate a class that accepts many different kinds of requests, and forwards them to the appropriate internal classIf the back ends vary, you man need to write a separate Interface class for each (all implementing the same interface), but the users of your Facade class dont need to changeExample:class Ocean { public void setBigFishGestationPeriod(int period) { BigFish.setGestationPeriod(period); } public int getNumberOfAlgae() { return Algae.getCount(); } ... }Of course, the Facade class can do other things as well

  • The Immutable Design PatternThere are many benefits to objects that cannot be changed after they have been createdSuch objects are called immutableObjects that refer to an immutable object never have to worry about whether that object has been changedImmutable objects are thread-safethis is a significant efficiency concern, because synchronization is expensiveExample: Strings in JavaIts easy to make immutable objects in Java:Make all instance variables private, andProvide no methods that change those variables

  • Delegation (or, when not to use inheritance)When you create a subclass, you agree to inherit all its (non-private) fields and methodsWhat if you dont want them all?Example: A Vector can do everything that a Stack should be able to do, and much, much moreYou may want to inherit just some of the functionality, and probably add some of your ownInheritance doesnt let you do thatat least, not easilyIf your class wants to hide variables or methods inherited from a superclass, it shouldnt inherit from that superclassIf an object needs to be a different subclass at different times (say, a LittleFish turning into a BigFish), then it shouldnt be a subclass of that class in the first placeInstead of inheriting, just use an instance of that class, and delegate to it

  • Example: Stacksclass Stack { Vector contents = new Vector(); public void push(Object o) { contents.add(o); // delegate to the Vector } public Object pop() { return contents.remove(contents.size() 1); } ... }

  • An exampleSome time ago I was working on code to evalute expressionsExpressions can be parsed into a tree structureNow what?You could walk the tree and, at each node, use a switch statement to do the right thingI discovered a better solution (basically, a simple form of the Command Design Pattern)

  • Using my Command patternclass Add extends Command { int evaluate( ) { int v1 = lhs.evaluate().value; int v2 = rhs.evaluate().value; value = v1 + v2; return value; } }To evaluate the entire tree, evaluate the root nodeThis is just a rough description; there are a lot of other details to considerSome operands are unaryYou have to look up the values of variablesEtc.

  • The Command Design PatternReasons for using the Command Design Pattern:You want to control if, when, and in what order the commands are executedYou want to keep a log of commands executedPopular reason: You want to manage undo and redo operationsPossible class organization (from GoF):AbstractCommand with doIt() and undoIt() methodsConcreteCommand subclasses of AbstractCommandInvoker is a class that creates ConcreteCommand objects if it needs to invoke a commandCommandManager to decide what, when, and how to execute and undo commands

  • RefactoringRefactoring is:restructuring (rearranging) code......in a series of small, semantics-preserving transformations (i.e. the code keeps working)......in order to make the code easier to maintain and modifyRefactoring is not just any old restructuringYou need to keep the code workingYou need small steps that preserve semanticsYou need to have unit tests to prove the code worksThere are numerous well-known refactoring techniquesYou should be at least somewhat familiar with these before inventing your own

  • When to refactorYou should refactor:Any time that you see a better way to do thingsBetter means making the code easier to understand and to modify in the futureYou can do so without breaking the codeUnit tests are essential for thisYou should not refactor:Stable code (code that wont ever need to change)Someone elses codeUnless youve inherited it (and now its yours)

  • Design vs. codingDesign is the process of determining, in detail, what the finished product will be and how it will be put togetherCoding is following the planIn traditional engineering (building bridges), design is perhaps 15% of the total effortIn software engineering, design is 85-90% of the total effortBy comparison, coding is cheap

  • The refactoring environmentTraditional software engineering is modeled after traditional engineering practices (= design first, then code)Assumptions:The desired end product can be determined in advanceWorkers of a given type (plumbers, electricians, etc.) are interchangeableAgile software engineering is based on different assumptions:Requirements (and therefore design) change as users become acquainted with the softwareProgrammers are professionals with varying skills and knowledgeProgrammers are in the best position for making design decisionsRefactoring is fundamental to agile programmingRefactoring is sometimes necessary in a traditional process, when the design is found to be flawed

  • A personal viewIn my opinion,Design, because it is a lot more creative than simple coding, is also a lot more funAdmittedly, more fun is not necessarily better...but it does help you retain good programmersMost small to medium-sized projects could benefit from an agile programming approachWe dont yet know about large projectsMost programming methodologies attempt to turn everyone into a mediocre programmerSadly, this is probably an improvement in generalThese methodologies work less well when you have some very good programmers

  • Back to refactoringWhen should you refactor?Any time you find that you can improve the design of existing codeYou detect a bad smell (an indication that something is wrong) in the codeWhen can you refactor?You should be in a supportive environment (agile programming team, or doing your own work)You should have an adequate set of automatic unit tests

  • Example 1: switch statementsswitch statements are very rare in properly designed object-oriented codeTherefore, a switch statement is a simple and easily detected bad smellOf course, not all uses of switch are badA switch statement should not be used to distinguish between various kinds of objectThere are several well-defined refactorings for this caseThe simplest is the creation of subclasses

  • Example 1, continuedclass Animal { final int MAMMAL = 0, BIRD = 1, REPTILE = 2; int myKind; // set in constructor ... String getSkin() { switch (myKind) { case MAMMAL: return "hair"; case BIRD: return "feathers"; case REPTILE: return "scales"; default: return "integument"; } } }

  • Example 1, improvedclass Animal { String getSkin() { return "integument"; } } class Mammal extends Animal { String getSkin() { return "hair"; } } class Bird extends Animal { String getSkin() { return "feathers"; } } class Reptile extends Animal { String getSkin() { return "scales"; } }

  • How is this an improvement?Adding a new animal type, such as Amphibian, does not require revising and recompiling existing codeMammals, birds, and reptiles are likely to differ in other ways, and weve already separated them out (so we wont need more switch statements)Weve gotten rid of the flags we needed to tell one kind of animal from anotherBasically, were now using Objects the way they were meant to be used

  • JUnit testsAs we refactor, we need to run JUnit tests to ensure that we havent introduced errorspublic void testGetSkin() { assertEquals("hair", myMammal.getSkin()); assertEquals("feathers", myBird.getSkin()); assertEquals("scales", myReptile.getSkin()); assertEquals("integument", myAnimal.getSkin()); }This should work equally well with either implementationThe setUp() method of the test fixture may need to be modifiedA (beta) version of JUnit is now included in BlueJ

  • Bad Smell ExamplesWe should refactor any time we detect a bad smell in the codeExamples of bad smells include:Duplicate Code Long MethodsLarge ClassesLong Parameter ListsMulti location code changesFeature EnvyData ClumpsPrimitive Obsession

  • The End