Exploring Dependencies


Unless your system is simple, no one subsystem does everything. Each subsystem must rely on services supplied by other subsystems to get its own job accomplished. When one subsystem can’t do its work without relying on another subsystem, you have dependency.

An example of dependency occurs in the air-filter business example: The Order Handling subsystem must rely on the classes inside the Common Objects subsystem to generate a product order. If you were to make a change to the operations of the AirFilter, Customer, or CustomerAccount classes, then you would have to change classes inside the Order Handling subsystem too.

The dependencies among your subsystems come in three flavors (Figure 20-4 illustrates these flavors):

  • Dependent: If one of your subsystems depends on the contents or interfaces of another subsystem, but not the other way around, this is the simple case of one-way dependency. The Order Handling subsystem is dependent on the Common Object subsystem. (You hear experts refer to client-supplier, or client-server dependency. These are just other terms for one-way dependency.)

    Show one-way dependency as a dashed line that connects two subsystems; include an arrowhead that points from the dependent subsystem to the subsystem it depends on. You can show dependencies among packages in the same way.

  • Codependent: Two subsystems are codependent or two-way dependent when they depend on each other. If a class in the Common Object subsystem must have access to a class in the Order Handling subsystem and some other class in the Order Handling subsystem needed access to yet other classes in Common Objects, then we have a two-way dependency (also known by its fancier name, peer-to-peer dependency).

    You show two-way dependency as two separate dependency lines that connect the same two subsystems but go in opposite directions. (A more informal notation for codependency is a single dashed line that has arrowheads at both ends.)

  • Independent: If you have two subsystems that have no dependency between them, they are called independent. In the air-filter example, the Persistent Store subsystem and the Accounting Interface Subsystem have no dependencies between them.

    You show that two subsystems are independent by not connecting them with dependency lines.

 Warning   If you want a maintainable system, avoid codependency among your subsystems. Dependency means that a change in a subsystem may lead to a change in the dependent system. But, codependency is worse. A change in one codependent subsystem may lead to a change in the other codependent subsystem, which in turn could lead to a change in the first codependent subsystem.

After you have your subsystems, consider the dependencies among them: Build a diagram that shows your design-time subsystems and packages, using dashed-lines-with-arrows to indicate each subsystem’s dependency on other subsystems and packages. While you’re exploring these dependencies, consider the degree of coupling and cohesion present in each subsystem:

  • Coupling: A highly coupled subsystem has many dependencies.

  • Cohesion: A highly coherent subsystem has all the classes it needs to meet its assigned responsibility.

To increase or decrease coupling and cohesion among subsystems, you move classes from one subsystem to another until you find the right balance.

 Tip   Look for codependent (two-way-dependent) subsystems—and try to make them one-way-dependent. You can do this by moving classes from one subsystem to another or by creating a subsystem that holds only the common classes. Architectural patterns (discussed at the end of this chapter) can also help you break the cycle of codependency.

 Remember   Every system you build has some amount of coupling and some degree of cohesion. But, the desired levels of coupling and cohesion depend on your design priorities and goals. Those goals relate to functional requirements, performance, cost, and schedule. You find more information on design priorities in Chapter 19.

Before you design your system, consider your design priorities. As you perform the design tasks, keep an eye on coupling and cohesion. Adjust your design to obtain the right level of coupling and cohesion to meet the design concerns.

Diagramming dependencies

Figure 20-4 illustrates a design diagram with subsystems, packages, and dependencies for the air-filter business example. We like to put the user- oriented (use case) subsystems at the top and low-level service-oriented subsystems at the bottom of our dependency diagrams.


Figure 20-4: Diagram showing dependencies among subsystems.

At the top of the diagram in Figure 20-4, you see the two subsystems that interface with the Order Clerk and Accountant actors—Order Handling and Billing. Order Handling depends on Billing because Billing contains the included use case Check Credit Card. Order Handling and Billing both depend on Common Objects. The Billing subsystem must access the Accounting System Interface subsystem, so there is a dependency there too.

All classes in the Common Objects subsystem—such as Customer, AirFilter, SupplierAccount, and CreditCard (not shown in the figure)—must be saved in a database. So, the Common Objects subsystem depends on the Persistent Store subsystem—and on the definitions of abstract datatypes and enumerations contained in the Domain-Datatypes package.

Notice that the Security subsystem has a property {visibility = Global}. That means that all the other subsystems may depend on the Security subsystem because it’s globally available to all parts of the system.

start sidebar
Considering coupling and cohesion

There are many possible design solutions for your system. Each solution has good points and bad points. We use the concepts of coupling and cohesion to figure out how well any particular design solution meets our design priorities. In and of themselves coupling and cohesion are neither good nor bad—but they can tell you a lot about how your system approaches its work—and whether you want to change that.

The concept of coupling expresses how interconnected the parts of our system are—in effect, how interconnected such parts as classes or subsystems are. A class with six associations is more coupled than a class with two associations. A subsystem that depends on another subsystem is more coupled than a subsystem with no dependencies. Another way to think about coupling is to consider how much an instance of a class must “know” about its surroundings. The more an object must know about other objects’ methods, the higher the coupling.

Cohesion, on the other hand, expresses how well all the internal parts of a class or subsystem work together. If a class must have every one of its attributes and operations in order to work, the class is highly cohesive. However, if a couple of attributes are only used with one operation and another few attributes are only in use by a different operation, the class has lower cohesion within the class. When all classes inside a subsystem work together to accomplish the tasks required of the subsystem, then the subsystem is highly cohesive. However if a subsystem has several groups of classes where each group works independently of each other, then the subsystem is less cohesive.

Suppose you’re designing a system to be flexible. A flexible design enables you to make changes in one subsystem without affecting or changing other subsystems. Flexible systems call for a modular design with high cohesion and low coupling. Subsystems with high cohesion are replaceable—and if they have a low degree of coupling, fewer changes are needed; the result is more modularity. If your subsystem exhibits high coupling, that means it’s dependent on many other subsystems. In a highly coupled system, chances are that a change in one subsystem leads to changes in other subsystems.

But, if you’re designing a system for performance, you tend to increase the coupling and lower the cohesion of your classes and subsystems. That way when an object must get data quickly, it goes directly to an object that can provide that data instead of indirectly through many interfaces. For example, you have an object that needs data from a database you have design options. You could have the object invoke the behavior of a generic database interface object. Or, you could write the access code right into a method in the object that needs the data. The second option ties your object directly to the database but, it performs faster.

end sidebar

 Tip   Instead of drawing a lot of dependency lines from just about every subsystem to just one commonly needed subsystem, use the global-visibility property instead.

Importing what you need

As you work on a subsystem, you come to a point at which you need the services of a class that resides in another subsystem or package. You have two choices:

  • Invoke an interface: When you call an operation on the subsystem where the needed class resides, that operation invokes the needed class. Suppose (for example) you’re using the façade design pattern (more on façade in the section “Using other architectural patterns,” at the end of this chapter) to make this happen: When an instance of the Customer class changes, an update must be made to the database. You can design the Customer class to invoke the interface operation store(this) on the Persistent Store subsystem. The internal elements of the Persistent Store subsystem then get to work storing the data from the Customer instance in tables in the database.

  • Import the class: You import the class right into the subsystem that must use the class, making it appear as if the imported class is inside the subsystem that needs it. The imported class is still owned by the package or subsystem from which you imported it, but you can use it directly. The AirFilter class needs the Pound (weight in pounds) abstract datatype that resides in the Domain - Datatype package. By importing the Domain-Datatype package into the Product subsystem, you can treat the Pound class definition as if it were inside the Product subsystem.

You import elements from other subsystems so that their visibility is either public or private. You make the contents of another package or subsystem public in another subsystem by using the «import» stereotype on a dashed dependency line. You make the insides of another package or subsystem private in another subsystem by using the «access» stereotype on a dashed dependency line.

Figure 20-5 illustrates what happens when you use the «import» and «access» stereotypes. On the left side of the figure, you import the contents of Domain - Datatypes into Product and make them publicly visible to other subsystems. So, when the Order Handling subsystem imports Product, it also imports the elements originally in the Domain - Datatypes package.

However, the situation is quite different on the right side of Figure 20-5. You “access” the contents of Domain - Datatypes and make them private—hidden from other subsystems. As a result, when the Order Handling subsystem imports Product, it does not import the elements originally in the Domain - Datatypes package and may not use them.


Figure 20-5: Importing subsystems into other subsystems.

Merging what you have

Suppose you realize that two subsystems are almost identical. They have about the same number of classes, the names of the classes are similar, and relationships between the classes are almost identical. To solve this problem, UML provides a special dependency called merge that works like inheritance. A package or subsystem merges the contents of another package or subsystem by inheriting its contents within its own scope. To indicate merging, attach the «merge» stereotype to the dependency line.

For example, in the air-filter business example we have similar subsystems—Customer and Supplier. Both subsystems have an account. In the Customer subsystem it’s called CustomerAccount and in the Supplier subsystem it’s called SupplierAccount. Each type of account is backed up by a line of credit. In the Customer subsystem, the customer’s credit card provides the line of credit. The business sends invoices to customers and receives invoices from suppliers. In both cases, the invoice is paid through the respective account—Customer or Supplier Account. There must be a way to simplify this situation. Figures 20-6 and 20-7 illustrate one such solution: merge.

When you see common classes and associations in different subsystems, create a subsystem that contains their commonality. Figure 20-6 shows a new subsystem called ClientAccount. The ClientAccount holds generic classes such as Client, Account, LineOfCredit, and Invoice. The Customer and Supplier subsystems are shown merging the ClientAccount.


Figure 20-6: Merging subsystem from another subsystem

Figure 20-7 shows the classes internal to the Supplier subsystem as a result of merging the ClientAccount subsystem. The supplier contains its own


Figure 20-7: Illustration of merged subsystem's internal classes.

Invoice, Client, Account and LineOfCredit classes. These classes play specific roles in association with each other. The Invoice class plays the role of payable in this subsystem. In the Customer subsystem (not shown), the Invoice plays the role of receivable.

Notice that Figure 20-7 shows some classes and associations as gray. We did that to illustrate how the merge dependency leads to inheritance. (You would not show those gray elements in your diagram. When you merge another subsystem or package those gray elements are implicitly there.) For example, the Invoice class owned by the Supplier subsystem is a subclass of the Invoice class owned by the ClientAccount subsystem—ClientAccount::Invoice.

You add attributes and operations specific to the classes in the Supplier subsystem and inherit the generic attributes and operations from the classes defined in the ClientAccount subsystem. The associations are also inherited. You see the bills association inherits from the gray bills association. As a consequence the bills association inherits the multiplicities (0..1 and 0..*) and role names (Invoice and Account). We have changed the role name from Invoice to payable. UML allows us to indicate that we’re redefining the role name by showing the new role name as payable and the old (inherited) role name as [Invoice].




UML 2 for Dummies
UML 2 For Dummies
ISBN: 0764526146
EAN: 2147483647
Year: 2006
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net