Transcript Chapter 20
Chapter 20
Principles of Package Design
Packages: Tools for High-level Organization
Classes are too fine-grained to organize large
applications
Six principles for using packages
Three for package cohesion – help us decide how to
allocate classes to packages
Three for package coupling – determine how
packages should be interrelated. Include
Dependency Management (DM) metrics to measure
and characterize designs.
Designing with packages
1.
2.
3.
4.
5.
What are the principles for allocating classes to
package?
What design principles govern the relationships
between packages?
Should packages be designed before classes (top
down) or should classes be designed before
packages (bottom up)?
How are packages physically represented? In C++?
In Java? In development environment?
Once created, to what purpose will we put these
packages?
Reuse-Release Equivalence Principle (REP)
The granule of reuse is the granule of release.
Anything we reuse must be released and tracked.
We want author to:
maintain code (otherwise too much effort for us!)
notify us if plans to change it, give option to refuse new
version
Consider the internal contents of package from
point of view of potential reusers. Either all of the
classes in a package are reusable or none of them
are.
Should also consider who reuser is. Container is
reusable. Financial framework is reusable. Should
not be in same package.
Common-Reuse Principle (CRP)
The classes in a package are reused together. If you
reuse one of the classes in a package, you reuse
them all.
Classes generally collaborate; these belong in same
package. Example: container + iterators.
Important point: what classes not to put in package.
If class only uses one class within package, still a
dependency, still must be re-tested every time
package is modified and released.
Often put package into jar. If put too much in jar,
then every time anything changes, jar must be
redistributed, all dependent classes re-tested. Waste
of time.
CRP = tightly bound
Common-Closure Principle (CCP)
The classes in a package should be closed together
against the same kinds of changes. A change that
affects a package affects all classes in that
package and no other packages.
Single-Responsibility Principle for packages.
Maintainability is often more important than
reusability. If change occurs, would prefer it is all
within one package. If two classes are so tightly
bound they always change together, they belong in
the same package.
Acyclic-Dependencies Principle (ADP)
Allow no cycles in the package-dependency graph.
“Morning-after syndrome” – work all day, get some
stuff working, go home, arrive next morning to find
your stuff no longer works. Why? Someone else
stayed later than you and changed something you
depend on.
Releasable packages. Checked out by developer.
Update, test, give new release number, move back
for others to use. Teams can choose when to move
to new release. No team is at the mercy of others.
Integration happens in small increments. But there
can be no cycles.
Package Structure as Directed (Acyclic) Graph
MyApplication
Message
Window
Task
Window
MyTasks
Database
Tasks
MyDialogs
Windows
Follow the dependency arrows backwards to see who is affected by any change.
e.g., change MyDialogs, MyTasks and MyApplication will be affected. Those teams
can decide when to get new release. Other packages not affected. To test change
to MyDialogs, compile and link with Windows package.
System is released from bottom up (Windows, then Tasks & MyDialogs, etc.)
Package Structure with a cycle
MyApplication
Message
Window
Task
Window
MyTasks
Database
Tasks
MyDialogs
Windows
What if change in requirement made MyDialogs depend on MyApplication?
MyTasks now depends on every other package in the system. In fact, MyTasks,
MyApplication and MyDialogs must always be released at the same time. Must
do a complete build just to test MyDialogs!! May be no correct order for build.
What to do??
1.
2.
Break the cycle. Two primary mechanisms:
Apply the Dependency-Inversion Principle
(DIP). Could create abstract base class
with interface MyDialog needs, inherit it
into MyApplication.
Create a new package on which both
MyDialogs and MyApplication depend.
Move the class(es) they both depend on
into that new package.
See figures of these in text!
Package Considerations
Package structure cannot be designed from the top
down
Structure will evolve as system grows and changes
Package dependency diagrams have very little to do
with describing the function of the application.
Instead they are a map to the buildability of the
application.
If we try to design package dependency structure
before we design any classes, likely to fail rather
badly! Package structure needs to grow along with
the logical design of the system.
Stable-Dependencies Principle (SDP)
Depend in the direction of stability.
Any package that we expect to be volatile should not be
depended on by a package that is difficult to change.
Stability is related to the amount of work required to make a
change. One way to make a package difficult to change is to
make lots of other packages depend on it.
x
x is responsible to 3 packages, needs to be stable.
x depends on nothing, it is independent.
An instable package
y
y is irresponsible and dependent
Stability continued
Stability Metrics
Ca – Afferent Couplings. Number of classes
outside this package that depend on classes
within this package.
Ce – Efferent Couplings. Number of classes
inside this package that depend on classes
outside this package.
I – instability. I = Ce / (Ca + Ce)
Stability Calculation
Pb
Pa
q
s
r
Pd
Pc
t
SDP says I metric should be larger
than the I metrics of packages it
depends on.
u
v
Pc Stability. Ca = 3 (classes q, r and s). Ce = 1 (u depends on v). I = 1/(1+3) = ¼
Easy to calculate based on #include statements in C++.
If I=1, no packages depend on this one (Ca = 0) and this package does depend on
others. This is instable. No reason not to change, dependencies may give plenty of
reason to change.
I = 0 means package is depended upon by others (Ca > 0) but it does not depend on
any others. This is responsible and independent.
Not all packages should be stable
If all packages were maximally stable, the
system would be unchangeable.
Want changeable packages to depend on
stable packages.
I=1
Instable
Instable
I=1
Stable
I=0
High-level architecture and design decisions
are typically not volatile, should be in stable
package.
Stable-Abstractions Principle (SAP)
A package should be as abstract as it is
stable.
A stable package should be abstract so that
its stability does not prevent it from being
extended.
An instable package should be concrete.
Measuring Abstractness
Nc
= Number of classes in package
Na = Number of abstract classes in package
A = Abstractness = Na/Nc. Ranges from 0 to 1.
Main Sequence
(0,1)
Zone of
Uselessness
maximally abstract but
no dependents
A
Zone
of Pain
highly stable
& concrete
database schemas
often fall here!
I
(1,0)
Chapter 21
FACTORY
Using new violates DIP
Dependency-Inversion Principle tells us to prefer
dependencies on abstract classes and avoid
dependencies on concrete classes. Consider the
statement:
Circle c = new Circle(origin, 1);
Circle is concrete. Any class that creates a new
Circle violates DIP. In fact, any Java statement that
uses new violates DIP.
Sometimes may be harmless (and may be
necessary), but the more likely a concrete class is
to change, the more likely depending on it will lead
to trouble.
Common Example
<<creates>>
Circle
Some App
<<interface>>
Shape
SomeApp depends on interface Shape,
which is good – but it must also create
instances of Circle and Square objects. If
Circle and Square objects change, must
recompile/test SomeApp.
Square
Some App
<<interface>>
ShapeFactory
+makeSquare()
+makeCircle()
ShapeFactory
Implementation
<<creates>>
<<interface>>
Shape
Circle
Here the application depends on
the ShapeFactoryImplementation
All code to create concrete classes
is encapsulated in ShapeFactory.
Problem: would require recompilation
every time a new type is added.
Square
Solution: one make function that
takes a String as argument.
Sample Factory Code – first attempt
public interface ShapeFactory
{
public Shape makeCircle();
public Shape makeSquare();
}
public class ShapeFactoryImplementation implements ShapeFactory
{
public Shape makeCircle(){
return new Circle();
}
public Shape makeSquare(){
return new Square();
}
}
Sample Factory Code – with string parameter
public Shape make(String shapeName) throws Exception
{
if (shapeName.equals("Circle"))
return new Circle();
else if (shapeName.equals("Square"))
return new Square();
else
throw new Exception("ShapeFactory cannot create “
+ shapeName);
}
If add new type:
- better, because interface doesn’t change
- make code will still change every time a new type is added
Could use Java reflection, pass in a default object instead of a string
Substitutable Factories
Benefit of factories: Can substitute one implementation of a
factory for another.
<<interface>>
Employee
Factory
+ makeEmp
+ makeTimecard
Application
<<interface>>
Employee
Oracle
Employee
Proxy
<<interface>>
TimeCard
Oracle
TimeCard
Proxy
instantiate Oracle
factory, get Oracle
objects.
instantiate FlatFile
factory, get FlatFile
Objects
Oracle
Employee
Factory
<<creates>>
FlatFile
Employee
Proxy
FlatFile
TimeCard
Proxy
FlatFile
Employee
Factory
Helps to keep one code base!
<<creates>>
When to use factories…
Strict interpretation of DIP would use for
every volatile class.
Not recommended, factories can be
complex.
Start out without factories, put them in
when need becomes apparent. If becomes
necessary to use PROXY*, will probably want
a FACTORY.
If you need thorough unit testing and want
to “spoof” creation of an object, create a
FACTORY.
*PROXY knows how to read particular objects out of particular kinds of databases
Chapter 22
Payroll Case Study (Part 2)
Problems with initial Payroll Design
All program files are in a single directory
No higher-order structure at all
No packages, no subsystems, no releasable
units other than the entire application
Need to make it more convenient for
multiple developers
requires
packages that can conveniently be
checked out, modified and tested
HOW should we organize this application?
Package structure and notation
One possible package structure for Payroll
Payroll
Application
+ PayrollApplication
+ TransactionSource
+ TextParserTransactionSource
Application
Transactions
+ Transaction
+ AllDerivatives
Payroll
Database
+ Application
indicate dependencies
+ PayrollDatabase
+ Employee
Methods
+ PaymentMethod
+ (All Derivatives)
Schedules
+ PaymentSchedule
+ (All Derivatives)
Classifications
+ PaymentClassification
+ (All Derivatives)
+ TimeCard
+ SalesReceipt
Affiliations
+ Affiliation
+ UnionAffiliation
+ ServiceCharge
Package Analysis
Done ad-hoc (classes that look like they belong together in
same package)
Is it effective? Consider what happens if Classifications
package is changed. Must recompile and test PayrollDatabase
(reasonable). Must also recompile and test Transactions.
Reasonable to review ChangeClassificationTransaction. But
really necessary for all the others? At minimum package
needs to be rereleased and redeployed.
Issue: classes in Transactions package do not share the same
closure. Each is sensitive to its own changes.
ServiceChargeTransaction -> ServiceCharge.
TimeCardTransaction -> TimeCard. etc.
PayrollApplication package even more sensitive. Any change
to any part of the system will affect it. May seem like this is
inevitable for package this high in hierarchy, but not true!
Applying the Common-Closure Principle
This diagram groups classes according to their closure
TextParser
+ TextParserTransactionSource
Payroll
Application
+ PayrollApplication
+ TransactionSource
Classifications
+ HourlyClassification
+ CommissionedClassification
+ SalariedClassification
+ ChangeClassficationTransaction
& Derivatives
+ TimeCard
+ SalesReceipt
Payroll Domain
Methods
+ MailMethod
+ HoldMethod
+ DirectMethod
ChangeMethodTransaction
& Derivatives
+ Employee
+ Affiliation
+ PayrollClassification
+ PayrollSchedule
+ Affiliations
+ PayrollMethod
+ Transaction
Schedules
+ WeeklySchedule
+ MonthlySchedule
+ BiweeklySchedule
+ ChangeScheduleTransaction
& Derivatives
Application
+ Application
irresponsible
responsible &
independent
Affiliations
+ UnionAffiliation
+ ServiceCharge
+ ChangeAffiliationTransaction
& Derivatives
Payroll
Database
+ PayrollDatabase
New structure analysis
PayrollDomain contains the essence of the whole system, but
it depends on nothing. How? Nearly all of the classes are
abstract.
Classification contains its 3 derivatives + related transaction
class + 2 inputs (SalesReceipt & TimeCard). Only dependency
is TextParser. Similar analysis for Methods, Schedules and
Affiliations.
Bulk of executable code is in packages with no dependencies.
Most general packages contain the least amount of
executable code. Heavily depended upon (responsible) but
don’t depend on anything (independent).
Hallmark of OOD: highly independent & responsible
generalities at the bottom, highly irresponsible and
dependent details at the top.
Reuse-Release Equivalency Principle (REP)
What portions of code can be reused?
For a division of company with different policies,
could not reuse Classifications, Methods, Schedules
or Affiliations. Could reuse PayrollDomain,
PayrollApplication, PayrollDatabase, possibly
PDImplentation.
If another division just wanted software to analyze
current employee database, could reuse
PayrollDomain, Classifications, Methods, Schedules,
Affiliations, PayrollDatabase and
PDImplementation.
In each case, granule of reuse is a package.
Individual classes not typically reused because
classes within a package depend on one another.
Reuse should not force us to modify the reused
components.
Metrics
Tom DeMarco: You can’t manage what you
can’t control, and you can’t control what
you don’t measure.
Idea: gather metrics about designs,
correlate those metrics with measured
performance of software and development
teams.
Metrics may be calculated by hand or by a
variety of automatic tools.
Metrics
Relational Cohesion. Average number of internal
relationships per class. R = class relationships, N =
number of classes in package. +1 prevents H=0
when N=1.
Afferent coupling. Number of classes from other
packages that depend on classes within the subject
package. Dependencies are class relationships such
as inheritance and association.
H=(R+1)/N
Ca
Efferent coupling. Number of classes in other
packages that the classes in the subject package
depend on.
Ce
Metrics, continued
Abstractness or Generality. Ratio of number
of abstract classes (or interfaces) in a
package to total number of classes (and
interfaces) in the package. Ranges from 0 to
1.
A
= AbstractClasses/TotalClasses
Instability. Ratio of efferent coupling to total
coupling.
I
= Ce/(Ce+Ca)
Metrics, continued
Distance from Main Sequence. Main
sequence is idealized by line A + I = 1. D is
distance from that line. Range ~.7 to 0,
closer to 0 is better.
D
= |A + I – 1|/sqrt(2)
Normalized Distance from Main Sequence.
Restates distance in range [0,1]. 0 is
coincident with main sequence, 1 is max
distance from main sequence.
D’
= |A + I – 1|
Questions
Answer questions for Chapter 22.