Design

Initial Designs

Textual Analysis

An easy start-from-nothing techniaue.
Look at your vision statement, feature list, and/or use-cases.

Class Diagram Centered Design

How to do it

Lets assume a group is gathered around a whiteboard.
  • A UML class diagram of the design is drawn on the whiteboard
    • classes interacting with each other are adjacent and are marked with a UML association
    • make any responsibility or action a class needs a method of the class (with an evocative name so you will remember what its supposed to do)
    • Think about "is-a" and "part-of" for inheritance and association edges respectively
    • Putting role names on association edges is helpful, more so than putting fields inside the classes.
  • Different use-cases are played out over this diagram to see what classes have what roles and need what fields/methods
    • The use-cases that need realizing are a key driving point in the design.
    • For each action from the use-cases, make sure that action is assigned to one or more classes (perhaps entailing creation of a new class).
    • In the beginning, start with the key features, not corner cases. See pp 334-... for a discussion of this topic in the book.
  • Iteratevely refine class diagrams and use-cases together
    • Add new classes/methods to implement actions in a new use-case
    • Refine the UML by noticing new or more subtle details in the associations, roles, aggregation, multiplicity, etc.
    • If two classes are similar but not quite the same, make an abstract superclass of the two containing the shared functionality
    • If one class is-another class, consider this class inheriting from the other
    • factor into multiple packages which you can work on independently when you see a clear division emerging
    • Throw out classes with no methods
    • Add steps missing from existing use-cases, add new use cases, split/merge use-cases, etc.

    Evolution of Initial Designs to Code

    The first step is to take the proposed classes and methods and map them on to class templates
    • Create a Java class for each proposed class. Include a brief comment describing the purpose of the class.
    • Turn named roles in the UML that you want to navigate into fields of the class
    • Proposed methods become Java operations;
    • Use JavaDoc comments for describing the attribute/operation purpose when it isn't clear from the name alone.

    Example Initial Design

    We will perform an initial design exercise in class

    Here is one example used in a past OOSE lecture (taken from CRC tutorial ):

    This application will support the operations of a technical library for an R&D organization. This includes the searching for and lending of technical library materials, including books, videos, and technical journals. Users will enter their company ids in order to use the system; and they will enter material ID numbers when checking out and returning items.

    Each borrower can be lent up to five items. Each type of library item can be lent for a different period of time (books 4 weeks, journals 2 weeks, videos 1 week). If returned after their due date, the library user's organization will be charged a fine, based on the type of item( books $1/day, journals $3/day, videos $5/day).

    Materials will be lent to employees with no overdue lendables, fewer than five articles out, and total fines less than $100.

    Some very simple scenarios to help elaborate this example:

    "Johny, who has no accrued fines and has one outstanding book, not overdue, checks out a book entitled "Document, Your job depends on it?"

    "Johny returns the book "Document, Your job depends on it" two days late. He has no other item checked out and no acquired fines."

    "Ivar uses the search feature to look for the book "Object-Oriented Software Engineering"" for which there are two copies available.

    "Grady uses the search feature to look for the video "Object-Oriented Analysis and Design without Applications" of which there is 1 copy but it is checked out".

    "Johnny was fired for goofing off too much in the library, but a week later tries to check out a book"

    Commonality Analysis

    This is a technique you can employ to elucidate finer details of what should be held in common (e.g. in a single class) and what should not for some related family of entities.
    1. (Have some family of entities in mind, e.g. Tanks, Soldier and Airplane units in a war game -- see book p. 356-..)
    2. Try to find a way to make the common bit huge and the variabilities tiny
    3. Describe members in terms of commonalities plus variability.
    --The more stuff held in common the more general the design is.

    Use-cases in Design

    • Requirements use-cases almost always need to be elaborated since the application was not understood well enough at requirements gathering time
    • Forms of Modification
      • Expand on some individual steps
      • Break out too-big steps into their own use cases
      • Add alternative paths
    Some of the book's terminology for use-cases with alternatives
    • The happy path through a use is the main path through the use-case
    • A scenario is one potential way to get through a use-case, which may or may not take alternative paths. (Scenarios are called stories by the XP/Agile school).

    Aside: the Statics/dynamics duality

    • There is a fundamental division of the physical world into static objects, and their dynamic interactions
    • Example: Particle physics is nothing but objects and (dynamic) interactions
    • Example: the fundamental importance of nouns (static objects) and verbs (dynamic actions) to human language.
    • This duality is also important in programming: the classes and their associations are the statics, and the use-cases and methods are the dynamics.
    • The design methodology above can be summarized as
      focus on only one of statics or dynamics at any one point; keep alternating from one to the other

    Dynamic UML Diagrams in Design

    • As we already mention in requirements use-cases, activity diagrams are helpful at the requirements level.
    • Later in the design cycle, sequence diagrams are the the choice method for diagramming complex use-cases, but communication diagrams also may be useful.
    • For larger designs, draw a class diagram showing only the classes involved in the use-case. This focuses the design, good old divide-and-conquer again.
    • State diagrams are partly dynamic and are usually not needed, but are incredibly helpful when the system undergoes global state changes which significantly changes the behavior of the objects.

    We will use several of these diagrams in design examples in class.

    See the course UML notes for details on these diagrams.

    Architecture: packages, components, and deployment

    The three architecture dimensions we discuss are packages (the Java concept), components, and deployment nodes.
    • Packages aka Modules: Dividing software into packages is critical for larger software: complex software has larger logical groupings of code than classes
    • Components: a component has a run-time identity with an explicit run-time interface. For example if you are using a database, that is a separate component.
    • Deployment node: the physical network setup for distributed applications. Components are often remotely deployed.

    There are corresponding UML package, component, and deployment diagrams.

    We will look at some example package, component and deployment diagrams in class to help refine those concepts

    Designing and refining the architecture

    • Obviously, factor classes into packages -- groups of classes which tightly interact amongst each other and loosely interact with outside classes
    • A firm component/deployment model proposal needs to be made early on based on requirements
    • An initial stab at package structuring can start in the initial design, with a great deal of refinement later
    • If the package factoring is not obvious at the start, focus on the underlying classes and activities, and worry about into packages later.

    Design Examples

    We will work through one or more larger design examples in class.

     

    Project Iteration 2: Design

    The details of what you need to do for Iteration 2 will be discussed.

    Design Principles

    Basic Analysis and Design Principles

    Here are the HFOOA&D basic principles, from Chapter 5:
    • Well-designed software is easier to change and extend.
    • Use basic O-O principles such as encapsulation and inheritance to increase flexibility.
    • If design is proving to be not flexible, REFACTOR! "A stitch in time saves nine" applies to software, too.
    • Make classes cohesive: class should have a single, clearly stated purpose which fits all of its fields and methods. Refactor to increase cohesion over the lifecycle: the more code you have to deal with, the more cohesive it needs to be for you to keep the dreaded Bug-Beast under control.

    Avoid Data-centric Design

    One easy trap to fall into is data-centric design.
    • Don't have classes where all its responsibilities are "holds X; holds Y; .." (class is nothing but a data holder)
    • Look carefully at the individual actions in the use-cases and use those things as the methods.
    • One sign of data-centric design is a couple really fat classes doing all the operations in the center of the design and a bunch of tiny classes that just passively hold data on the edge.
    • Data-centric designs should be refactored to push methods from the big class in the center out to the data classes.
          If in doubt, push it out!!
    • C programmers often produce data-centric OO designs: the central class hold all the functions, and the little data classes are like the C structs.
    • Goal at the end is the method should be "right in the middle of the action".

    The Open-Closed Principle (OCP)

    See p.86 of Head First DP's and p. 377 of HFOOA&D.
    Make code which is open for extending, but closed for modifying
    • What this means is to be very careful in your use of inheritance -- either
      • don't allow subclasses at all - declare classes final and use composition to "plug in" the part that incorporates the extension
        (example: JTextField - plug in a different Document to change behavior;
        note JTextField is in fact not final, to support internal framework extentions), or
      • allow subclassing but declare nearly all methods final so subclasses cannot modify them; only a few methods are to be overridden, in contractually circumscribed ways
        (example: JPanel -- only override paintComponent() and a few other things).
    • It makes code more reliable since complex interdependencies don't have random changes injected into them by outsiders.
  • In general any framework design and use has to strictly follow this principle, or debugging will become impossible.

    Don't Repeat Yourself (DRY)

    HFOOA&D p. 382.
    Avoid duplicate code -- abstract out things in common to a single location.
    • The problem is if you don't abstract it out, you have two parallel codebases to try to keep consistent and you often fail.
    • And maybe two copies turns into three turns into four before you realize what is happening.
    Here is an example from the book.

    The Single Responsibility Principle (SRP)

    HFOOA&D p. 390.
    Classes should not have more than one focus of responsibility.

    • This principle is fuzzy as stated above, classes can reasonably be involved in different interactions, it is the focus that is the issue.
    • This principle is similar to the cohesion principle above
    • Another way the principle is stated: a class should only have one axis of change: only one kind of external change will induce a change in the class.
    • Example of violation: if Model, View, and Controller are all in one class, that class has three distinct foci or three axes of change;
      factoring into three classes M-V-C brings the code back in line with this priciple.
    • Data-centric designs always violate this principle: the fat classes in the middle have many foci.
    SRP Analysis: a way to figure out if the methods belong in the class.
    Example:
    class Phone
      dial()
      hangup()
      plugInToWall()
      repairBroken()
      talk()
      openConnection(number)
      closeConnection()
    
    -- for each method X, ask, "is the phone respnsible for X-ing?" If the answer is no, the method doesn't belong.

    The Liskov Substitution Principle (LSP)

    See p. 400 of HFOOA&D.
    Subtypes must be substitutable for their base types
    • In other words, be precise in your use of interfaces and inheritance to conform to the "is-a" specification inherent in them.
    • Note that just because "X is a Y" informally makes sense doesn't mean that X always makes a good subclass of Y; the "is a" also has to make sense for the particular interfaces offered by X and Y.
    • If you violate this principle, your program may behave erratically.
    • Here is one simple example to show the problem of disobeying this principle.

    The Interface Segregation Principle (ISP)

    Clients should not be forced to depend on methods (inherit from or implement) they don't use
    • To be more precise, the bad methods are ones that not only don't they use now, they will never conceivably want to use them because they intuitively "don't belong": the interface is too fat.
    • These extra methods are "junk" and clutter the design space; more fundamentally, they are a sign that methods were not allocated precisely enough and e.g. the Liskov Substitution Principle may also be violated.

    The Dependency Inversion Principle

    See p.139 of Head First DP's. What is it?

    Don't depend on concrete classes, depend on abstractions
    Don't have high-level (user) code directly call/inherit from low-level (library) code; instead,
    1. Library or component publishes an interface (or, if thats not possible, an abstract class)
    2. Users write a class conforming to that interface (or extending abstract class), which then interacts with the other library classes.
    Why?
    • (First, the "normal" method is that the higher-level components directly invoke the lower-level ones; this principle inverts that since the user code now depends on a low-level interface: dependency inversion has taken place)
    • And, this is good because it allows different low-level implementations to be swapped out; as long as they implement the common interface all is well.
    • A more detailed explanation is in Martin's original article on this subject.

    The Principle of Loose Coupling

    See p. 53 of Head First DP's.
    Strive for loosely coupled designs of autonomous, interacting objects
    • Make objects as little aware of each other as possible.
    • Swing Listeners are also an example of this principle: the AWT event system and the user's action code need to know almost nothing about each other besides the methods on the listener.

    The Principle of Least knowledge

    See p. 265 of Head First DP's.
    Talk only to your immediate friends
    • Don't dig deep inside your friends for friends of friends of friends and get in deep conversations with them -- don't do
      friend.pal().longLostChum().letsDiscussHeidegger()
      (talking philisophy with your friend's pal's long lost chum)
    • Code is more convoluted if too many objects are directly interacting with one another. Let the shared friend be an intermediary instead of introducing lots of long-range dependencies.
      friend.pleaseAskYourPalsToReadThis(aHeideggerDiatribe)