99 Bottles of OOP
Things to Remember
- "This code places the burden upon the reader" -> forces you to create a mental map
- "interpolate duplicated logic inside strings"
- "concepts hidden in the code that are not yet visible because they haven’t been isolated and named"
- "duplicates both data and logic"
- "concepts that it does not name"
- "concise to the point of incomprehensibility while simultaneously retaining loads of duplication."
- Goals: "concrete enough to be understood while simultaneously being abstract enough to allow for change."
- When do we reach that point? a lot sooner... why? lack of concepts and the concepts are the ones that vary
- How difficult was it to write?
- How hard is it to understand?
- No concepts expressed.
- Logic duplicated
- How expensive will it be to change?
- have to change multiple lines etc
Problems with consistency, duplication, and naming.
it is incumbent upon you to accept the harder task and write simpler code.
Naming / Understandability
your efforts are for other readers
Write for understandability now. The very act of looking at a piece of code declares that you wish to understand it at this moment.
Code is easy to understand when it clearly reflects the problem it’s solving, and thus openly exposes that problem’s domain.
Naming
named the method after what it does right now. Unfortunately, when you name a method after its current implementation, you can never change that internal implementation without ruining the method name.
The tie-breaker here is that the "name things at one higher level of abstraction" rule applies more to methods than to classes. while you should continue to name methods after what they mean, classes can be named after what they are.
the price you pay for poor names is relatively low [in the short term]. However, code is read many. more times than it is written, and its ultimate cost is often very high and paid by someone else.
You should name methods not after what they do, but after what they mean, what they represent in the context of your domain.
- He named methods after what they did in our domain not after what they represent in the context of our domain
If you were to ask your customer what X is in the context of the Y
- the word "beverage" is one level of abstraction higher than "beer."
Look for terms one level of abstraction higher -> isolates from changes in implementation.
name methods after the concept they represent rather than how they currently behave.
perfect name for a concept is elusive:
- allot themselves five to ten minutes to ponder
- instantly choose a meaningless name like foo or namethis
- "You’ll never know less than you know right now"
- Ask someone
Concepts
The number of concepts, number of variants for the concept, the difference between the variants, and the algorithm for looping are distressingly obscure.
A lack of ability to identify concepts and break up functionality along conceptual lines.
Differences
How can we make the differences obvious?
Clarity / Complexity
Why are there so many levels of indirection needed?
while complexity is not forbidden, it is required to pay its own way.
priority is NOT brevity
Coupling
Didn't extract behavior along the lines of the concept. It's a valid way to do things even in functional and OO.
- He coupled concepts -> coupling concepts leads to things that change for different reasons being coupled together.
- concepts are mixed together
concepts are mixed together, conflated, such that their individual natures are obscured.
- The core of this problem is identifying the concepts (and naming the concepts) and isolating how they vary regardless of whether you choose a functional or OO style.
Premature Abstraction
its author feared that the logic for selecting or invoking a template would someday need to change,
Duplication
The code is DRY, and DRYing out code should reduce costs. DRY promises that if you put a chunk of code into a method and then invoke that method instead of duplicating the code, you will save money later if the behavior of that chunk changes.
- The fault here, however, lies not with the DRY principle, but with the names of the methods. (beer being changed to koolaid)
1) Rediscovering Simplicity
OO code is less concrete but more abstract—you’ve made it initially harder to understand in hopes that it will ultimately be easier to maintain.
OOD isn't free it just claims that it's benefits outweight its costs. DRY isn't free either.
Abstractions -> hard -> easy to get wrong -> tend to over-anticipate abstractions -> should resist them until they absolutely insist upon being created
1.1 Simplifying
"It must remain concrete enough to be understood while simultaneously being abstract enough to allow for change."
"concise to the point of incomprehensibility while simultaneously retaining loads of duplication."
"concepts that it does not name"
"duplicates both data and logic"
"Having multiple copies of the strings "of beer" and "on the wall" isn’t great, but at least string duplication is easy to see and understand."
"Logic, however, is harder to comprehend than data, and duplicated logic is doubly so."
"maximum confusion, you can interpolate duplicated logic inside strings, as does the verse method above."
Duplication of logic suggests that there are concepts hidden in the code that are not yet visible because they haven’t been isolated and named. Each bit of logic serves some purpose, and it is up to you to construct a mental map of what these purposes might be.
This code would be easier to understand if it did not place that burden upon you, the intrepid reader
Referring to the act of invoking behavior as "calling" a method or function suggests (albeit mildly) that you know what that called code does. "sending a message" leaves a bit more mental space between the sender’s intention and the receiver’s implementation.
identifying the code you’d like to extract and deciding on a method name. This, in turn, requires naming the concept,
Code is easy to understand when it clearly reflects the problem it’s solving, and thus openly exposes that problem’s domain.
Write for understandability now. The very act of looking at a piece of code declares that you wish to understand it at this moment.
- How many verse variants are there?
- Which verses are most alike? In what way?
- Which verses are most different, and in what way?
- What is the rule to determine which verse comes next?
The number of variants, the difference between the variants, and the algorithm for looping are distressingly obscure.
1.1.2 Speculative Generality
Verses 0, 1 and 2 are clearly different from 3-99, although it’s not obvious in what way.
Verses 0, 1 and 2 are clearly different from 3-99, although it’s not obvious in what way.
This choosing/rendering code is overly complicated, and while complexity is not forbidden, it is required to pay its own way. In this case, complexity does not.
its author feared that the logic for selecting or invoking a template would someday need to change,
(1.1.3 Concretely Abstract
Basically the methods are exactly the what a thing is potentially ("goToTheStoreOrTakeOneDown()").
While changing the code inside any individual method is cheap, in many cases, one simple change will cascade and force many other changes.
The code is DRY, and DRYing out code should reduce costs. DRY promises that if you put a chunk of code into a method and then invoke that method instead of duplicating the code, you will save money later if the behavior of that chunk changes.
The fault here, however, lies not with the DRY principle, but with the names of the methods.
If you were to ask your customer what "beer" is in the context of the "99 Bottles" song
1.1.4 Shameless Green
Shameless Green is clearly the best solution, yet almost no one writes it. It feels embarrassingly easy
if you pretend that this problem is a proxy for a real, production application, the proper course of action is not so clear.
One of the biggest challenges of design is knowing when to stop
1.2 Shameless Green
definitions generally describe how code looks when it’s done without providing any concrete guidance about how to get there.
I like my code to be elegant and efficient. — Bjarne Stroustrup
Clean code is ... full of crisp abstractions ... — Grady Booch
Clean code was written by someone who cares. — Michael Feathers
Any pile of code can be made to
work; good code not only works, but is also simple, understandable, expressive and changeable. -- Sandi Metz
The attributes they use to describe good code are qualitative, not quantitative.
Since form follows function, good code can also be defined simply, and somewhat circularly, as that which provides the highest value for the lowest cost.
1.3 Summary
As programmers grow, they get better at solving challenging problems, and become comfortable with complexity. This higher level of comfort sometimes leads to the belief that complexity is inevitable, as if it’s the natural, inescapable state of all finished code. However, there’s something beyond complexity—a higher level of simplicity. Infinitely experienced programmers do not write infinitely complex code; they write code that’s blindingly simple.
Shameless Green--it is cheaper to manage temporary duplication than to recover from incorrect abstractions. if nothing ever changes, the most cost-effective strategy is to deploy this code and walk away.
The Shameless Green solution strives for maximum understandability but is generally unconcerned with changeability.
2
2.3 Removing Duplication
While in the "writing tests" hat, you keep your eye on the big picture and work your way forward with the overall plan in mind. When in the "writing code" hat, you pretend to know nothing other than the requirements specified by the tests at hand.
TDD requires that you pass tests by writing simple code.
The first part (lines 2-6) contains the conditional, and the second (lines 8-12) contains a template that could correctly generate many verses
you’ve separated the things that change from the things that remain the same,
As tests get more specific, code should become more generic. Code becomes more generic by becoming more abstract.
gets worse before it gets better.
With embedded conditionals, "even if an abstraction lurks here, it certainly has not been named." The concept of pluralization is a red herring
The fact that "bottle" is duplicated many times signals that there’s an underlying concept that has not yet been unearthed.
Making one look different will ultimately make it harder to see how all are the same.
Code like this pluralize method gets written when programmers take the DRY principle to extremes, as if they’re allergic to duplication.
Does the change I’m contemplating make the code harder to understand?
What is the future cost of doing nothing now?
When will the future arrive, or how soon will I get more information?
Writing Shameless Green means optimizing for understandability, not changeability, and patiently tolerating duplication if doing so will help reveal the underlying abstraction.
You should complete the entire horizontal path before indulging in any vertical digressions.
Use of if / else if implies that each subsequent condition varies in a meaningful way.
This implementation reveals some important concepts in the domain.
This lack of indirection is a direct result of the dearth of abstractions.
Some duplication is tolerable during the search for Shameless Green. However, not all duplication is helpful,
Duplication is useful when it supplies independent, specific examples of a general concept that you don’t yet understand.
new code duplicates an example that already exists
duplicating this already-existing code masks the true responsibility of verses
Developing the habit of writing just enough code to pass the tests forces you to write better tests.
It’s best to save Obvious Implementation for very small leaps.
Knowledge that one object has about another creates a dependency. Dependencies tie objects together, exacerbating the cost of change. Your goal as a message sender is to incur a limited number of dependencies, and your obligation as a method provider is to inflict few.
The distinction between intention and implementation [...] allows you to understand a computation first in essence and later, if necessary, in detail. -- Kent Beck
A great deal of this pain originates with tests that are tied too closely to code.
write tests that confirm what your code does without any knowledge of how your code does it.
Tests are not the place for abstractions—they are the place for concretions.
If you insist on reducing duplication by adding logic to your tests, this logic by necessity must mirror the logic in your code.
Testing, done well, speeds development and lowers costs.
TDD can prevent costly guesses, but only if you commit to writing code in small steps.
Tests can make it safe and easy to refactor, but only if they are carefully de-coupled from the current code.
3.) Unearthing Concepts
The Shameless Green solution values understandability, straight-forwardness and efficiency, with little regard for changeability. It contains duplication, and is unapologetic about leaning in the procedural direction.
Conditionals are the bane of OO
The "open" principle says that you should not conflate the process of moving code around, of refactoring, with the act of adding new features. You should instead separate these two operations.
there is no direct connection between removing the duplication, and succeeding in making the code open to the six-pack requirement. That, however, is the beauty of this technique. You don’t have to know how to solve the whole problem in advance.
safe refactoring relies upon tests.
never change tests during a refactoring.
Tests that make assertions about how things are done, rather than what actually happens, are the prime contributors to this predicament.
Flocking Rules
- Select the things that are most alike.
- Find the smallest difference between them.
- Make the simplest change to remove that difference:
a. parse the new code
b. parse and execute it
c. parse, execute and use its result
d. delete unused code
Making small changes means you get very precise error messages when something goes wrong, so it’s useful to know how to work at this level of granularity.
- Alignment - Steer towards the average heading of neighbors
- Separation - Don’t get too close to a neighbor
- Cohesion - Steer towards the average position of the flock
sameness is easier to identify, difference is more useful because it has more meaning. DRYing out sameness has some value, but DRYing out difference has more.
The focus here is encapsulating the concept that varies, a theme of many design patterns
a systematic application of the rules of refactoring converts difference to sameness, decomposing a problem into its constituent parts.
You don’t have to identify the underlying abstractions in advance of refactoring. If you merely write the code dictated by the rules, the abstractions will follow.
solving easy problems, through a magical alchemy of code, sometimes transmutes hard problems into easy ones. It is common to find that hard problems are hard only because the easy ones have not yet been solved.
Within the context of the song, "bottle/bottles" does not represent pluralization.
The general rule is that the name of a thing should be one level of abstraction higher than the thing itself.
no change breaks the tests, the code can be deployed to production at any intermediate point.
"Gradual Cutover Refactoring," a strategy for keeping the code in a releasable state by gradually switching over a small number of pieces at a time.
The act of adding a new branch to the conditional while executing only the previously existing code is a mini-example of the Open/Closed Principle. You can think of this change as making the container method open to a new requirement
The small amount of time lost to making incremental changes is more than recouped by avoiding lengthy and frustrating debugging sessions. This style of coding is not only fast, it’s also stress-free.
Real refactoring is comfortingly predictable, and saves brainpower for more thought-provoking challenges.
refactor the existing code to be open to the new requirement, next, add the new code.
Sometimes the first step, refactoring to openness, requires such a large leap that it is not obvious how to achieve it. In that case, be guided by code smells.
Making existing code open to a new requirement often requires identifying and naming abstractions.
4) Practicing Horizontal Refactoring
The act of substituting a variable for an explicit number is so minor that it doesn’t adequately reflect the enormity of the underlying idea, but step back and consider what just happened. Replacing differing concrete values with a reference to a common variable changes difference into sameness.
If all verse variants are alike in an underlying, more abstract, way, then "it" and "one" must represent a smaller abstraction within that larger one.
perfect name for a concept is elusive:
allot themselves five to ten minutes to ponder
instantly choose a meaningless name like foo or namethis
"You’ll never know less than you know right now"
Ask someone
Do not take this as a general license to think far ahead. While you are allowed to use common sense, it’s usually best to stay horizontal and concentrate on the current goal. When creating an abstraction, first describe its responsibility as you understand it at this moment
All of these refactorings extract a method. Because this is done in very small steps, the extracted methods start out simple and then gradually become more complicated. One of the complications is that each method changes to take a parameter.
Notice the similarities in the above methods. Each has a single responsibility. They are identical in shape. All take the same argument. Each contains a conditional and that conditional tests the argument against a specific value; it checks to see if the argument is equal to something, as opposed to greater or less than something. These methods are incredibly consistent, and this did not happen by accident—it’s a direct result of the refactoring rules
(no more vs No more) These words are one thing, and whether they need to be capitalized is quite another.
The above change follows the strategy of gradually making things more alike in hopes that it will then become clear how to make them identical.
The idea of reducing the number of dependencies imposed upon message senders by requiring that receivers return trustworthy objects is a generalization of the Liskov Substitution Principle. Liskov, in straightforward language, requires that objects be what they promise they are. Liskov prohibits you from doing anything that would force the sender of a message to test the returned result in order to know how to behave.
Successor does feel like the right name for the current concept, but using it requires that you define successor to mean following rather than higher.
The concept is so subtle most programmers don’t notice it, and yet it simply appears if you follow this simple set of rules.
abstractions tell you where your code relies upon an idea.
according to the metrics, it’s worse
turn one conditional into many, while simultaneously adding 55% more code.
An improvement has been made that is invisible to static analysis tools.
Power of the Flocking Rules to uncover sophisticated concepts: You don’t have to understand the entire problem in order to find and express the correct abstractions—you merely apply these rules, repeatedly, and abstractions will naturally appear.
5) RESP
the current incarnation is less amenable to this requirement than was Shameless Green. The truth about refactoring is that it sometimes makes things worse
The refactoring recipes don’t promise to result in code that better expresses the problem—they merely make it easy to create that new expression, and just as easy to revert it.
Proper refactoring allows you to explore a problem domain safely.
continue to be guided by code smells
- Do any methods have the same shape?
- Do any methods take an argument of the same name?
- Do arguments of the same name always mean the same thing?
- If you were to make some methods private, which ones would they be?
- If you were going to break this class into two pieces, where’s the dividing line?
Do the tests in the conditionals have anything in common?
How many branches do the conditionals have?
Do the methods contain any code other than the conditional?
Does each method depend more on the argument that got passed, or on the class as a whole?
non-essential variation disguises a common shape
Superfluous difference raises the cost of reading code, and increases the difficulty of future refactorings.
the argument they receive is a bottle number rather than a verse number. Thus, the verse method and the flocked five methods use the same argument name to represent different concepts.
Having multiple methods that take the same argument is a code smell.
the term "same" means same concept, not identical name.
the same concept might go by several different names
different concepts might hide behind a single name
naming mistakes make it harder to notice underlying code smells
Testing for equality has several benefits over the alternatives. Most obviously, it narrows the range of things that meet the condition.
a deeply non-object-oriented pattern: the flocked five methods take an argument, examine it, and then supply behavior for it.
injecting a dependency ($number), but that dependency is too impaired to supply the needed behavior.
know about $number, but it’s also forced to understand what the specific values of $number mean,
OO mindset, and that mindset is deeply suspicious of conditionals.
when you see a conditional, the hairs on your neck should stand up. Its very presence ought to offend your sensibilities. You should feel entitled to send messages to
objects, and look for a way to write code that allows you to do so. The above pattern means that objects are missing, and suggests that subsequent refactorings are needed to reveal them.
There is a place for conditionals in OO.
Manageable OO applications consist of pools of small objects that collaborate to accomplish tasks.
big difference between a conditional that selects the correct object and one that supplies behavior. The first is acceptable and generally unavoidable. The second suggests that you are missing objects in your domain.
What does it mean to be "open"?
Each item above acts like a vote, and these votes combine to point to Primitive Obsession as the dominant code smell.
Obsessing on a primitive results in code that passes built-in types around, and supplies behavior for them. The cure for Primitive Obsession is to create a new class to use in place of the primitive. For this operation, the refactoring recipe is Extract Class.
This new class does not represent a kind of bottle: it represents a kind of number. The distinction may seem subtle, but the divide between these two concepts is chasmic. A bottle is a thing, while a number is an idea. It’s easy to imagine creating objects that stand in for things, but the power of OO is that it lets you model ideas.
Model-able ideas often lie dormant in the interactions between other objects.
The tie-breaker here is that the "name things at one higher level of abstraction" rule applies more to methods than to classes. while you should continue to name methods after what they mean, classes can be named after what they are.
remember to save the code after every change, and to run the tests after every save
The methods weren’t moved—they were duplicated, so nothing about Bottle has yet been changed
the example above starts with Fowler’s final step, and combines all of the method moves within a single change. [Fowler's refactoring was done that way so that it could be done without tests]
Learning the art of transforming code one line at a time, while keeping the tests passing at every point, lets you undertake enormous refactorings piecemeal. This small problem is a good place to practice this technique, in preparation for later tackling bigger ones.
If you adhere to a recipe and tests start failing, it’s likely that there’s something about the problem that you don’t yet understand.
A great benefit of these refactoring techniques is that you can accomplish quite a bit while thinking very little.
The real world is pervaded by this idea—what exists, will change.
Because they are easy to reason about, immutable objects are also easy to test. Objects that change need tests for the affected behavior.
The benefits of immutability are so great that, if it were free, you’d choose it every time. The ongoing costs of immutability are therefore mostly in the creation of new objects,
Given this, the best programming strategy is to write the simplest code possible and measure its performance once you’re done.
Your goal is to optimize for ease of understanding while maintaining performance that’s fast enough. Don’t sacrifice readability in advance of having solid performance data. The first solution to any problem should avoid caching, use immutable objects, and treat object creation as free. This results in speedy development of simple code, which leaves plenty of time to identify and correct the real performance problems.
(new BottleNumber($number))->quantity();
And just like that, you’ve added caching plus mutation. You’ll find that the changes needed to do this add complexity. This complexity may cost more than the benefit gained by faster performance.
You have every right to expect any method named successor to return an object that implements the same API as the receiver, but alas, this successor method does not.
You have every right to expect any method named successor to return an object that implements the same API as the receiver, but alas, this successor method does not.
This inconsistency is another violation of the generalized Liskov Substitution Principle.
This current refactoring is almost complete, and it is often better to finish horizontal refactorings before undertaking vertical tangents. You could veer from the path and fix the Liskov violation, but in the spirit of completing the current thought before undertaking a new task, stay the course.
6) Achieving Openness
This increasing isolation of the concepts that need to vary is an indication that the code is moving in the right direction.
Above, quantity and container appear together in three different places (lines 8, 10, and 12). The duplication of this pairing gives off a whiff of the Data Clump code smell.
Having a clump of data usually means you are missing a concept. When a clump gets sent as a set of parameters, the method that receives the clump can easily become polluted with clump management logic. some or all of this management logic will inevitably get duplicated in several places. Not only is it a pain to maintain this duplication, but over time the logic might accidentally diverge, introducing errors and confusing everyone involved.
usually removed by extracting a class.
The current __toString maximizes the effect of removing the Data Clump in verse
The verse method is getting simpler, but it still has more than one responsibility. This problem is reflected by the very structure of the code—the above method contains a blank line. Programmers add blank lines to signify changes of topic. The presence of multiple topics suggests the existence of multiple responsibilities, which makes code harder to understand when reading, and easier to harm when changing.
These conditionals characterize the class, and make Switch Statement the most identifiable code smell.
The Replace Conditional with State/Strategy recipe removes conditionals by dispersing their branches into new, smaller objects, one of which is later selected and plugged back in at runtime. This recipe results in a code arrangement known as composition.
The Replace Conditional with Polymorphism recipe removes conditionals by creating one class to hold the defaults of the conditionals (the false branches), and adding subclasses for each specialization (the true branches of the various conditions). It then chooses one of these new objects to plug back in at runtime. This recipe solves the conditional problem using inheritance.
They know what’s right before they do it. Or at least they do, sometimes. They also know that they don’t know everything.
they engage in careful, precise, reproducible, and reversible coding experiments. Practice builds intuition. Do it enough, and you’ll seem magical too.
polymorphism refers to the idea of having many different kinds of objects that respond to the same message.
Senders don’t care what receivers are; instead, they depend on what receivers do.
Polymorphism, by definition, involves more than one kind of object, so changing from a procedural to a polymorphic code arrangement will increase the overall number of classes. as conditionals disappear from BottleNumber, new dependencies will arise.
Obsessions are usually cured by extracting a class, and if you suspect that class extraction is called for here, you are correct.
The specific logic for 0 needs to be isolated in a class of its own, as does the logic for 1.
Removing the conditionals without breaking the tests requires a process that carefully and systematically moves the code from each true branch into a new object, rather than willy-nilly deleting it. The specific logic for 0 needs to be isolated in a class of its own, as does the logic for 1. Also, as these new classes come into existence, some additional code will have to be written to choose the correct class based on the value of number.
As previously stated, this recipe uses inheritance. Modern object-oriented programming is biased towards preferring composition over inheritance. However, this bias shouldn’t be taken to mean that the use of inheritance is banned.
When several classes play a common role, some code, somewhere, must know how to choose the right role-playing class for any specific contingency. This choosing very often involves a conditional, which should exist in one and only one place.
When a factory exists for a role, the factory has sole responsibility for creating objects to play that role. The factory’s purpose is to isolate the names of the concrete classes, and to hide the logic needed to choose the correct one.
The bottleNumberFor method inserts a level of indirection between the desire for a BottleNumber and its creation.
The above code works, but it’s not perfect. The problem is that the branches of the conditional combine things that differ (BottleNumber0 and BottleNumber) with things that remain the same (return new ...($number)). This conflation forces the reader to study the code to discern the difference.
return new $className($number);
The nice thing about this version is that it isolates the things that vary, which highlights the difference between the conditions.
When you invoke the factory to get a bottle number, you don’t need to know the class of the returned object. This willful ignorance of type is fundamental to object-oriented programming.
It insulates code that calls a factory from changes of implementation within that factory. By refusing to be aware of the classes of the objects with which you interact, you grant others the freedom to alter your code’s behavior without editing its source.
The conditional above may be giving you a sense of deja vu. It’s reminiscent of, although not quite identical to, the switch statement from the original Shameless Green solution.
If introducing polymorphism improved the code, this new version ought to tell an accurate and easily understood story about the domain.
The successor methods return a result so unexpected that it’s perilously close to being an outright lie.
Liskov violations are insidious, and over time cause increasing harm. As your application evolves, successor might get sent from many places. Each place will have to know that successor returns a number, and must also know how to convert that number back into a bottle number. This interconnected web of duplicated knowledge binds every sender of successor to its current implementation, which inflicts dependencies that make code resistant to change.
The root of the problem is that a new type (BottleNumber) was introduced, but its successor method continued to return the old type (int).
The current predicament stands in for the real-world problem of needing to change the type returned by a polymorphic method that has many implementors and many senders.
For problems of this size, you might be successful at changing everything at once, but real life typically involves larger problems that require many more changes and present a much greater challenge.
the best choice is to make the factory a static function on an existing class.
there are two good reasons to simplify its name.
First, changing the name avoids the "echo chamber" effect. BottleNumber->bottleNumberFor is both redundant and overly specific. It suffers from the same ailment as the beer method in Chapter 1. This name is tightly coupled to the current context, and tight coupling makes code resistant to change. if you someday decide to rename the BottleNumber class, you’ll have to change this method name too, or forever be misled.
for is a generic request that works fine as the name of any factory. When factory-ish objects polymorphically implement for, you can send this message without regard for the receiver’s type. Polymorphism preserves the option of constructing applications where the factories themselves are substitutable.
First, changing the name avoids the "echo chamber" effect. BottleNumber->bottleNumberFor is both redundant and overly specific.
This name is tightly coupled to the current context, and tight coupling makes code resistant to change.
Second, and more abstractly, for supports polymorphism. __toString() allows for polymorophism because all classess in php support it. If each class used a unique name or qualified their string casting method by their type then you would have to know the type of the object in order to cast it to a string
It’s much simpler to have stringifiable objects polymorphically implement an identically named method,
the trick to moving forward using one-undo (more on this later) changes is to temporarily alter the factory to tolerate both kinds of input.
[if you have types in place for params, you can still remove them and or change the signature of the method and get some of the benefits of polymorphism]
Correcting the Liskov violation is important because object-oriented programming, especially in dynamically-typed languages like PHP, relies on explicit trust in the implicit contracts between objects.
Trustworthy objects are a joy to work with because they always behave as you expect.
untrustworthy objects require senders of messages to know too much.
When your application has code that needs knowledge of the internals of other objects in order to correctly interact with them (as did successor above), changes to those other objects might break your code.
If you have to check the type of an object in order to know what message to send, you’re forced into a conditional that lists every concrete class with which you’re willing to collaborate.
Checking to see if an object responds to a message rather than checking that object’s type may reduce the size of this conditional, but it doesn’t ameliorate the problem.
All of the above are symptoms of an inability to trust other objects, and failures of trustworthiness are, at least by the current generous interpretation of the principle, Liskov violations.
Objects made promises that they did not keep. In every case, the underlying cause is an insufficient use of polymorphism.
Consider the meaning of __toString versus that of quantity and container. The latter two methods reflect fundamental concepts in this domain.
Consider the meaning of __toString versus that of quantity and container. The latter two methods reflect fundamental concepts in this domain.
7) Manufacturing Intelligence
The difference in the number of branches, although highly visible, is merely an artifact of the specific domain of the "99 Bottles of Beer" song. Having explained that difference away, another yet remains, and this one has significantly more meaning.
The Shameless Green verse method contains a conditional that:
- understands why you might switch (case 0, for example), and
- knows the behavior needed for this case ("No more bottles . . ."). The factory for method:
- is similar in that it also understands the reasons for switching (again, case 0, etc) but 2. differs in that it knows the name of the class that supplies the behavior for the case.
Factories don’t know what to do: instead, they know how to choose who does.
difference between a conditional that supplies behavior and a conditional that selects an object
The power of polymorphism is that these role-playing objects are interchangeable from the message sender’s point of view.
The message- sending object thinks of its collaborator as a player of a role rather than a kind of a type. They know what their collaborators do, but refuse to be aware of how they do it.
A system comprised of message senders who collaborate with role-playing objects can be extremely tolerant of unexpected change.
this system works only if message senders really do treat collaborators as if they’re interchangeable
If message senders are to be immune from side-effects when adding or removing role-players, these senders can’t know things that are unique to specific variants.
Message senders aren’t allowed to know the names of the concrete variant classes, nor may they know the logic needed to choose between them.
A factory’s responsibility is to manufacture the right object for a given role.
This choosing usually involves a conditional, and putting this conditional in a factory allows you to isolate it to a single place in your code.
Thus, factories are where conditionals go to die. Isolating conditionals in factories loosens the coupling between collaborating objects, which lowers the cost of change.
Factories vary along these dimensions:
- The factory can be open to new variants or closed.
- The logic that chooses a variant can be owned by the factory or by the variant.
- The factory can be responsible for knowing/figuring out which classes are eligible to be manufactured or the variants can volunteer themselves.
Downsides of meta programmed factories
harder to understand
no longer explicitly referenced in the source code.
need to have covering tests
gnores bottle number classes whose names do not follow the convention.
Again, need good tests
A downside of the switch is that, If new classes get added regularly, this is annoying and expensive.
The first step towards an open factory that both centralizes knowledge and supports arbitrary class names is to rearrange the code to increase the isolation of the names. You can do this by replacing the switch statement with a key/value lookup
the data has been separated from the algorithm
The benefit of this separation is that you can now think of the driving data [finding the class] as an entity in itself, separate from the choosing algorithm.
Notice that in the switch version, the colors alternate, while in the key/value version, like colors group more closely together. These groupings say something about the code.
When the colors change constantly it means that the code changes topics a lot. Code that is more object-oriented tends to group like things together, with fewer changes of topic.
On procedures
The upside of procedures is that simple ones (short and without conditionals) are easy to understand. The downside is that complex ones (long and with many conditionals) are costly to change.
The most efficient, expedient way to fulfill a new requirement may be to write a simple, unglamorous procedure, but if this procedure needs to change it should be converted into object- oriented code.
Procedural code can save you money when used to create small, isolated features that never need to change, but this style of coding will break the bank if used on large, shared features that are core to your domain.
OO asks you to break code up into small, cohesive pieces. The benefit of having smaller pieces is that each individual piece, relative to its procedural analog, is easier to understand and change.
The straightforwardness of simple procedures can make them seem attractive, and indeed, they’re fine as long as nothing ever changes. However, if your code needs to adapt and grow, it’s worth paying the toll charged by OOP.
Choosing logic in factories
The logic needed to select the right class might be long, complex, and more closely related to the class being chosen than to the factory itself. If the choosing logic changes in lockstep with code that lives in the class being chosen, then the choosing logic belongs in that class, not in the factory.
each choose-able object implements its own method to determine if it should be chosen
This factory disperses the choosing logic into the things chosen. In this example, that logic is so simple that this technique is excessive, but in some situations, choosing will involve lots of code, and that code will change in lockstep with the class being chosen.
several issues. First, it’s closed. Each time a new class is added you must update the list on 5-8. BottleNumber must always be the last class on the list. it’s possible that more than one handles methods would return true
If your choosing logic is more closely related to the class being chosen than to the factory, the choosing logic should be co-located in that class.
Self-registering candidates
f you would like the factory to simply continue working when new candidates appear, you have two basic options.
- The factory could dynamically figure out which classes belong on its list, or
- classes who want to be on the list could explicitly ask the factory to put them there. registry
inheritance vs class name dependency
Choosing between depending on a class name versus depending on inheritance means placing a bet on which dependency is more stable.
[what about using composition and an interface?]
8) Developing a Programming Aesthetic
Here’s the process used to create the verse template role:
- Identify the code you want to vary.
- Name the underlying concept.
- Extract the identified code into its own class.
- Inject this new role-playing object back into the object from which it was extracted. 5. Forward messages from the original class to the injected object.
Isolate the behavior you want to vary. One of the most fundamental concepts in OO is to isolate the behavior you want to vary.
Isolating variants often requires that you invert dependencies, and an excellent technique for inverting dependencies is to inject them.
Law of Demeter
The code may produce the correct output at this moment, but will not age well. These messages tightly couple Foo to a chain of different objects, all of which must be available. interferes with your ability to use Foo in new and unexpected contexts. Nothing here stands alone—this group of objects acts like a single thing and any use requires every piece.
Tests serve many purposes, one of which is to reveal how easy it is to reuse code. Tightly-coupled code is difficult to test. Tightly-coupled objects require adding lots of context, all of which must be provided in order to run any test.
An object that’s hard to test is attempting to warn you that it will be difficult to reuse.
The Law of Demeter says that from within a method, messages should be sent only to:
objects that are passed in as arguments to the method
objects that are directly available to $this
It’s not the number of object operators that matter, but the kind of object returned by each message.
The Law of Demeter effectively restricts the list of other objects to which an object may send a message.
an object may talk to its neighbors but not to its neighbor’s neighbors.
Curing Demeter
introducing message forwarding,
The code in each method above obeys the Law of Demeter by sending messages only to direct collaborators.
The trick to honoring the Law while simultaneously avoiding encoding the names of existing objects into the names of the forwarding messages is to think about design from the message senders point of view.
Now that Foo is talking only to a direct collaborator instead poking around in distant objects, it more easily tolerates unexpected change.
you should inject the thing you want to talk to
If you want something, just ask for it. If the receiver doesn’t know how to comply, teach it.. loosen coupling by designing a conversation that embodies what the message sender wants.
This static function does two things. First, it uses forwarding to eradicate the extra hop and resolve the Demeter violation.
Test setup seems likely to be painful. Pain in testing is a sign of a rigid application and an indication that there’s something wrong with the design.
Be extremely biased towards fixing violations. The overall cost of dealing with each transgression as it occurs is guaranteed to be less than the ultimate cost of repairing the few that spiral out of control after infecting your code for several years.
Classes vs Instances
Despite the fact that earlier in this chapter, classes were treated as "just another object" when deciding if new contributed to a Demeter violation, classes are different from instances in the most fundamental object-oriented way. In instances, common behavior combines with differing data to create objects that collaborate to form your application.
Putting domain behavior in a static function rather than in an instance method places a bet that this domain concept will never involve data that varies. This bet makes sense only if the value of not typing n e and w today is greater than the future cost of converting all the static functions to instance methods should you find that data needs to vary.
always create instances of objects. Part of your programming aesthetic is to reflexively put domain behavior on instances.
If someone else knows enough to provide _lyrics with the right $number, surely that someone can provide the right BottleNumber instead.
Well-designed object-oriented applications consist of loosely-coupled objects that rely on polymorphism to vary behavior. Injecting dependencies loosens coupling. Polymorphism isolates variant behavior into sets of interchangeable objects that look the same from the outside but behave differently on the inside.
Separating Object Creation from Object Use
systems where object creation begins to separate from object use. Object creation gets pushed more towards the edges, towards the outside, and the objects themselves interact more towards the middle, or the inside.
resist giving instance methods knowledge of concrete class names, and
seek opportunities to move the object creation towards the edges of the application.
These are guidelines, not hard and fast rules, so you can allow yourself some leeway. especially true in cases like this where the hard-coded reference is to a factory, so the coupling is already loose.
The one line rule
it’s worth practicing how to fix these names with smaller changes. Before doing so, however, it’s finally time to amend the one-line rule.
Instead of being restricted to one-line changes, refactoring permits one-undo changes.
one-undo rule allows you to use the find/replace feature of your text editor to make many changes at once.
Put domain behavior on instances.
Be averse to allowing instance methods to know the names of constants.
Seek to depend on injected abstractions rather than hard-coded concretions.
Push object creation to the edges, expecting objects to be created in one place and used in another.
Avoid Demeter violations, using the temptation to create them as a spur to search for deeper abstractions.
Complying with these precepts will frequently increase the amount of code and add levels of indirection, at least in the short term.