410 likes | 753 Views
Domain-driven Design. (book by Eric Evans) Part of the system is a “domain model” Customers and developers share “ubiquitous language”, which is expressed in domain model You understand the problem best on the last day of the project, which is why the domain model keeps changing. Patterns.
E N D
Domain-driven Design • (book by Eric Evans) • Part of the system is a “domain model” • Customers and developers share “ubiquitous language”, which is expressed in domain model • You understand the problem best on the last day of the project, which is why the domain model keeps changing
Patterns • People don’t design from first principles. • People design by reusing ideas they have seen before • From previous projects • From books, courses, etc
What Really is an Object? • Relationship can be an object if it has attributes and relationships • Marriage has duration and children • Intersection has accidents • Attribute can be an object if it has attributes • Temperature is taken at a point in time
What Really is an Object? • Reservation -- a promise to give service to a customer • Ticket -- record the event of the customer paying for service in advance, • Flight -- an event in which an airplane provides service • Payment -- an event (transaction?) in which money is exchanged
Attributes are simpler than objects. • Associations are simpler than objects. • Models should be as simple as possible, but no simpler.
Why this is important • Many of the objects in a good design are NOT problem domain objects. • Example: ValueWithHistory • They can represent operations, processes, states, constraints, relationships, ...
Design for Reuse • Purpose is to make a system flexible, extensible, and easily changed. • Make things objects if they need to be changed and manipulated.
Design for Reuse • Goal is to build an application just by using preexisting objects. • Compose, don't program. • Relationship between objects as important as class hierarchy.
Piecemeal Growth • The way living things grow • The way software grows • More than just addition -- transformation
http://c2.com/cgi/wiki?ExtremeNormalForm • Your classes are small and your methods are small; you've said everything OnceAndOnlyOnce and you've removed the last piece of unnecessary code. • Somewhere far away an Aikido master stands in a quiet room. He is centered.. aware.. ready for anything. • Outside a sprinter shakes out her legs and settles into the block, waiting for the crack of the gun while a cool wind arches across the track. • Softly, a bell sounds.. all of your tests have passed. • Your code is in ExtremeNormalForm.. tuned to today and poised to strike at tomorrow.
Extreme Normal Form Defined • All the code is tested. The code passes all the tests. • No one on the team can think of code in the system that they could remove or simplify without reducing the feature set. • You never have to go more than one place to change one thing.
Refactoring • Extreme Normal Form is a result of refactoring • Refactoring: changes to the organization of a program, not its function. • Behavior preserving program transformations.
Why refactoring is important • Only defense against software decay. • Often needed to fix reusability bugs. • Lets you add patterns after you have written a program; lets you transform program into framework. • Lets you worry about generality tomorrow; just get it working today. • Necessary for beautiful software.
Refactoring • Refactoring: Improving the Design of Existing Code by Martin Fowler with contributions by Kent Beck, John Brant, William Opdyke, and Don Roberts, Addison-Wesley 1999 • www.refactoring.com
How to refactor • Make changes as small as possible. • Test after each change. • Many small changes are easier than one big change. • Each change makes it easier to see that more changes are needed.
How to Refactor • Use Sunit to write test suite • Use TestRunner to run it; “keep your light green”. • Plan change so it is a series of small refactoring. • Run tests after every refactoring, once every 5 minutes.
Example: Strategy • Initially: class C with public method m • 1) Make a Strategy class S with empty method mFor: • 2) Add instance variable “strategy” to C, initialize it with an instance of S, and change m to have “strategy mFor: self” • 3) Move code from m to mFor: • 4) Move private methods from C to S.
Refactoring browser • A better browser • Automates many refactorings - renaming, moving code, splitting • Undo • Lint helps you find places that need to be refactored.
Code Smells • Data class • Switch statements • Speculative generality • Temporary field • Refused bequest Duplicated code Long method Large class Long parameter list Message chain Feature envy
Duplicate code • Eliminate duplication by • making new methods • making new objects • moving methods to common superclass
Long methods • Each method should do one thing. • One comment for each method. • Method should fit on the screen. • Method is all on same level of abstraction. • Method should be in right class.
FileSystemHandler • directoryResponse: aDirectory • self indexFileNames • do: [:each | indexFile | • indexFile := aDirectory construct: each. indexFile safeIsReadable • ifTrue: [^FileResponse fileNamed: indexFile]]. • ^DirectoryResponse fileNamed: aDirectory in: self • indexFileNames • ^#('index.html' 'index.htm' 'default.html' 'default.htm')
FileSystemHandler • directoryResponse: aDirectory • (self indexFilesFor: aDirectory) • do: [:indexFile | indexFile safeIsReadable • ifTrue: [^FileResponse fileNamed: indexFile]]. • ^DirectoryResponse fileNamed: aDirectory in: self • indexFilesFor: aDirectory • ^#('index.html' 'index.htm' 'default.html' 'default.htm') • collect: [:each | aDirectory construct: each]
Long method in CompositeWiki • responseKeyFor: aRequest • | mightNotBeTheRightPlace key | • mightNotBeTheRightPlace := aRequest identifier size >= self depth. • mightNotBeTheRightPlace • ifTrue: [key := aRequest identifier at: self depth] • ifFalse: • [^aRequest postDataAt: #COMMAND • ifAbsent: [aRequest postDataAt: #DEFAULT_COMMAND ifAbsent: [self rootKey]]]. • (self containsAction: key) • ifTrue: [aRequest decodeUrlencodedFormData: aRequest lastIdentifier]. • ^key
Method in CompositeWiki • responseKeyFor: aRequest • aRequest identifier size < self depth • ifTrue: • [^aRequest postDataAt: #COMMAND ifAbsent: • [aRequest postDataAt: #DEFAULT_COMMAND ifAbsent: • [self rootKey]]]. • (self containsAction: (self keyIn: aRequest)) • ifTrue: [aRequest decodeUrlencodedFormData: aRequest lastIdentifier]. • ^self keyIn: aRequest
responseKeyFor: aRequest • (aRequest isKeyMissingFor: self) • ifTrue: • [^aRequest postDataAt: #COMMAND ifAbsent: • [aRequest postDataAt: #DEFAULT_COMMAND ifAbsent: • [self rootKey]]]. • (self containsAction: (self keyIn: aRequest)) • ifTrue: [aRequest decodeUrlencodedFormData: aRequest lastIdentifier]. • ^self keyIn: aRequest
Data class • Some classes have only variables and accessors. • Look for places where accessors are used and eliminate “feature envy” and “message chains”.
Feature Envy • teacher classes add: thisClass. • teacher classLoad: (teacher classLoad + 1) • teacher addClass: thisClass
Message chain • Eliminate navigation code • Ensures that user of an object doesn’t have to know many classes • aFigure boundingBox center
Message chain • Law of Demeter: • Method should only send messages to classes of instance variables and arguments. • anX m1 m2 m3 m4 m5 m6 => anX m6
Large classes • More than seven or eight variables • More than fifty methods • You probably need to break up the class • Components (Strategy, Composite, Decorator) • Inheritance
Long parameter lists • If you see the same set of parameters repeated in several methods, bundle them into a new object. Create accessers to get the original parameters from the object. Change the methods to use the new object instead of the parameters. • Then figure out what other functionality needs to move to the object.
Case Statements • Use polymorphism, not case statement. • Make class hierarchy, one class for each case. • Make a method for each case statement. • Make each branch of case statement be a method in a class
Refused bequest • Subclass does not use all the inherited methods. • Overrides methods with different algorithm • Overrides methods with error message • Solution: • Make a new superclass
Refused Bequest A doX doY C doY B doX B doX A doX
Temporary field • Instance variable is only used during part of the lifetime of an object. • For example, it is only used while the object is initialized. • Move variable into another object
Non-localized plan • Adding a feature requires a plan. If adding a feature requires changing many parts of a program, it is called a “non-localized plan”. • Parallel class hierarchies: adding a class in one class hierarchy requires adding a class in another • Example: a new view class requires a new controller class
How to refactor non-localized plan • Make a new object that represents everything that changes. • Methods that change together should stay together.
When to refactor • If a new feature seems hard to implement, refactor. • If a new feature created some ugly code, refactor. • When you can’t stand to look at your code, refactor.