1 / 39

Practical object oriented design techniques

Satish Annapureddy Director, Technology Myrio Corporation. Practical object oriented design techniques. Introduction. Focus Practical techniques and guidelines, that can be used daily, to create “good” object-oriented designs. How to design objects – fields and methods

tamyra
Download Presentation

Practical object oriented design techniques

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Satish Annapureddy Director, Technology Myrio Corporation Practical object oriented design techniques

  2. Introduction • Focus • Practical techniques and guidelines, that can be used daily, to create “good” object-oriented designs. • How to design objects – fields and methods • How to design relationships and interactions • Inheritance, composition, interfaces • Thread-safe design with OO. • Java design idioms

  3. Proper intialization • Objects can be seen as finite-state machines. • Instance variables store state. Methods change state by changing instance variables. • The challenge is in keeping the object in a valid state at all times. • Data hiding is imperative! • Proper initialization ensures that the object starts in a valid state.

  4. Proper Initialization (contd) • Design constructors and initializers so that there is no way the object can start in an invalid state. • Throw exception to indicate invalid constructor/method parameters eg., java.lang. IllegalArgumentException • If for some reason, it is needed to allow the object to start in an invalid state, throw exception to indicate improper usage. eg., java.lang.IllegalStateException.

  5. Proper finalization • Finalizers – what they are NOT for - • System resources – file handles, sockets, memory etc. are finite. Free them when no longer needed. • In Java, garbage collector runs finalizer when it frees unreferenced object (not when running out of some non-memory resource). • Provide an API to allow clients to release resources. eg., open() and close().

  6. Proper finalization (contd) • Finalizers – what are they for? • In Java, for releasing memory allocated in native methods via JNI. • Last attempt at releasing non-memory resources. • “Attempt” since finalizers may not run on live objects at time of exit. • In Java, java.lang.Runtime.runFinalizersOnExit() ensures that finalizers are run on all live objects at exit.

  7. Designing fields • Use a variable per purpose. • In version 1, let's say that the temperature sensor is capable of tracking temps >= 0; public class TemperatureSensor { ... public int getTemperature() { return temp; } public boolean isSensorWorking() { return temp < 0; // dual-use } private int temp; }

  8. Designing fields (contd) • In version 2, the sensor can track negative temperatures too. public class TemperatureSensor { ... public int getTemperature() { return temp; } public boolean isSensorWorking() { return working == true; } private int temp; private boolean working; }

  9. Designing methods • Minimizemethod coupling as much as possible. • The less a method (and hence a class) knows about other classes, the better. • Take in the most relevant object and output the object actually affected. • Least coupled are utility methods (static methods that depend only on input parameters and class constants)

  10. Designing methods (contd) // encode x-www-form-urlencoded strings public class XWWWFormURLEncoder { public void encode(URLString str) { ... } } Note: x-www-form-urlencoded is applied to a String, usually in the context of a URL, but not necessarily.

  11. Designing methods (contd) • Maximize Cohesion – each method must focus on a single conceptual task. eg., insert(..), delete(..), open(..) etc. • Why? • Changes localized. Removes side-effects. • More readable. • How? • Avoid passing control parameters. Create multiple methods instead.

  12. Designing methods (contd) public class Account { // low cohesion: control data (type) is passed in. // split into multiple methods: creditAccount(..), debitAccount(..), clearAccount(..) public void updateBalance(int type, float amount) { if(type == CREDIT) {...} else if(type == DEBIT) {...} else if(type == CLEAR) {...} else if... } }

  13. Encapsulation and information hiding • Encapsulation and information hiding are two different concepts! • Encapsulation refers to bundling data and operations that use that data. • Information hiding refers to hiding implementation details of the class • You can have encapsulation without information hiding. • Good OO requires both done right! • Objects should be intelligent entities. Do not separate methods from related data.

  14. Encapsulation and information hiding (contd) • Information hiding guidelines • Don't expose data. • Don't expose the fact that certain attribute is derived – use getDuration(...) instead of computeDuration(...) • Don't expose details on internal data structures – getMap() instead of getTreeMap(). • Don't give out mutable handles to internal data.

  15. Encapsulation and information Hiding (contd) public class SortedList { public void insert(ListElement obj) { // insert at appropriate position so that // the list remains sorted. } public ListElement getElement(int position) { return elementAt(position); // direct access to internal data. } } Notes: client code can use getElement().setX(...) so that the list is no longer sorted.

  16. Encapsulation and Information Hiding (contd) • get/set expose implementation details via interfaces • For example, code that uses int getX() breaks when the return type is changed to float. • Objects should be designed to be intelligent. ie., request services not data. • By understanding how the class will be used, you can eliminate most get/set methods by providing services instead.

  17. UI Design without getters and setters • Problem: If get/set are removed, an object must somehow know how to present its UI. • But, it is not feasible for an object to support all possible UIs. • Solution: Use the Builder pattern • Use a Builder helper object and provide for export and import via interfaces. • This basically moves the get/set to the Builder (UI) object from the business class object.

  18. UI design without getters and setters (contd) public class TestingStats { public interface Exporter { void setScores(float[] scores); void setAvg(float avg); .. } public void export(Exporter builder) { builder.setScores(scores); builder.setAvg(avg); ... } private float[] scores; private float avg, median, mean; // and others ... }

  19. UI Design without getters and setters (contd) public class TestingStatsUI extends JPanel implements Exporter { public void setScores(float[] scores) { graph.setData(scores); } public void setAvg(float avg) { add(new JtextField(“”+avg)); } ... private SuperPowerfulGraphWidget graph; } public void showUI(...) { ... testingStats.export(testingStatsUI); ... testingStatsUI.show(); ... }

  20. Thread-safe design • Heap (and method area) is common, stacks are local among threads. • Methods cause state transitions. But during the transition, the state becomes invalid. Atomicity is required to ensure that the invalid state is not exposed to other threads. • Guarding critical sections prevents race conditions – read/write and write/write conflicts.

  21. Thread-safe design (contd) • For example, insert() below has both Read/Write and Write/Write conflicts. public class LinkedList { public void insert(LinkElement new_element) { tmp = cur.next; cur.next = new_element; new_element.next = tmp; } public void printList() { for(LinkElement elem = start; elem != null; elem=elem.next) print(elem); } ... }

  22. Thread-safe design (contd) • Three approaches • Protect critical sections with mutexes. • Another reason why fields MUST be private! • Identify critical sections and use lock/unlock to enter and leave critical sections. • Synchronizing everything degrades performance and may also result in deadlocks. • Most common and most powerful approach. Thread coordination (wait and notify) are impossible without locks. • Use immutable objects. • Inherently thread-safe - object state does not change after creation.

  23. Thread-safe design (contd) • Identify critical sections but no locking. • Critical sections that read are left alone. • Critical sections that write are changed to create new immutable objects to reflect new states. • eg., java.lang.String • Thread-safe wrappers • Front-end the object with a thread-safe wrapper that has the same interface. (Decorator pattern) • Flexible – make objects thread-safe only when really needed. • Eg., Collections.synchronizedSet(Set set)

  24. Exceptions • When to throw exceptions? • To indicate an abnormal event: If your method encounters an “error condition”, throw an exception. • What constitutes an “error condition”? • Is EOF an error condition? • While reading byte by byte into a buffer? • When only 3 bytes of a 4-byte int are read? • To indicate broken contract: caller violates pre-condition. • What about calling java.util.Iterator.next() when java.util.Iterator.hasNext() returns false?

  25. Exceptions (contd) • A Runtime exception – NoSuchElementException is thrown. • What about a method that expects non-null String but is passed null instead? • A runtime exception – IllegalArgumentException is thrown. • What exception to throw? • The client programmer should handle “abnormal” conditions by throwing specific checked exceptions. These are declared and hence enforced by the compiler. • Broken contracts indicate bad code and should be fixed before release. Runtime exceptions are appropriate for this purpose.

  26. Inheritance vs Composition • Inheritance makes use of dynamic binding and polymorphism. • Great for adding new behaviors via subclassing. • Bad when superclass's interface needs modifications. • Changes to public method signatures ripple to all the clients that use the superclass or any of its subclasses. • Composition delegates actual implementation to a back-end class.

  27. Inheritance vs Composition (contd) • Composition: Advantages • Better isolates back-end changes: The front-end implementation will change to reflect the back-end change, but the front-end interface may remain the same. • Subclasses are more rigid than front-end classes. eg., changing return type on an inherited method is not possible. • Composition allows optimizations – lazy instantiation of back-end objects and dynamically changing back-end objects.

  28. Inheritance vs Composition (contd) • Composition: Disadvantages • Polymorphism makes inheritance far more effective than composition for adding new implementations (new subclasses). • Composition with interfaces solves this problem. • Delegation approach in composition might affect performance. • How to choose between the two? • Inheritance ONLY for permanent is-a relationships. Otherwise, use composition. • Do not use inheritance just to reuse implementation or for polymorphism

  29. Inheritance vs Composition (contd) Consider the following classes: public class MediaAsset { public String getURL() { ... } } public class MovieAsset extends MediaAsset { ... } public class MoviePlayer { public void setupMovie(MovieAsset movie) { BandwidthController.reserveBandwidth(authenticationInfo, movie.getURL()); ... } }

  30. Inheritance vs Composition Let us say that getURL() method in MediaAsset is changed to return URL. public class MediaAsset { public StringURL getURL() { } ... } • Now, MoviePlayer object is broken since it uses MovieAsset which extends MediaAsset. If we used composition instead, the change would not ripple past MovieAsset (the front-end class) public class MovieAsset { public String getURL() { return mediaAsset.getURL().getPath(); } private MediaAsset mediaAsset; }

  31. Composition with interfaces • Interfaces allow for more polymorphism than inheritance. • Inheritance with polymorphism allows for using a subclass in place of a superclass. • With interfaces you are not limited to one inheritance hierarchy. Any class that implements the interface can be substituted. • Composition with interfaces is just as powerful and flexible as inheritance with polymorphism. • Interfaces do not suffer from the “diamond” problem.

  32. Composition with interfaces(contd) • No multiple inheritance of implementation (methods and non-private fields) and hence no ambiguity. • Interfaces allow implementation to be totally decoupled from interface. • How to use interfaces: • Various subsystems should communicate with each other via interfaces. • Use interfaces to abstract out subsystems that can have many implementations. • Use interfaces to represent functionality common to different class hierarchies.

  33. Composition with interfaces (contd) • In the previous example, MovieAsset cannot be substituted for MediaAsset if it is implemented using composition. By combining composition with interface, we can easily solve this problem! public interface Asset { String getAssetURL(); ... } public class MediaAsset implements Asset { .. } public class MovieAsset implements Asset { // delegate to mediaAsset instance. }

  34. Implementation inheritance issues • Implementation inheritance couples subclass method implementation to superclass even if no data is exposed. • Any superclass implementation changes may break the subclasses. • Protected member variables are worse! • Don't make any assumptions about or use artifacts of superclass implementation • Better yet, consider using interfaces.

  35. Implementation inheritance issues (contd) public class Base { public void foo() { ... } ... } public class Foo extends Base { public void foo() { ... } public boolean equals(Object obj) { ... } ... } // foo1 and foo2 are two instances of Foo and foo1.equals(foo2) returns true. Hashtable htbl = new Hashtable().put(foo1, “Foo 1”); Now, htbl.get(foo2) should return “Foo 1”. But it returns null. Why?

  36. Abstract base classes and interfaces • Combine interfaces with abstract base classes to get the best of both worlds • Implement an interface with default implementation in an abstract base class. • Class hierarchy is used as a common code repository. If the implementation changes radically, you can go back to using the interface.

  37. Abstract base class and interfaces (contd) public interface RTSPClient { void setup(..); void teardown(..); void play(..); void pause(..); void describe(..); ... } public abstract class AbstractRTSPClient implements RTSPClient { abstract void setup(...); // other methods as well. // connection management, keep alive timer impl. Etc. }

  38. Abstract base classes and interfaces (contd) public class NcubeRTSPClient extends AbstractRTSPClient { void setup(...) { // compose request headers // send request and receive response // activate keep-alive based on session timeout in response // update state machine } } public class BitBandRTSPClient implements RTSPClient { void setup() { bitband_setup(...); } native void bitband_setup(...); // RTSP and more is done in native library }

  39. References • OODP/Java design articles by Bill Venners at http://www.artima.com • OODP/Java design articles by Allen Holub at http://www.holub.com • OODP series at http://www.javaworld.com/channel_content/jw-oop-index.shtml

More Related