OOP Best Practices
Object-oriented programming (OOP for short) is the programming paradigm of the 1990s. Many of the programming languages used today are either fundamentally object-oriented (Java, Eiffel, SmallTalk) or have been provided with object-oriented extensions over time (Basic, Pascal, ADA). Even some script languages allow access to (sometimes predefined) objects or have object-oriented properties (JavaScript, Python). Object-oriented programming was one of the "silver bullets" that should lead the software industry out of its crisis and lead to more robust, error-free and easier to maintain programs. The following pictures shows the four layers of the OO design pyramid:
- Subsystem layer contains a representation of each of the subsystems that:
- enable the software to achieve its customer-defined requirements
- implement the technical infrastructure that supports customer requirements.
- Class and object layer contains:
- class hierarchies that enable system to be created using generalizations
- representations of each object.
- Message layer :
- contains the design details that enable each object to communicate with its collaborators
- establishes the external and internal interfaces for the system.
- Responsibilities layer contains the data structure and algorithmic design for all attributes and operations for each object.
Design Issues
Criterias for judging a design method's ability to achieve modularity:
- Decomposability: the facility with which a design method helps the designer to decompose a large problem into subproblems that are easier to solve.
- Composability: the degree to which a design method ensures that program components (modules), once designed and built, can be reused to create other systems.
- Understandability: the ease with which a program component can be understood without reference to other information or other modules.
- Continuity: the ability to make small changes in a program and have these changes manifest themselves with corresponding changes in just one or a very few modules.
- Protection: an architectural characteristic that will reduce the propagation of side effects if an error does occur in a given module.
Basic design principles that can be derived for modular architectures:
- linguistic modular units: programming language used should be capable of supporting the modularity defined directly
- few interfaces: to achieve low coupling - the number of interfaces between modules should be minimized
- small interfaces (weak coupling): to achieve low coupling - the amount of information that moves across an interface should be minimized
- explicit interfaces: components should communicate in an obvious and direct way
- information hiding: all information about a component is hidden from outside access
Generic Steps
To perform object-oriented design, a software engineer should perform the following generic steps:
- Describe each subsystem and allocate it to processors and tasks.
- Choose a design strategy for implementing data management, interface support, and task management.
- Design an appropriate control mechanism for the system.
- Perform object design by creating a procedural representation for each operation and data structures for class attributes.
- Perform message design using collaborations between objects and object relationships.
- Create the messaging model.
- Review the design model and iterate as required.
note that the design steps are iterative.
The most basic OO principles include encapsulation, inheritance, and polymorphism, abstraction, association, aggregation, and composition; they form the foundation of the OO approach. These basic principles rest on a concept of objects that depicts real-world entities such as, say, books, customers, invoices, or birds. These classes can be considered templates for object instantiation, or object types. In other words, class specifies what an object can do (behavior) and defines patterns for its data (state). Modeling the real world in terms of an object's state and behavior is the goal of the OO approach. In this case, state is represented by a set of object attributes, or data, and behavior is represented by the object's methods.
- A class is:
- a module (a chunk of executable code)
- a type definition (describes data, behavior for a type of objects)
- An object has:
- identity (memory address)
- state (fields)
- behavior (methods)
- public interface (user's view)
Best Practices
Encapsulation
All data should be hidden within its class.
hiding the private state and behavior within a class and its objects. * encapsulation promotes abstraction: ability to focus on task at hand and not on specific unnecessary details * if data is public, it can never be changed (it becomes part of published interface) * public data exposes the client to unnecessary details about how the class is implemented
Dependency on clients
Users of a class must be dependent on its public interface, but a class should not be dependent on its users.
Example: GradeCalculator class with 3 methods:
readGradeInput, computeGrades, showGradeOutput
if the 3 methods are not called in exactly this order, GradeCalculator code crashes!
the possible solutions are:
* have computeGrades
or showGradeOutput
call their preceding methods if that hasn't been done already
* combine these methods into only one public method that calls all 3 in order
Number of methods
Minimize the number of methods of a class.
Example: a LinkedList class with too many methods makes it hard to find a common operation, such as merge with another linked list.
Minimal public methods
Implement a minimal public interface that all classes understand.
Example: implement toString
for all classes
Example: implement equals
and compareTo
as appropriate
Private methods
Do not put implementation details such as common-code private functions into the public methods of a class.
Public methods confusion
Do not clutter the public methods of a class with items that users of that class are not able to use or are not interested in using.
Example: LinkedList
class has add and remove methods that both advance the list to the proper linked node to add/remove, then performs the operation. A correct design could be as follows:
* use a common helper named getNodeAt(int index)
that advances the list to the proper node and returns that node
* make the helper getNodeAt
a private method so client code does not see it and cannot call it
Reducing public methods
Some ways to minimize a class's methods:
- Make a method private unless it needs to be public.
- Only supply getters (not setters) for fields' values if you can get away with it.
- example: Card object with rank and suit (get-only)
- When writing a class that wraps up a data structure, don't replicate that data structure's entire API; only expose the parts you absolutely need.
- example: Deck class holds a list of cards, but the game only shuffles and draws the top card, so rather than having a getCard(int), add, remove, etc., just have shuffle() and drawTopCard() methods
- example: If your Game has an inner list or map of Players, supply just an Iterator or a getPlayerByName(String) method
- Use a Java interface that holds only the needed methods from the class, and then refer to your class by the interface type in client code.
Cohesion
Cohesion means how complete and closely related things are within your class (the more the cohesion, the better the design).
A class should capture one and only one key abstraction.
Example (bad one): PokerGame class that holds all the players, holds an array representing the card deck, stores all bets and money, does the logic for each betting round, etc.
Reducing Coupling
Coupling means how much classes are connected to / depend on each other (the less the coupling, the better the design): * nil coupling: no connection at all * export coupling: one class depends on public interface of another * overt coupling: one class uses another's innards legally (Java subclasses using protected stuff) * covert coupling: using another class's inner details without permission (hard to do this in Java)
Classes should only exhibit nil or export coupling with other classes, that is, a class should only use operations in the public interface of another class or have nothing to do with that class.
Some ways to reduce coupling: * combine two classes that don't represent a whole abstraction * make a coupled class an inner class * example: list and list iterator * example: GUI frame and event listener * provide more unified communication points between subsystems, so that one subsystem does not need to communicate with every piece of another subsystem
Related data and behavior
Keep related data and behavior in one place.
This avoids having to change two places when one change is needed.
Spin off nonrelated behavior into another class (i.e., noncommunicating behavior).
Roles of objects
Be sure the abstractions that you model are classes and not simply the roles objects play.
- example: To model a family tree, should we make classes for Father, Mother?
- example: Should we make a Club, Diamond, Heart, Spade class to represent each suit of cards?
- example: Should we make a FirstBetter class that represents the player who is currently betting first in our card game?
Interfaces
Interfaces should be used when you want: * Polymorphism: ability to treat different types of objects the same way (can call same method on two objects and get different results, based on their types) * no code sharing (each class implements the methods in the interface in its own way)
- Example:
- Java's collection framework:
- List --> LinkedList, ArrayList
- Map --> HashMap, TreeMap
- Iterator
- Java's Comparable interface
- Java's collection framework:
Inheritance
inheritance should be used when you want: * code sharing: subclass's object receives all code from superclass's objects; those methods can now be called on it * substitutability: client code can use subclass's object in any situation that a superclass object was expected, and expect the same results to occur
example: Java Swing JComponent
class:
* a superclass that represents any graphical component
* stores common features like size, location, color, and font
* JButton
, JPanel
, etc. extend JComponent
to share these features.
* any JComponent
may be substituted for another in a variety of cases, such as being added to a frame on the screen
God class
God class is a class that hoards too much of the data or functionality of a system. A god class reduces the modularity.
Distribute system intelligence horizontally as uniformly as possible, that is, the top-level classes in a design should share the work uniformly.
Do not create god classes/objects in your system. Be very suspicious of a class whose name contains Driver, Manager, System, or Subsystem.
Forms of god classes
- behavioral god class: holds too much logic often has a name ending with "System", "Manager"
- data god class: holds too much information often has a bunch of access/search methods.
Eliminating god classes
Beware of classes that have many accessor methods defined in their public interface. Having many implies that related data and behavior are not being kept in one place. * location of policy behavior should be in the place where that policy is enforced/enacted * lots of accessors are okay on a class with important data that needs to be shown by a GUI
Non-communicating behavior
Beware of classes that have too much non-communicating behavior, that is, methods that operate on a proper subset of the data members of a class. God classes often exhibit much non-communicating behavior. Model and view
Controller classes
A controller class is a class that only contains behavior (might contain some fields, but they are superfluous).
* usually grabs state from other classes and acts on it
* often has an -er
or -or
verb phrase as its name, such as DataLoader
, PasswordChecker
* similar to a behavioral god class on a smaller scale
Problems with controller classes are: * data (entity) and behavior (control) should be together, not in separate classes * controller classes are basically the action-oriented paradigm * it becomes difficult to ask a piece of data, what functionality depends on you?
In the real world, people dislike controllers. For example microwave, radio, car have data and behavior together.
Model and View
- Model: classes in your system that are related to the internal representation of the state of the system
- often part of the model is connected to file(s) or database(s)
- examples (card game): Card, Deck, Player
- examples (bank system): Account, User, UserList
- View: classes in your system that display the state of the model to the user
- generally, this is your GUI (could also be a text UI)
- should not contain crucial application data
- Different views can represent the same data in different ways (example: bar chart vs. pie chart)
- examples: PokerPanel, BankApplet
Model-View-Controller
Model-View-Controller (MVC) is a common design paradigm for graphical systems: * controller classes that connect model and view * defines how user interface reacts to user input (events) * receives messages from view (where events come from) * sends messages to model (tells what data to display)
Advantages of MVC
These are the advantages of MVC pattern:
- decreases coupling
- simplifies complex user interface code
- multiple controllers may be defined based on desired behavior
- changes to part can affect others without requiring the changed object to know details of others
- increases cohesion
- only the view is concerned with pixels and screen-based data
- improved flexibility
- new views can be added without changing model
- change the feel (Controller) without changing the look (view)
- increases reuse
- one model can be represented in several ways
Model-View separation
In applications that consist of an object-oriented model interacting with a user interface, the model should never be dependent on the interface. The interface should be dependent on the model.
Example (bad): Making a system with lots of classes that know how to render themselves; the GUI just calls card.draw(), deck.draw(), player.draw(). GUI should render them, not delegate!
The behavior of drawing things is related to the GUI, because it is part of the GUI's appearance. So it belongs in the GUI classes. Having things draw themselves locks them in to one representation.
Proliferation of classes
When object-oriented design leads us to design a system that has too many classes that are too small in size and scope, making the system hard to use, debug, and maintain.
Eliminate irrelevant classes from your design.
irrelevant classes often have only get/set methods
Eliminate classes that are outside the system.
don't model behavior of a blender just because you sell blenders; don't model a user just because the system is used
Do not turn an operation into a class.
Be suspicious of any class whose name is a verb, especially those that have only one piece of meaningful behavior. Ask if that piece of behavior needs to be migrated to some existing or undiscovered class.
Be sure the abstractions that you model are classes and not simply the roles objects play.
Agent classes
An agent class is a class that acts as a middle-man to help two or more other classes communicate. * example: Farmer class to link Cow and Milk classes * example: Librarian class to link Book and Shelf classes
Agent classes are often placed in the analysis model of an application. During design time, many agents are found to be irrelevant and should be removed.
What defines whether an agent is relevant? A relevant agent must have some other behavior beyond simply being a middle-man; it must have some useful purpose of its own as well.
Exercise
- Define a set of classes and their major attributes and operations, for a calendar management system.
- Multiple users should be able to use the system to:
- create appointments (either individual or group meetings)
- see an overview of their calendars at various levels of granularity (day, week, month)
- edit or delete existing appointments
- set reminders to go off for an existing appointment
- Appt. data should be persistent (save and load).
- What classes would you use in this system?
- What model would you use? What data would it contain?
- What views of the model(s) would you want?