More on this book
Community
Kindle Notes & Highlights
by
Metz Sandi
Read between
November 26, 2019 - July 21, 2020
Object-oriented design is about managing dependencies.
devise an arrangement of code that is cost effective in the present and will continue to be so in the future.
The purpose of design is to allow you to do design later, and its primary goal is to reduce the cost of change.
The project gradually becomes doomed as participants switch from working to make it succeed to striving to avoid being blamed for its failure.
At this stage, your first obligation is to take a deep breath and insist that it be simple. Your goal is to model your application, using classes, such that it does what it is supposed to do right now and is also easy to change later.
You will never know less than you know right now.
Code that is Transparent, Reasonable, Usable, and Exemplary (TRUE) not only meets today’s needs but can also be changed to meet the needs of the future. The first step in creating code that is TRUE is to ensure that each class has a single, well-defined responsibility.
A class should do the smallest possible useful thing; that is, it should have a single responsibility.
“Please, Mr. Gear, what is your tire (size)?” is just downright ridiculous.
The most cost-effective course of action may be to wait for more information.
All the same design techniques work; ask them questions about what they do and try to describe their responsibilities in a single sentence.
Despite its daunting reputation, dependency injection truly is this simple.
if you cannot remove unnecessary dependencies, you should isolate them within your class.
One could infer that the direction of the dependency does not matter, that it makes no difference whether Gear depends on Wheel or vice versa. Indeed, in an application that never changed, your choice would not matter.
depend on things that change less often than you do.
Depending on an abstraction is always safer than depending on a concretion because by its very nature, the abstraction is more stable.
Depend on things that change less often than you do is a heuristic that stands in for all the ideas in this section.
Classes control what’s in your source code repository; messages reflect the living, animated application.
The design goal, as always, is to retain maximum future flexibility while writing only enough code to meet today’s requirements.
Changing the fundamental design question from “I know I need this class, what should it do?” to “I need to send this message, who should respond to it?”
You don’t send messages because you have objects, you have objects because you send messages.
When Moe knows how to decide if a trip is suitable, he isn’t ordering behavior off of a menu, he’s going into the kitchen and cooking.
Because Mechanic promises that its public interface is stable and unchanging, having a small public interface means that there are few methods for others to depend on.
You already know the technique for collaborating with others without knowing who they are—dependency injection.
: “I know what I want, and I trust you to do your part.” This blind trust is a keystone of object-oriented design.
Your goal is to write code that works today, that can easily be reused, and that can be adapted for unexpected use in the future.
Ruby provides three relevant keywords: public, protected, and private. Use of these keywords serves two distinct purposes. First, they indicate which methods are stable and which are unstable. Second, they control how visible a method is to other parts of your application.
Demeter is often paraphrased as “only talk to your immediate neighbors” or “use only one dot.”
replacing costly dependencies on class with more forgiving dependencies on messages. Duck typed objects are chameleons that are defined more by their behavior than by their class.
Every sequence diagram thus far has been simpler than its corresponding code, but this new diagram looks frighteningly complicated. This complexity is a warning. Sequence diagrams should always be simpler than the code they represent; when they are not, something is wrong with the design.
It is relatively easy to implement a duck type; your design challenge is to notice that you need one and to abstract its interface.
Well-designed applications are constructed of reusable code. Small, trustworthy self-contained objects with minimal context, clear interfaces, and injected dependencies are inherently reusable.
You might be tempted to skip the middleman and just leave this bit of code in Bicycle to begin with, but this push-everything-down-and-then-pull-some-things-up strategy is an important part of this refactoring.
When deciding between refactoring strategies, indeed, when deciding between design strategies in general, it’s useful to ask the question, “What will happen if I’m wrong?”
Promotion failures thus have low consequences.
Untrustworthy hierarchies force objects that interact with them to know their quirks.
The consequences of a demotion failure can be widespread and severe.
The general rule for refactoring into a new inheritance hierarchy is to arrange code so that you can promote abstractions rather than demote concretions.
“What will happen when I’m wrong?”
document template method requirements by implementing matching methods that raise useful errors.
When composition is used in this stricter sense, you know not only that meals have appetizers but also that once the meal is eaten the appetizer is also gone.
Aggregation is exactly like composition except that the contained object has an independent life. Universities have departments, which in turn have professors.
The university–department relationship is one of composition (in its strictest sense) and the department–professor relationship is aggregation.
Composition contains far fewer built-in dependencies than inheritance; it is very often the best choice. Inheritance is a better solution when its use provides high rewards for low risk.
benefits of inheritance was careful to qualify its assertions as applying only to a “correctly modeled hierarchy.” Imagine reasonable, usable, and exemplary as two-sided coins.
The flip side of the reasonable coin is the very high cost of making changes near the top of an incorrectly modeled hierarchy. In this case, the leveraging effect works to your disadvantage; small changes break everything.
As these costs and benefits illustrate, composition is excellent at prescribing rules for assembling an object made of parts but doesn’t provide as much help for the problem of arranging code for a collection of parts that are very nearly identical.
“Inheritance is best suited to adding functionally to existing classes when you will use most of the old code and add relatively small amounts of new code.”
“Use composition when the behavior is more than the sum of its parts.”
Use Inheritance for is-a Relationships

