370 likes | 462 Views
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
E N D
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 • 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.
MyApplication Message Window Task Window MyTasks Database Tasks MyDialogs Windows Package Structure as Directed (Acyclic) Graph 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.)
MyApplication Message Window Task Window MyTasks Database Tasks MyDialogs Windows Package Structure with a cycle 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. Mustdo a complete build just to test MyDialogs!! May be no correct order for build.
What to do?? • 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)
Pb Pa s q r Pd Pc u v t Stability Calculation SDP says I metric should be larger than the I metrics of packages it depends on. 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. Instable Instable I=1 I=1 Stable High-level architecture and design decisions are typically not volatile, should be in stable package. I=0
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 maximally abstract but no dependents (0,1) Zone of Uselessness A Zone of Pain (1,0) highly stable & concrete database schemas often fall here! I
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>> Some App 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. <<interface>> Shape Circle Square 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. Solution: one make function that takes a String as argument. Some App <<interface>> ShapeFactory +makeSquare() +makeCircle() <<interface>> Shape Circle Square ShapeFactory Implementation <<creates>>
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. instantiate Oracle factory, get Oracle objects. instantiate FlatFile factory, get FlatFile Objects <<interface>> Employee Factory + makeEmp + makeTimecard Application <<interface>> TimeCard <<interface>> Employee Oracle EmployeeProxy Oracle TimeCard Proxy 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?
Payroll Application + PayrollApplication + TransactionSource + TextParserTransactionSource Transactions + Transaction + AllDerivatives Application Payroll Database + Application + PayrollDatabase + Employee Methods Affiliations Classifications Schedules + PaymentMethod + (All Derivatives) + Affiliation + UnionAffiliation + ServiceCharge + PaymentClassification + (All Derivatives) + TimeCard + SalesReceipt + PaymentSchedule + (All Derivatives) Package structure and notation • One possible package structure for Payroll indicate dependencies
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!
Classifications + HourlyClassification + CommissionedClassification + SalariedClassification + ChangeClassficationTransaction & Derivatives + TimeCard + SalesReceipt TextParser + TextParserTransactionSource Payroll Application + PayrollApplication + TransactionSource Application Schedules + Application Payroll Domain + WeeklySchedule + MonthlySchedule + BiweeklySchedule + ChangeScheduleTransaction & Derivatives + Employee + Affiliation + PayrollClassification + PayrollSchedule + Affiliations + PayrollMethod + Transaction Methods Affiliations + MailMethod + HoldMethod + DirectMethod ChangeMethodTransaction & Derivatives Payroll Database + UnionAffiliation + ServiceCharge + ChangeAffiliationTransaction & Derivatives + PayrollDatabase Applying the Common-Closure Principle • This diagram groups classes according to their closure irresponsible responsible & independent
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. • H=(R+1)/N • 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. • 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.