[Review] Design Patterns in Ruby
Book | Design Patterns in Ruby |
Author | Russ Olsen |
Link | designpatternsinruby.com |
- Meta Design Pattern
- About Design Pattern
- Design Pattern Classification
- The Template Method
- The Strategy
- The Observer
- Composite
- The Iterator
- The Command
- The Adapter
- The Proxy
- The Decorator
- Singleton
- Factory
- Builder
- Interpreter
- Domain-Specific Languages
- Custom Objects
- Convention Over Configuration
- Reference
Meta Design Pattern
- Seperate out the things that change from thos that stay the same.
- Program to an interface, not an implementation.
- Prefer composition over inheritance.
- Delegate, delegate, delegate.
Others:
- YAGNI, You ain’t gonna need it.
- A pattern is not just about code: Intent is critical.
Seperate out the things that change from thos that stay the same.
A key goal of software engineering is to build systems that allow us to contain the damage. In an ideal system, all changes are local.
You get there by separating the things that are likely to change from the things that are likely to stay the same. If you can identify which aspects of your system design are likely to change, you can isolate those bits from the more stable parts.
But how do you keep the changing parts from infecting the stable parts? Program to an interface, not an implementation.
Program to an interface, not an implementation.
A good start is to write code that is less tightly coupled to itself in the first place.
The idea here is to program to the most general type you can.
Prefer composition over inheritance.
The trouble is that inheritance comes with some unhappy strings attached. When you create a subclass of an existing class, you are not really creating two separate entities: Instead, you are making two classes that are bound together by a common implementation core. Inheritance, by its very nature, tends to marry the subclass to the superclass.
If our goal is to build systems that are not tightly coupled together, to build systems where a single change does not ripple through the code like a sonic boom, breaking the glassware as it goes, then probably we should not rely on inheritance as much as we do.
We can assemble the behaviors we need through composition. In short, we try to avoid saying that an object is a kind of something and instead say that it has something.
Delegate, delegate, delegate.
The combination of composition and delegation is a powerful and flexible alternative to inheritance. We get most of the benefits of inheritance, much more flexibility, and none of the unpleasant side effects. Of course, nothing comes for free. Delegation requires an extra method call, as the delegating object passes the buck along. This extra method call will have some performance cost—but in most cases, it will be very minor.
Another cost of delegation is the boilerplate code you need to write.
YAGNI, You ain’t gonna need it.
You Ain’t Gonna Need It (YAGNI for short). The YAGNI principle says simply that you should not implement features, or design in flexibility, that you don’t need right now.
A well-designed system is one that will flex gracefully in the face of bug fixes, changing requirements, the ongoing march of technology, and inevitable redesigns. The YAGNI principle says that you should focus on the things that you need right now, building in only the flexibility that you know you need.
The use of design patterns has somehow become associated with a particularly virulent strain of over-engineering, with code that tries to be infinitely flexible at the cost of being understandable, and maybe even at the cost of just plain working. The proper use of design patterns is the art of making your system just flexible enough to deal with the problems you have today, but no more.
Your system will not work better because you used all 23 of the GoF design patterns in every possible combination. Your code will work better only if it focuses on the job it needs to do right now.
About Design Pattern
Background
In 1995, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides set out to redirect all the effort going into building redundant software wheels into something more useful. That year, building on the work of Christopher Alexander, Kent Beck, and others, they published Design Patterns: Elements of Reusable Object-Oriented Software. The book was an instant hit, with the authors rapidly becoming famous (at least in software engineering circles) as the Gang of Four (GoF).
Focus on some key questions:
- How do objects like the ones you tend to find in most systems relate to one another?
- How should they be coupled together?
- What should they know about each other?
- How can we swap out parts that are likely to change frequently?
It’s commonly agreed that the most useful thing about patterns is the way in which they form a vocabulary for articulating design decisions during the normal course of development conversations among programmers.
Design patterns are little spring-loaded solutions to common programming problems. And a reckless use of every design pattern on the menu to solve nonexistent problems gives design patterns a bad name in some circles.
With Ruby
With Ruby, we no longer need to pull out relatively heavyweight design patterns to solve tiny problems. Instead, Ruby allows you to do simple things simply.
-
Like a Command object in the GoF sense is essentially a wrapper around some code that knows how to do one specific thing, to run a particular bit of code at some time. Of course, that is also a fairly accurate description of a Ruby code block object or a
Proc
. -
Internal Domain Specific Languages. I believe that his treatment of the subject, as an evolution of the Interpreter pattern, is the first significant reference work in publication on the topic.
The Ruby programming language makes implementing patterns so easy that sometimes they fade into the background.
- Ruby is dynamically typed.
- Ruby has code closures.
- Ruby classes are real objects.
- Ruby has an elegant system of code reuse.
The traditional implementations of many design patterns work, but they make you work, too. Ruby allows you to concentrate on the real problems that you are trying to solve instead of the plumbing.
The increasing industry recognition of the value of dynamic and flexible languages such as Ruby has plunged us into yet another wisdom gap.
Design Patterns was published in the need for wisdom.
Bruce Tate is fond of pointing out that when a new programming technique or language pops up, there is frequently a wisdom gap. The industry needs time to come to grips with the new technique, to figure out the best way to apply it. How many years had to elapse between the first realization that object-oriented programming was the way to go and the time when we really began to use object-oriented technology effectively? Those years were the object-oriented wisdom gap.
Design Pattern Classification
Creational (5)
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Singleton
Structural (7)
- Facade
- Adapter
- Proxy
- Decorator
- Bridge
- Composite
- Flyweight
Behavioural (11)
- Template Method
- Observer
- State
- Strategy
- Chain of Responsibility
- Command
- Visitor
- Mediator
- Memento
- Iterator
- Interpreter
The Template Method
Basic idea is Seperate out the things that change from thos that stay the same.
Description
- Extract the common part into an abstract base class
- Create some hook methods as the interface
- Let the subclass to implement it
The Template Method pattern is simply a fancy way of saying that if you want to vary an algorithm, one way to do so is to code the invariant part in a base class and to encapsulate the variable parts in methods that are defined by a number of subclasses.
The abstract base class controls the higher-level processing through the template method; the sub-classes simply fill in the details.
Non-abstract methods that can be overridden in the concrete classes of the Template Method pattern are called hook methods.
Duck typing is a trade-off: You give up the compile-time safety of static typing, and in return you get back a lot of code clarity and programming flexibility.
Using and Abusing
The Template Method pattern is at its best when it is at its leanest—that is, when every abstract method and hook is there for a reason. Try to avoid creating a template class that requires each subclass to override a huge number of obscure methods just to cover every conceivable possibility. You also do not want to create a template class that is encrusted with a multitude of hook methods that no one will ever override.
In the Wild
There is another very common example of the Template Method pattern that is perhaps so pervasive that it is hard to see. Think about the initialize
method that we use to set up our objects. All we know about initialize
is that it is called sometime toward the end of the process of creating a new object instance and that it is a method that we can override in our class to do any specific initialization. Sounds like a hook method to me.
Class#new
calls allocate
first, then initialise
. Every class inherits the new
method, and defines its own concrete initialise
method. So, we can treat Class#new
as a template method, and initialise
as a hook method.
The Strategy
Basic idea is Delegate, delegate, delegate and Prefer composition over inheritance.
Description
- Pull the algorithm out into a seperate “strategy” object.
- All of the startegy objects support the same interface.
- Let the context choose.
Given that all of the strategy objects look alike from the outside, the user of the strategy—called the context class by the GoF—can treat the strategies like interchangeable parts.
Comparing to the Template Method
The Template Method pattern is built around inheritance.
No matter how carefully you design your code, your subclasses are tangled up with their superclass: It is in the nature of the relationship. On top of this, inheritance-based techniques such as the Template Method pattern limit our runtime flexibility. Once we have selected a particular variation of the algorithm—in our example, once we have created an instance of HTMLReport—changing our mind is hard.
1 2 3 4 5 |
|
Because the Strategy pattern is based on composition and delegation, rather than on inheritance, it is easy to switch strategies at runtime.
1 2 3 4 5 |
|
Sharing Data between the Context and Strategy
- Pass in everything that the strategy needs as arguments when the context calls the methods on the strategy object. The downside of doing things this way is that if there is a lot of complex data to pass between the context and the strategy, then, well, you are going to be passing a lot of complex data around without any guarantee that it will get used.
- Having the context object pass a reference to itself to the strategy object.
Using and Abusing
Particular attention to the details of the interface between the context and the strategy as well as to the coupling between them. Remember, the Strategy pattern will do you little good if you couple the context and your first strategy so tightly together that you cannot wedge a second or a third strategy into the design.
In the Wild
Ruby code blocks, which are essentially code wrapped up in an instant object (the Proc object), are wonderfully useful for creating quick, albeit simple, strategy objects.
Use Proc as the lightweight strategy object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
The Observer
Description
The aiming is to build a system that is highly integrated—that is, a system where every part is aware of the state of the whole.
The GoF called this idea of building a clean interface between the source of the news that some object has changed and the consumers of that news the Observer pattern. The class with the news is the subject, and the objects which are interested in getting the news are the observor.
It has always seemed to me that the Observer pattern is somewhat misnamed. While the observer object gets top billing—in fact, the only billing—it is actually the subject that does most of the work. It is the subject that is responsible for keeping track of the observers. It is also the subject that needs to inform the observers that a change has come down the pike.
Code Usage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
The Ruby standard library comes with a fine, prebuilt Observable module that provides all of the support you need to make your object, well, observable.
With the observable module, the observable object must:
- assert that it has
#changed
- call
#notify_observers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Interfaces. Pull or Push?
The key decisions that you need to make when implementing the Observer pattern all center on the interface between the subject and the observer.
Just have a single method in the observer whose only argument is the subject. The GoF term for this strategy is the pull method, because the observers have to pull whatever details about the change that they need out of the subject.
The other possibility—logically enough termed the push method—has the subject send the observers a lot of details about the change:
1 2 |
|
The advantage in providing more details is that the observers do not have to work quite as hard to keep track of what is going on. The disadvantage of the push model is that if all of the observers are not interested in all of the details, then the work of passing the data around goes for naught.
Using and Abusing
The frequency and timing of the updates. The subject class can help with all of this by avoiding broadcasting redundant updates. Just because someone updates an object, it does not mean that anything really changed.
1 2 3 4 5 6 7 8 |
|
The consistency of the subject as it informs its observers of changes.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Badly behaved observers. Like responds by raising an exception?
In the Wild
Use Proc as Observers. Just use call
as the interface when notifying observers.
1 2 3 4 5 6 7 |
|
ActiveRecord::Observer has been deprecated from Rails 4.0, but we can still get the feature by the extracted gem. rails-observers
Composite
Description
- component, a common interface or base class for all of your objects.
- leaf, the class doing simple, indivisible building blocks of process.
- composite, a component, also a higher-level object that is build from subcomponents.
The GoF called the design pattern for our “the sum acts like one of the parts” situation the Composite pattern. You will know that you need to use the Composite pattern when you are trying to build a hierarchy or tree of objects, and you do not want the code that uses the tree to constantly have to worry about whether it is dealing with a single object or a whole bushy branch of the tree. Once you grasp its recursive nature, the Composite pattern is really quite simple.
Code Usage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
Concerns
How to handle the difference between a composite and a leaf?
The goal of the Composite pattern is to make the leaf objects more or less indistinguishable from the composite objects. But there is one unavoidable difference between a composite and a leaf: The composite has to manage its children, which probably means that it needs to have a method to get at the children and possibly methods to add and remove child objects. The leaf classes, of course, really do not have any children to manage; that is the nature of leafyness.
As I say, how you handle this decision is mostly a matter of taste: Make the leaf and composite classes different, or burden the leaf classes with embarrassing methods that they do not know how to handle. My own instinct is to leave the methods off of the leaf classes. Leaf objects cannot handle child objects, and we may as well admit it.
How to traverse the tree structrue which the composite pattern make?
Each composite object holds references to its subcomponents but the child components do not know a thing about their parents, it is easy to traverse the tree from the root to the leaves but hard to go the other way.
Add a parent reference in the component class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Using and Abusing
The error that crops up so frequently with the Composite pattern is assuming that the tree is only one level deep—that is, assuming that all of the child components of a composite object are, in fact, leaf objects and not other composites.
Remember, the power of the Composite pattern is that it allows us to build arbitrarily deep trees.
In the Wild
The Iterator
Description
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Iterators in Ruby are a great example of what is right with the language. Instead of providing special-purpose external iterator objects for each aggregate class, Ruby relies on the very flexible idea of Proc objects and code blocks to build internal iterators.
external iterator, the iterator is a separate object from the aggregate.
1 2 3 4 5 6 7 8 |
|
internal iterator, the code block-based iterators, all of the iterating action occurs inside the aggregate object.
1 2 3 4 5 6 7 8 9 10 11 |
|
Internal Iterators vs. External Iterators
With Internal Iterator, the main advantage is simplicity and code clarity.
With External Iterator
-
You have more flexibility on iteration control. With an external iterator, you won’t call
next
until you are good and ready for the next element. With an internal iterator, by contrast, the aggregate relentlessly pushes the code block to accept item after item.If you are trying to merge the contents of two sorted arrays into a single array that was itself sorte?
the merge is actually fairly easy with an external iterator, simply create an iterator for the two input arrays and then merge the arrays by repeatedly pushing the smallest value from either of the iterators onto the output array.
-
A second advantage of external iterators is that, because they are external, you can share them—you can pass them around to other methods and objects. Of course, this is a bit of a double-edged sword: You get the flexibility but you also have to know what you are doing. In particular, beware of multiple threads getting hold of a non-thread-safe external iterator.
The Inimitable Enumerable
To mix in Enumerable
, you need only make sure that your internal iterator method is named each
and that the individual elements that you are going to iterate over have a reasonable implementation of the <=>
comparison operator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
Using and Abusing
The main danger is this: What happens if the aggregate object changes while you are iterating through it?
You may use a shallow copy when initializing.
1 2 3 4 5 6 |
|
A Ruby trick example.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Finally, a multithreaded program is a particularly dangerous home for iterators. You need to take all of the usual care to ensure that one thread does not rip the aggregate rug out from under your iterator.
In the Wild
IO
The neat thing about the IO object is that it is amphibious—it does both internal and external iterators.
1 2 3 4 5 6 7 8 9 |
|
Pathname API
Pathname tries to offer one-stop shopping for all your directory and path manipulation needs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
ObjectSpace API
ObjectSpace provides a window into the complete universe of objects that exist within your Ruby interpreter. The fundamental iterator supplied by ObjectSpace is the each_object
method. It iterates across all of the Ruby objects—everything that is loaded into your Ruby interpreter:
1 2 3 4 5 |
|
Try this execellent subclasses_of
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
The Command
Description
The idea of factoring out the action code into its own object is the essence of the Command pattern.
The key thing about the Command pattern is that it separates the thought from the deed. When you use this pattern, you are no longer simply saying, “Do this”; instead, you are saying, “Remember how to do this,” and, sometime later, “Do that thing that I told you to remember.”
Command pattern can be useful in
- Keeping track of what you need to do, or what you have already done
- Undo or redo
- Queuing up comands
Keep Track of What You Have Done
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Use Composite
When we are trying to keep track of what we are about to do—or have done—we will need a class to collect all of our commands. Hmm, a class that acts like a command, but really is just a front for a number of subcommands. Sounds like a composite:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Undo or Redo
Every undoable command that we create has two methods. Along with the usual execute
method, which does the thing, we add an unexecute
method, which undoes the same thing.
As delete a file maybe destructive, so we need to save the contents of the original file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Creating a file with CreateFile could be destructive, too: The file that we are trying to create might already exist and be overwritten as we create the new file. In a real system, we would need to deal with this possibility as well as with a host of issues related to file permissions and ownership.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Finnaly, add an unexecute
method to the CompositeCommad
class.
1 2 3 4 5 6 7 8 9 |
|
Queuing Up Commands
For example, it frequently takes a minor computer-time eternity to connect to a database. If you need to perform a number of database operations over time, you sometimes face the unpleasant choice of (1) leaving the connection open for the whole time, thereby wasting a scarce resource, or (2) wasting the time it takes to open and close the connection for each operation.
The Command pattern offers one way out of this kind of bind. Instead of performing each operation as a stand-alone task, you accumulate all of these commands in a list. Periodically, you can open a connection to the database, execute all of your commands, and flush out this list.
Using and Abusing
The key thing about the Command pattern is that it separates the thought from the deed. When you use this pattern, you are no longer simply saying, “Do this”; instead, you are saying, “Remember how to do this,” and, sometime later, “Do that thing that I told you to remember.” Make sure that you really need that complexity before you pull the Command pattern out of your bag of tricks.
Creation Time versus Execution Time
Assuming you really do need the Command pattern, to make it work you have to be sure that the initial thought is complete. You have to carefully think through the circumstances in which the command object will find itself when it is executed versus when it was created. Yes, this key file was open, and that vital object was initialized when I created the command. Will it all still be there for me when the command is executed?
In the Wild
ActiveRecord::Migration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Madeleine
Imagine how slow your system would be if you had to write out a whole airport’s worth of seat assignments every time someone changed his or her mind and wanted that aisle seat after all.
Madeleine is a transactional, high-performance, object persistence framework that does not need any object relational mapping for the simple reason that it does not use a relational database—or any other kind of database, for that matter. Instead, Madeleine relies on the Ruby Marshal package, a facility for converting live Ruby objects into bytes and for turning those bytes back into objects. Unfortunately, being able to marshal your objects to a file is not by itself a complete solution to application persistence.
The Adapter
Description
An adapter is an object that crosses the chasm between the interface that you have and the interface that you need.
The client expects the target to have a certain interface. But unknown to the client, the target object is really an adapter, and buried inside of the adapter is a reference to a second object, the adaptee, which actually performs the work.
Adapt or Modify?
The choice of using an adapter or modifying the object really comes down to how well you understand the class in question and the issue of encapsulation.
Lean toward modifying the class in the following circumstances:
- The modifications are simple and clear.
- You understand the class you are modifying and the way in which it is used.
Lean toward an adapter solution in the following situations:
- The interface mismatch is extensive and complex.
- You have no idea how this class works.
Engineering is all about trade-offs. Adapters preserve encapsulation at the cost of some complexity. Modifying a class may buy you some simplification, but at the cost of tinkering with the plumbing.
Using and Abusing
One of the advantages that Ruby’s duck typing gives to adapter writers is that it allows us to create adapters that support only that part of the target interface that the client will actually use. Partially implemented adapters are something of a double-edged sword: On the one hand, it is very convenient to implement only what you absolutely need; on the other hand, your program can come to grief if the client decides to call a method that you didn’t think you needed.
In the Wild
ActiveRecord
deals with all of these differences by defining a standardized interface, encapsulated in a class called AbstractAdapter
. The AbstractAdapter
class defines the interface to a database that is used throughout ActiveRecord
.
AbstractAdapter
defines a standard method to execute a SQL select statement and return the results, called select_all
. Each individual adapter implements the select_all
method in terms of the API of the underlying database system.
The Proxy
Description
The Proxy pattern is essentially built around a little white lie. The counterfeit object, called the proxy by the GoF, has a reference to the real object, the subject, hidden inside. Whenever the client code calls a method on the proxy, the proxy simply forwards the request to the real object.
Inside the proxy is hidden a reference to the other, real object—an object that the GoF referred to as the subject.
Once we have a proxy, we have a place to stand squarely between the client and the real object. The proxy provides the ideal pinch point to exert control.
The proxy serves as a pinch point between the client and the subject:
- “Is this operation authorized?” asks the protection proxy.
- “Does the subject actually live on this other machine?” asks the remote proxy.
- “Have I actually created the subject yet?” asks the virtual proxy.
In short, the proxy controls access to the subject.
The Protection Proxy
A proxy that controls access to the subject.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
The advantage of using a proxy for protection is that it gives us a nice separation of concerns: The proxy worries about who is or is not allowed to do what. The only thing that the real bank account object need be concerned with is, well, the bank account.
By splitting the protection cleanly off from the workings of the real object, we can minimize the chance that any important information will inadvertently leak out through our protective shield.
The Remove Proxy
You could hide the complexity behind a remote proxy, an object that lives on the client machine and looks, to the client code, just like the real BankAccount object. When a request comes in, the remote proxy goes through all the horror of packaging up the request, sending it over the network, waiting for a response, unpacking the response, and returning the answer to the unsuspecting client.
From the client’s point of view, it called a method on what it thought was the real BankAccount object and sometime later—perhaps an unusually long time later—the answer came back. This is how virtually all remote procedure call (RPC) systems work.
1 2 3 4 5 6 |
|
Once the proxy object is set up, the client code no longer has to worry about the fact that the service actually lives at www.webservicex.net. Instead, it simply calls GetWeatherByZipCode and leaves all of the network details to the proxy.
The Virtual Proxy
In a sense, the virtual proxy is the biggest liar of the bunch. It pretends to be the real object, but it does not even have a reference to the real object until the client code calls a method. Only when the client actually calls a method does the virtual proxy scurry off and create or otherwise get access to the real object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
That approach tangles the proxy and the subject up a little more than we might like. We can improve on this strategy by applying a little of that Ruby code block magic:
1 2 3 4 5 6 7 8 9 10 11 |
|
Leverage Ruby
Use ghost method method_missing
and dynamic dispatch send
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Using and Abusing
Overusing method_missing
, like overusing inheritance, is a great way to obscure your code.
In the Wild
drb using a remote proxy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
The Decorator
But what if you simply need to vary the responsibilities of an object? What do you do when sometimes your object needs to do a little more, but sometimes a little less?
Description
The ConcreteComponent is the “real” object, the object that implements the basic component functionality.
The Decorator pattern is a straightforward technique that you can use to assemble exactly the functionality that you need at runtime. The Decorator class has a reference to a Component—the next Component in the decorator chain—and it implements all of the methods of the Component type.
Each decorator supports the same core interface, but adds its own twist on that interface. The key implementation idea of the Decorator pattern is that the decorators are essentially shells: Each takes in a method call, adds its own special twist, and passes the call on to the next component in line.
The Decorator pattern lets you start with some basic functionality and layer on extra features, one decorator at a time.
Why Not The Template Method?
The trouble is that the inheritance-based approach requires you to come up with all possible combinations of features up-front, at design time.
Code Usage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
Fowardable module API
Ruby provides the Forwardable module provides delegation of specified methods to a designated object, using the methods def_delegator
and def_delegators
.
1 2 3 4 5 6 7 8 9 10 11 |
|
The forwardable module is more of a precision weapon than the method_missing
technique. But the method_missing
technique really shines when you want to delegate large numbers of calls.
Dynamic Alternatives - Wrapping Methods
- Around Alias
- Refinement Wrapper
- Prepended Wrapper
Check this.
Dynamic Alternatives - Decorating with Modules
1 2 3 4 5 |
|
With both of these techniques, it is hard to undo the decoration. Unwrapping an aliased method is likely to be tedious, and you simply cannot un-include a module.
Using and Abusing
- The classic Decorator pattern is loved more by the folks who build the thing than by those who use it.
- One thing to keep in mind when implementing the Decorator pattern is that you need to keep the component interface simple.
- Another potential drawback of the Decorator pattern is the performance overhead associated with a long chain of decorators.
- Finally, one drawback of the method-aliasing technique for decorating objects is that it tends to make your code harder to debug.
In the Wild
alias_method_chain
in ActiveSupport
1 2 3 4 5 6 7 8 9 |
|
The alias_method_chain
method will rename the original write_line
method to write_line_without_timestamp
and rename write_line_with_timestamp
to plain old write_line
, essentially creating a chain of methods. The nice thing about alias_method_chain
is that, as its name suggests, you can chain together a number of enhancing methods.
Adapter, Proxy or Decorator
They are all “one object stands for another”, and the basic idea is Delegate, delegate, delegate.
- The Adapter hides the fact that some object has the wrong interface by wrapping it with an object that has the right interface.
- The Proxy also wraps another object, but not with the intent of changing the interface. Instead, the proxy has the same interface as the object that it is wrapping. The proxy isn’t there to tre; it is there to control. Proxies are good for tasks such as enforcing security, hiding the fact that an object really lives across the network, and delaying the creation of the real object until the last possible moment.
- The Decorator enables you to layer features on to a basic object.
Singleton
A singleton class has exactly one instance, and access to that one instance is available globally.
Code Usage
- Creating the class variable and initializing it with the singleton instance
- Creating the class-level
instance
method - Make
new
private.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Creating the singleton instance before you actually need it is called eager instantiation.
Singleton module API
1 2 3 4 5 6 7 |
|
The Singleton module, waits until someone calls instance before it actually creates its singleton. This technique is known as lazy instantiation.
Alternatives
Global Variables and Constants
- If you use a global variable or a constant for this purpose, there is no way to delay the creation of the singleton object until you need it.
- Neither of these techniques does anything to prevent someone from creating a second or third instance of your supposedly singleton class.
Class and Module methods
Lazy initialization remains and all of those self.methods
and @@variables
makes a strange feel.
Using and Abusing
Don’t expect the Singleton module really prevent anything
1 2 3 4 5 6 7 8 9 |
|
Use public_class_method
.
1 2 3 4 5 6 7 |
|
Use clone
1 2 3 4 5 6 |
|
The Ruby philosophy is that if you decide to circumvent the very clear intent of the author of the ClassBasedLogger class by cloning it, the language is there to help you out. You are in the driver’s seat, not the language. By keeping almost everything open to modification, Ruby allows you to do the things that you say you want to do—but it is up to you to say the right things.
Coupling concern
Create a singleton, and you have just made it possible for widely separated bits of your program to use that singleton as a secret channel to communicate with each other and, in the process, tightly couple themselves to each other. The horrible consequences of this coupling are why software engineering got out of the global variable business in the first place.
There is only one solution to this problem: Don’t do that.
Considering the count, Do I really only need one instance?
a Need-to-Know Basis
Another mistake that many people make is to spread the knowledge of a class’s singleton-ness far and wide.
1 2 3 4 5 6 7 8 9 |
|
Which classes are actually aware that DatabaseConnectionManager is a singleton?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
A better approach might be to concentrate the knowledge that DatabaseConnectionManager
is a singleton in the PreferenceManager
class and simply pass it into the preference reader and writer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Test Interferes
As the Singleton saves the state, there is one exceedingly nasty thing about the Singleton pattern is the way that it interferes with unit testing.
One way to deal with this problem is to create two classes: an ordinary (i.e., non-singleton) class that contains all of the code, and a subclass of the first class that is a singleton.
1 2 3 4 5 6 7 8 9 |
|
The actual application code uses the SingletonLogger
, while the tests can use the plain old, non-singleton Logger
class.
In the Wild
Inflections in ActiveSupport
The Inflections
class is a singleton, which saves space and ensures that the same inflection rules are available everywhere.
Rake::Application in rake API
As it runs, rake—like most build tools—reads in information about what it needs to do: which directories to create, which files to copy, and so on. All of this information needs to be available to all of the moving parts of rake, so rake stores it all in a single object (the Rake::Application
object, to be precise) that is available as a singleton to the entire rake program.
Factory
picking the right class for the circumstances
Description
The GoF called this technique of pushing the “which class” decision down on a subclass the Factory Method pattern.
- The creators are the base and concrete classes that contain the factory methods.
- The products are the objects being created.
At its heart, this pattern is really just the Template Method pattern applied to the problem of creating new objects. In both the Factory Method pattern and the Template Method pattern, a generic part of the algorithm is coded in the generic base class, and subclasses fill in the blanks left in the base class.
Parameterized Factory Method
Parameterized factory method is a method that can produce either a plant or an animal, depending on the symbol that is passed in:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Claasses Are Just Objects, Too
While the GoF concentrated on inheritance-based implementations of their factories, we can get the same results with much less code by taking advantage of the fact that in Ruby, classes are just objects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
Abstract Factory
An object dedicated to creating a compatible set of objects is called an abstract factory.
The problem is that you need to create sets of compatible objects. The solution is that you write a separate class to handle that creation.
The important thing about the abstract factory is that it encapsulates the knowledge of which product types go together. You can express that encapsulation with classes and subclasses, or you can get to it by storing the class objects as we did in the code above. Either way, you end up with an object that knows which kind of things belong together.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
Claasses Are Just Objects, Too
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Naming
Another way that we can simplify the implementation of abstract factories is to rely on a consistent naming convention for the product classes.
Factory && Abstract Factory
- The Factory Method pattern is really the Template Method pattern applied to object creation.
- the Abstract Factory pattern is simply the Strategy pattern applied to the same problem.
Using and Abusing
Not every object needs to be produced by a factory. (You Ain’t Goona Need It).
Engineers do have a tendency to build the Queen Mary (or perhaps the Titanic?) when a canoe will suffice. If you have a choice of exactly one class at the moment, put off adding in a factory.
In the Wild
Base in ActiveRecord
1 2 3 |
|
Builder
Description
Builder pattern, a pattern designed to help you configure those complex objects. The builder class takes charge of assembling all of the components of a complex object.
The client of the builder object the director because it directs the builder in the construction of the new object (called the product). Builders not only ease the burden of creating complex objects, but also hide the implementation details.
The idea behind the Builder pattern is that if your object is hard to build, if you have to write a lot of code to configure each object, then you should factor all of that creation code into a separate class, the builder.
The builders are less concerned about picking the right class and more focused on helping you configure your object.
- Take control of configuring your object
- Prevent you from constructing an invalid object
Code Usage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
Builders Can Ensure Sane Objects
That final “give me my object” method makes an ideal place to check that the configuration requested by the client really makes sense and that it adheres to the appropriate business rules.
1 2 3 4 5 6 7 |
|
Resuable Buidlers
An important issue to consider when writing and using builders is whether you can use a single builder instance to create multiple objects.
One way to deal with this issue is to equip your builder with a reset
method, which reinitializes the object under construction.
1 2 3 4 5 6 |
|
The reset method will let you reuse the builder instance, but it also means that you have to start the configuration process all over again for each computer. If you want to perform the configuration once and then have the builder produce any number of objects based on that configuration, you need to store all of the configuration information in instance attributes and create the actual product only when the client asks for it.
Better Builders with Magic Methods
Use ghost method method_missing
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Using and Abusing
It is usually fairly easy to spot code that is missing a builder: You can find the same object creation logic scattered all over the place. Another hint that you need a builder is when your code starts producing invalid objects.
Builder pattern sometimes creeps up on you as your application becomes increasingly complex.
In the Wild
Interpreter
Description
Interpreter pattern, which suggests that sometimes the best way to solve a problem is to invent a new language for just that purpose.
The heart of the Interpreter pattern is the abstract syntax tree.
The GoF called such values or conditions supplied at the time the AST is interpreted the context
.
- The parser reads in the program text and produces a data structure, called an abstract syntax tree (AST).
- The AST is evaluated against some set of external conditions, or context, to produce the desired computation.
ASTs are, in fact, specialized examples of the Composite pattern, with the nonterminal expressions playing the parts of the composites.
You can supply your clients with an API for building up the tree in code, or you can write a parser that takes strings and turns them into the AST.
With a Parser
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
Let XML or YAML Do The Parsing
Keep in mind that the main motivation behind building an interpreter is to give your users a natural way to express the kind of processing that needs to be done.
Racc
Racc is modeled (and named) after the venerable UNIX YACC utility. Racc takes as input a description of the grammar for your language and spits out a parser, written in Ruby for that language.
Without a Parser
Internal Domain-Specifc Languages.
You may implement your Interpreter pattern in such a way that users could write their pro-grams in actual Ruby code. Maybe you could design your AST API in such a way that the code flows so naturally that your users might be unaware that they are, in fact, writing Ruby code.
Using and Abusing
- The complexity issue. (The sheer number of components is why the Interpreter pattern is in practice limited to relatively simple languages.)
- Program efficiency, it is probably best to limit your use of the Interpreter pattern to areas that do not demand high performance.
In the Wild
SQL
HTML
Ruby, of course, an interpreted language.
regular expression
Domain-Specific Languages
Description
The DSL pattern suggests that you should focus on the language itself, not on the interpreter.
External DSLs are external in the sense that there is a parser and an interpreter for the DSL, and there are the programs written in the DSL.
An internal DSL, according to Fowler, is one in which we start with some implementation language, perhaps Ruby, and we simply bend that one language into being our DSL.
Using and Abusing
- You are limited to what you can parse with a Ruby-based internal DSL.
- Error messages.
In the Wild
The most prominent example of a pure internal DSL in the Ruby world is probably rake, Ruby’s answer to ant or make.
rake, Ruby’s answer to ant or make.
Custom Objects
Meta-programming certainly takes a different tack in producing the right object, at its heart this pattern focuses on leveraging the flexibility of Ruby.
- We can start with a simple object and add individual methods or even whole modules full of methods to it.
- Using
class_eval
, we can generate completely new methods at runtime. - We can take advantage of Ruby’s reflection facilities, which allow a program to examine its own structure
A note:
The
attr_accessor
method and its friends live in the moduleModule
, which is included by theObject
class. If you go looking for the Ruby code forattr_accessor
,attr_reader
, andattr_writer
, however, you are destined to be disappointed. For the sake of efficiency—but purely for efficiency—these methods are written in C.
Custom-Tailoring Technique
This custom-tailoring technique is particularly useful when you have lots of orthogonal features that you need to assemble into a single object.
Of course, there really is no rule that says you need to start your customizations with a plain-vanilla instance of Object. In real life, you will likely want to start with an instance of a class that provides some base level of functionality and then tweak the methods from there.
1 2 3 4 5 6 7 8 9 10 |
|
No matter whether you tailor your objects one method at a time or in module-sized chunks, the ultimate effect is to create a customized object, uniquely made to order for the requirements of the moment.
Reflections
If you are meta-programming new functionality into your classes on the fly, how can you tell what any given instance can do?
Reflection features like public_methods
and respond_to?
are handy anytime but become real assets as you dive deeper and deeper into meta-programming, when what your objects can do depends more on their history than on their class.
Using and Abusing
Tests are absolutely mandatory for systems that use a lot of meta-programming.
Convention Over Configuration
The common message is that you should not just take your language as you find it, but rather mold it into something closer to the tool that you need to solve the problem at hand.
Description
The Convention Over Configuration pattern suggests that you define a convention that a sensible engineer might use anyway.
-
Try to deduce how your users will behave.
-
You can give your user a kick start by supplying him or her with a model, a template, or an example to follow. You could also supply a utility to generate the outline or scaffold of a class. It is easy to discount the value of this scaffold-generating script.
Using and Abusing
One danger in building convention-based systems is that your convention might be incomplete, thereby limiting the range of things that your system can do.
Our message gateway, for example, does not really do a thorough job of transforming host names into Ruby class names. The code in this chapter will work fine with a simple host name like russolsen.com, transforming it into RussOlsenDotCom. But feed our current system something like icl-gis.com and it will go looking for the very illegal Icl-gisDotComAuthorizer class.
Another potential source of trouble is the possibility that a system that uses a lot of conventions may seem like it is operating by magic to the new user. Configuration files may be a pain in the neck to write and maintain, but they do provide a sort of road map—perhaps a very complicated and hard-to-interpret road map, but a map nevertheless—to the inner workings of the system. A well-done convention-based system, by contrast, needs to supply its operational road map in the form of (gasp!) documentation.
Also keep in mind that as the convention magic becomes deeper and more complex, you will need ever more thorough unit tests to ensure that your conventions behave, well, conventionally.