Chapter 5. Reducing Costs with Duck Typing

The purpose of object-oriented design is to reduce the cost of change. Now that you know messages are at the design center of your application, and now that you are committed to the construction of rigorously defined public interfaces, you can combine these two ideas into a powerful design technique that further reduces your costs.

This technique is known as duck typing. Duck types are public interfaces that are not tied to any specific class. These across-class interfaces add enormous flexibility to your application by 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. This is how the technique gets its name; if an object quacks like a duck and walks like a duck, then its class is immaterial, it’s a duck.

This chapter shows you how to recognize and exploit duck types to make your application more flexible and easier to change.

Understanding Duck Typing

Programming languages use the term “type” to describe the category of the contents of a variable. Procedural languages provide a small, fixed number of types, generally used to describe kinds of data. Even the humblest language defines types to hold strings, numbers, and arrays.

It is knowledge of the category of the contents of a variable, or its type, that allows an application to have an expectation about how those contents will behave. Applications quite reasonably assume that numbers can be used in mathematical expressions, strings concatenated, and arrays indexed.

In Ruby these expectations about the behavior of an object come in the form of beliefs about its public interface. If one object knows another’s type, it knows to which messages that object can respond.

An instance of the Mechanic class contains, obviously, the complete public interface of Mechanic. It is blindingly apparent that any object holding onto an instance of Mechanic can treat the instance as if it is a Mechanic; the object, by its very nature, implements the Mechanic class’s public interface.

However, you are not limited to expecting an object to respond to just one interface. A Ruby object is like a partygoer at a masquerade ball that changes masks to suit the theme. It can expose a different face to every viewer; it can implement many different interfaces.

Just as beauty is in the physical world, within your application an object’s type is in the eye of the beholder. Users of an object need not, and should not, be concerned about its class. Class is just one way for an object to acquire a public interface; the public interface an object obtains by way of its class may be one of several that it contains. Applications may define many public interfaces that are not related to one specific class; these interfaces cut across class. Users of any object can blithely expect it to act like any, or all, of the public interfaces it implements. It’s not what an object is that matters, it’s what it does.

If every object trusts all others to be what it expects at any given moment, and any object can be any kind of thing, the design possibilities are infinite. These possibilities can be used to create flexible designs that are marvels of structured creativity or, alternatively, to construct terrifying designs that are incomprehensibly chaotic.

Using this flexibility wisely requires that you recognize these across-class types and construct their public interfaces as intentionally and as diligently as you did those of within-class types back in Chapter 4, Creating Flexible Interfaces. Across-class types, duck types, have public interfaces that represent a contract that must be explicit and well-documented.

The best way to explain duck types is to explore the consequences of not using them. This section contains an example that goes through several refactorings, solving a messy design problem by finding and implementing a duck type.

Overlooking the Duck

In the following code Trip’s prepare method sends message prepare_bicycles to the object contained in its mechanic parameter. Notice that the Mechanic class is not referenced; even though the parameter name is mechanic, the object it contains could be of any class.


 1  class Trip
 2    attr_reader :bicycles, :customers, :vehicle
 3 
 4    # this 'mechanic' argument could be of any class
 5    def prepare(mechanic)
 6      mechanic.prepare_bicycles(bicycles)
 7    end
 8 
 9    # ...
10  end
11 
12  # if you happen to pass an instance of *this* class,
13  # it works
14  class Mechanic
15    def prepare_bicycles(bicycles)
16      bicycles.each {|bicycle| prepare_bicycle(bicycle)}
17    end
18 
19    def prepare_bicycle(bicycle)
20      #...
21    end
22  end


Figure 5.1 contains the corresponding sequence diagram, where an outside object gets everything started by sending prepare to Trip, passing along an argument.

Image

Figure 5.1. Trip prepares itself by asking a mechanic to prepare the bicycles.

The prepare method has no explicit dependency on the Mechanic class but it does depend on receiving an object that can respond to prepare_bicycles. This dependency is so fundamental that it’s easy to miss or to discount, but nonetheless, it exists. Trip’s prepare method firmly believes that its argument contains a preparer of bicycles.

Compounding the Problem

You may already have noticed that this example is like the sequence diagram in Figure 4.6 of Chapter 4. The next refactoring there improved the design by pushing knowledge of how a Trip gets prepared into Mechanic. The next example here, alas, is no improvement at all.

Imagine that requirements change. In addition to a mechanic, trip preparation now involves a trip coordinator and a driver. Following the established pattern of the code, you create new TripCoordinator and Driver classes and give them the behavior for which they are responsible. You also change Trip’s prepare method to invoke the correct behavior from each of its arguments.

The following code illustrates the change. The new TripCoordinator and Driver classes are simple and inoffensive but Trip’s prepare method is now a cause for alarm. It refers to three different classes by name and knows specific methods implemented in each. Risks have dramatically gone up. Trip’s prepare method might be forced to change because of a change elsewhere and it might unexpectedly break as the result of a distant, unrelated change.


 1  # Trip preparation becomes more complicated
 2  class Trip
 3    attr_reader :bicycles, :customers, :vehicle
 4 
 5    def prepare(preparers)
 6      preparers.each {|preparer|
 7        case preparer
 8        when Mechanic
 9          preparer.prepare_bicycles(bicycles)
10        when TripCoordinator
11          preparer.buy_food(customers)
12        when Driver
13          preparer.gas_up(vehicle)
14          preparer.fill_water_tank(vehicle)
15        end
16      }
17    end
18  end
19 
20  # when you introduce TripCoordinator and Driver
21  class TripCoordinator
22    def buy_food(customers)
23      # ...
24    end
25  end
26 
27  class Driver
28    def gas_up(vehicle)
29      #...
30    end
31 
32    def fill_water_tank(vehicle)
33      #...
34    end
35  end


This code is the first step in a process that will paint you into a corner with no way out. Code like this gets written when programmers are blinded by existing classes and neglect to notice that they have overlooked important messages; this dependent-laden code is a natural outgrowth of a class-based perspective.

The roots of the problem are innocent enough. It’s easy to fall into the trap of thinking of the original prepare method as expecting an actual instance of Mechanic. Your technical brain surely recognizes that prepare's argument can legally be of any class, but that doesn’t save you; in your heart of hearts you think of the argument as being a Mechanic.

Because you know that Mechanic understands prepare_bicycle and are confident that you are passing a Mechanic, initially all is well. This perspective works fine until something changes and instances of classes other than Mechanic begin to appear on the argument list. When that happens, prepare must suddenly deal with objects that do not understand prepare_bicycle.

If your design imagination is constrained by class and you find yourself unexpectedly dealing with objects that don’t understand the message you are sending, your tendency is to go hunt for messages that these new objects do understand. Because the new arguments are instances of TripCoordinator and Driver, you naturally examine the public interfaces of those classes and find buy_food, gas_up and fill_water_tank. This is the behavior that prepare wants.

The most obvious way to invoke this behavior is to send these very messages, but now you’re stuck. Every one of your arguments is of a different class and implements different methods; you must determine each argument’s class to know which message to send. Adding a case statement that switches on class solves the problem of sending the correct message to the correct object but causes an explosion of dependencies.

Count the number of new dependencies in the prepare method. It relies on specific classes, no others will do. It relies on the explicit names of those classes. It knows the names of the messages that each class understands, along with the arguments that those messages require. All of this knowledge increases risk; many distant changes will now have side effects on this code.

To make matters worse, this style of code propagates itself. When another new trip preparer appears, you, or the next person down the programming line, will add a new when branch to the case statement. Your application will accrue more and more methods like this, where the method knows many class names and sends a specific message based on class. The logical endpoint of this programming style is a stiff and inflexible application, where it eventually becomes easier to rewrite everything than to change anything.

Figure 5.2 shows the new sequence diagram. 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.

Image

Figure 5.2. Trip knows too many concrete classes and methods.

Finding the Duck

The key to removing the dependencies is to recognize that because Trip’s prepare method serves a single purpose, its arguments arrive wishing to collaborate to accomplish a single goal. Every argument is here for the same reason and that reason is unrelated to the argument’s underlying class.

Avoid getting sidetracked by your knowledge of what each argument’s class already does; think instead about what prepare needs. Considered from prepare’s point of view, the problem is straightforward. The prepare method wants to prepare the trip. Its arguments arrive ready to collaborate in trip preparation. The design would be simpler if prepare just trusted them to do so.

Figure 5.3 illustrates this idea. Here the prepare method doesn’t have a preordained expectation about the class of its arguments, instead it expects each to be a “Preparer.”

Image

Figure 5.3. Trip needs each argument to act like a preparer.

This expectation neatly turns the tables. You’ve pried yourself loose from existing classes and invented a duck type. The next step is to ask what message the prepare method can fruitfully send each Preparer. From this point of view, the answer is obvious: prepare_trip.

Figure 5.4 introduces the new message. Trip’s prepare method now expects its arguments to be Preparers that can respond to prepare_trip.

Image

Figure 5.4. Trip collaborates with the preparer duck.

What kind of thing is Preparer? At this point it has no concrete existence; it’s an abstraction, an agreement about the public interface on an idea. It’s a figment of design.

Objects that implement prepare_trip are Preparers and, conversely, objects that interact with Preparers only need trust them to implement the Preparer interface. Once you see this underlying abstraction, it’s easy to fix the code. Mechanic, TripCoordinator and Driver should behave like Preparers; they should implement prepare_trip.

Here’s the code for the new design. The prepare method now expects its arguments to be Preparers and each argument’s class implements the new interface.


 1  # Trip preparation becomes easier
 2  class Trip
 3    attr_reader :bicycles, :customers, :vehicle
 4 
 5    def prepare(preparers)
 6      preparers.each {|preparer|
 7        preparer.prepare_trip(self)}
 8    end
 9  end
10 
11  # when every preparer is a Duck
12  # that responds to 'prepare_trip'
13  class Mechanic
14    def prepare_trip(trip)
15      trip.bicycles.each {|bicycle|
16        prepare_bicycle(bicycle)}
17    end
18 
19    # ...
20  end
21 
22  class TripCoordinator
23    def prepare_trip(trip)
24      buy_food(trip.customers)
25    end
26 
27    # ...
28  end
29 
30  class Driver
31    def prepare_trip(trip)
32      vehicle = trip.vehicle
33      gas_up(vehicle)
34      fill_water_tank(vehicle)
35    end
36    # ...
37  end


The prepare method can now accept new Preparers without being forced to change, and it’s easy to create additional Preparers if the need arises.

Consequences of Duck Typing

This new implementation has a pleasing symmetry that suggests a rightness about the design, but the consequences of introducing a duck type go deeper.

In the initial example, the prepare method depends on a concrete class. In this most recent example, prepare depends on a duck type. The path between these examples leads through a thicket of complicated, dependent-laden code.

The concreteness of the first example makes it simple to understand but dangerous to extend. The final, duck typed, alternative is more abstract; it places slightly greater demands on your understanding but in return offers ease of extension. Now that you have discovered the duck, you can elicit new behavior from your application without changing any existing code; you simply turn another object into a Preparer and pass it into Trip’s prepare method.

This tension between the costs of concretion and the costs of abstraction is fundamental to object-oriented design. Concrete code is easy to understand but costly to extend. Abstract code may initially seem more obscure but, once understood, is far easier to change. Use of a duck type moves your code along the scale from more concrete to more abstract, making the code easier to extend but casting a veil over the underlying class of the duck.

The ability to tolerate ambiguity about the class of an object is the hallmark of a confident designer. Once you begin to treat your objects as if they are defined by their behavior rather than by their class, you enter into a new realm of expressive flexible design.

Writing Code That Relies on Ducks

Using duck typing relies on your ability to recognize the places where your application would benefit from across-class interfaces. It is relatively easy to implement a duck type; your design challenge is to notice that you need one and to abstract its interface.

This section contains patterns that reveal paths you can follow to discover ducks.

Recognizing Hidden Ducks

Many times unacknowledged duck types already exist, lurking within existing code. Several common coding patterns indicate the presence of a hidden duck. You can replace the following with ducks:

• Case statements that switch on class

kind_of? and is_a?

responds_to?

Case Statements That Switch on Class

The most common, obvious pattern that indicates an undiscovered duck is the example you’ve already seen; a case statement that switches on the class names of domain objects of your application. The following prepare method (same as above) should grab your attention as if it were playing trumpets.


 1  class Trip
 2    attr_reader :bicycles, :customers, :vehicle
 3 
 4    def prepare(preparers)
 5      preparers.each {|preparer|
 6        case preparer
 7        when Mechanic
 8          preparer.prepare_bicycles(bicycles)
 9        when TripCoordinator
10          preparer.buy_food(customers)
11        when Driver
12          preparer.gas_up(vehicle)
13          preparer.fill_water_tank(vehicle)
14        end
15      }
16    end
17  end


When you see this pattern you know that all of the preparers must share something in common; they arrive here because of that common thing. Examine the code and ask yourself, “What is it that prepare wants from each of its arguments?”

The answer to that question suggests the message you should send; this message begins to define the underlying duck type.

Here the prepare method wants its arguments to prepare the trip. Thus, prepare_trip becomes a method in the public interface of the new Preparer duck.

kind_of? and is_a?

There are various ways to check the class of an object. The case statement above is one of them. The kind_of? and is_a? messages (they are synonymous) also check class. Rewriting the previous example in the following way does nothing to improve the code.


 1    if preparer.kind_of?(Mechanic)
 2      preparer.prepare_bicycles(bicycle)
 3    elsif preparer.kind_of?(TripCoordinator)
 4      preparer.buy_food(customers)
 5    elsif preparer.kind_of?(Driver)
 6      preparer.gas_up(vehicle)
 7      preparer.fill_water_tank(vehicle)
 8    end


Using kind_of? is no different than using a case statement that switches on class; they are the same thing, they cause exactly the same problems, and they should be corrected using the same techniques.

responds_to?

Programmers who understand that they should not depend on class names but who haven’t yet made the leap to duck types are tempted to replace kind_of? with responds_to?. For example:


 1    if preparer.responds_to?(:prepare_bicycles)
 2      preparer.prepare_bicycles(bicycle)
 3    elsif preparer.responds_to?(:buy_food)
 4      preparer.buy_food(customers)
 5    elsif preparer.responds_to?(:gas_up)
 6      preparer.gas_up(vehicle)
 7      preparer.fill_water_tank(vehicle)
 8    end


While this slightly decreases the number of dependencies, this code still has too many. The class names are gone but the code is still very bound to class. What object will know prepare_bicycles other than Mechanic? Don’t be fooled by the removal of explicit class references. This example still expects very specific classes.

Even if you are in a situation where more than one class implements prepare_bicycles or buy_food, this code pattern still contains unnecessary dependencies; it controls rather than trusts other objects.

Placing Trust in Your Ducks

Use of kind_of?, is_a?, responds_to?, and case statements that switch on your classes indicate the presence of an unidentified duck. In each case the code is effectively saying “I know who you are and because of that I know what you do.” This knowledge exposes a lack of trust in collaborating objects and acts as a millstone around your object’s neck. It introduces dependencies that make code difficult to change.

Just as in Demeter violations, this style of code is an indication that you are missing an object, one whose public interface you have not yet discovered. The fact that the missing object is a duck type instead of a concrete class matters not at all; it’s the interface that matters, not the class of the object that implements it.

Flexible applications are built on objects that operate on trust; it is your job to make your objects trustworthy. When you see these code patterns, concentrate on the offending code’s expectations and use those expectations to find the duck type. Once you have a duck type in mind, define its interface, implement that interface where necessary, and then trust those implementers to behave correctly.

Documenting Duck Types

The simplest kind of duck type is one that exists merely as an agreement about its public interface. This chapter’s example code implements that kind of duck, where several different classes implement prepare_trip and can thus be treated like Preparers.

The Preparer duck type and its public interface are a concrete part of the design but a virtual part of the code. Preparers are abstract; this gives them strength as a design tool but this very abstraction makes the duck type less than obvious in the code.

When you create duck types you must both document and test their public interfaces. Fortunately, good tests are the best documentation, so you are already halfway done; you need only write the tests.

See Chapter 9, Designing Cost-Effective Tests, for more on testing duck types.

Sharing Code Between Ducks

In this chapter, Preparer ducks provide class-specific versions of the behavior required by their interface. Mechanic, Driver and TripCoordinator each implement method prepare_trip. This method signature is the only thing they have in common. They share only the interface, not the implementation.

Once you start using duck types, however, you’ll find that classes that implement them often need to share some behavior in common. Writing ducks that share code is one of the topics covered in Chapter 7.

Choosing Your Ducks Wisely

Every example thus far unequivocally declares that you should not use kind_of? or responds_to? to decide what message to send an object, yet you don’t have to look far to find reams of well-received code that do exactly that.

The following code is an example from the Ruby on Rails framework (active_record/relations/finder_methods.rb). This example patently uses class to decide how to deal with its input, a technique that is in direct opposition to the guidelines stated above. The first method below clearly decides how to behave based on the class of its args argument.

If sending a message based on the class of the receiving object is the death knell for your application, why is this code acceptable?


 1  # A convenience wrapper for <tt>find(:first, *args)</tt>.
 2  # You can pass in all the same arguments to this
 3  # method as you can to <tt>find(:first)</tt>.
 4  def first(*args)
 5    if args.any?
 6      if args.first.kind_of?(Integer) ||
 7           (loaded? && !args.first.kind_of?(Hash))
 8        to_a.first(*args)
 9      else
10        apply_finder_options(args.first).first
11      end
12    else
13      find_first
14    end
15  end


The major difference between this example and the previous ones is the stability of the classes that are being checked. When first depends on Integer and Hash, it is depending on core Ruby classes that are far more stable than it is. The likelihood of Integer or Hash changing in such a way as to force first to change is vanishingly small. This dependency is safe. There probably is a duck type hidden somewhere in this code but it will likely not reduce your overall application costs to find and implement it.

From this example you can see that the decision to create a new duck type relies on judgment. The purpose of design is to lower costs; bring this measuring stick to every situation. If creating a duck type would reduce unstable dependencies, do so. Use your best judgment.

The above example’s underlying duck spans Integer and Hash and therefore its implementation would require making changes to Ruby base classes. Changing base classes is known as monkey patching and is a delightful feature of Ruby but can be perilous in untutored hands.

Implementing duck types across your own classes is one thing, changing Ruby base classes to introduce new duck types is quite another. The tradeoffs are different; the risks are greater. Neither of these considerations should prevent you from monkey patching Ruby at need; however, you must be able to eloquently defend this design decision. The standard of proof is high.

Conquering a Fear of Duck Typing

This chapter has thus far delicately sidestepped the dynamic versus static typing battlefield, but the issue can no longer be avoided. If you have a statically typed programming background and find the idea of duck typing alarming, this section is for you.

If you are unfamiliar with the argument, are happily using Ruby, and were convinced by the prior discourse on duck typing, you can skim this section without fear of missing important new concepts. You might, however, find what follows useful if you need to fend off arguments made by your more statically typed friends.

Subverting Duck Types with Static Typing

Early in this chapter, type was defined as the category of the contents of a variable. Programming languages are either statically or dynamically typed. Most (though not all) statically typed languages require that you explicitly declare the type of each variable and every method parameter. Dynamically typed languages omit this requirement; they allow you to put any value in any variable and pass any argument to any method, without further declaration. Ruby, obviously, is dynamically typed.

Relying on dynamic typing makes some people uncomfortable. For some, this discomfort is caused by a lack of experience, for others, by a belief that static typing is more reliable.

The lack-of-experience problem cures itself, but the belief that static typing is fundamentally preferable often persists because it is self-reinforcing. Programmers who fear dynamic typing tend to check the classes of objects in their code; these very checks subvert the power of dynamic typing, making it impossible to use duck types.

Methods that cannot behave correctly unless they know the classes of their arguments will fail (with type errors) when new classes appear. Programmers who believe in static typing take these failures as proof that more type checking is needed. When more checks are added, the code becomes less flexible and even more dependent on class. The new dependencies cause additional type failures, and the programmer responds to these failures by adding yet more type checking. Anyone caught in this loop will naturally have a hard time believing that the solution to their type problem is to remove type checking altogether.

Duck typing provides a way out of this trap. It removes the dependencies on class and thus avoids the subsequent type failures. It reveals stable abstractions on which your code can safely depend.

Static versus Dynamic Typing

This section compares dynamic and static typing, hoping to allay any fears that keep you from being fully committed to dynamic types.

Static and dynamic typing both make promises and each has costs and benefits.

Static typing aficionados cite the following qualities:

• The compiler unearths type errors at compile time.

• Visible type information serves as documentation.

• Compiled code is optimized to run quickly.

These qualities represent strengths in a programming language only if you accept this set of corresponding assumptions:

• Runtime type errors will occur unless the compiler performs type checks.

• Programmers will not otherwise understand the code; they cannot infer an object’s type from its context.

• The application will run too slowly without these optimizations.

Dynamic typing proponents list these qualities:

• Code is interpreted and can be dynamically loaded; there is no compile/make cycle.

• Source code does not include explicit type information.

• Metaprogramming is easier.

These qualities are strengths if you accept this set of assumptions:

• Overall application development is faster without a compile/make cycle.

• Programmers find the code easier to understand when it does not contain type declarations; they can infer an object’s type from its context.

• Metaprogramming is a desirable language feature.

Embracing Dynamic Typing

Some of these qualities and assumptions are based on empirical facts and are easy to evaluate. There is no doubt that, for certain applications, well-optimized statically typed code will outperform a dynamically typed implementation. When a dynamically typed application cannot be tuned to run quickly enough, static typing is the alternative. If you must, you must.

Arguments about the value of type declarations as documentation are more subjective. Those experienced with dynamic typing find type declarations distracting. Those used to static typing may be disoriented by lack of type information. If you are coming from a statically typed language, like Java or C++, and feel unmoored by the lack of explicit type declarations in Ruby, hang in there. There’s lots of anecdotal evidence to suggest that, once accustomed to it, you’ll find this less verbose syntax easier to read, write, and understand.

Metaprogramming (i.e., writing code that writes code) is a topic that programmers tend to feel strongly about and the side of the argument they support is related to their past experience. If you have solved a massive problem with a simple, elegant piece of metaprogramming, you become an advocate for life. On the other hand, if you’ve faced the daunting task of debugging an overly clever, completely obscure, and possibly unnecessary bit of metaprogramming, you may perceive it as a tool for programmers to inflict pain upon one another and wish to banish it forever.

Metaprogramming is a scalpel; though dangerous in the wrong hands, it’s a tool no good programmer should willingly be without. It confers great power and requires great responsibility. The fact that some people cannot be trusted with knives does not mean sharp instruments should be taken from the hands of all. Metaprogramming, used wisely, has great value; ease of metaprogramming is a strong argument in favor of dynamic typing.

Image

Figure 5.5. Sharp instruments: useful, but not for everyone.

The two remaining qualities are static typing’s compile time type checking and dynamic typing’s lack of a compile/make cycle. Static typing advocates assert that preventing unexpected type errors at runtime is so necessary and so valuable that its benefit trumps the greater programming efficiency that is gained by removing the compiler.

This argument rests on static typing’s premise that:

• The compiler truly can save you from accidental type errors.

• Without the compiler’s help, these type errors will occur.

If you have spent years programming in a statically typed language you may accept these assertions as gospel. However, dynamic typing is here to shake the foundations of your belief. To these arguments dynamic typing says “It can’t” and “They won’t.”

The compiler cannot save you from accidental type errors. Any language that allows casting a variable to a new type is vulnerable. Once you start casting, all bets are off; the compiler excuses itself and you are left to rely on your own wits to prevent type errors. The code is only as good as your tests; runtime failures can still occur. The notion that static typing provides safety, comforting though it may be, is an illusion.

Furthermore, it doesn’t actually matter whether the compiler can save you or not; you don’t need saving. In the real world, compiler preventable runtime type errors almost never occur. It just doesn’t happen.

This is not to suggest that you’ll never experience a runtime type error. Few programmers make it through life without sending a message to an uninitialized variable or assuming an array has elements when it is actually empty. However, discovering at runtime that nil doesn’t understand the message it received is not something the compiler could have prevented. These errors are equally likely in both type systems.

Dynamic typing allows you to trade compile time type checking, a serious restriction that has high cost and provides little value, for the huge gains in efficiency provided by removing the compile/make cycle. This trade is a bargain. Take it.

Duck typing is built on dynamic typing; to use duck typing you must embrace this dynamism.

Summary

Messages are at the center of object-oriented applications and they pass among objects along public interfaces. Duck typing detaches these public interfaces from specific classes, creating virtual types that are defined by what they do instead of by who they are.

Duck typing reveals underlying abstractions that might otherwise be invisible. Depending on these abstractions reduces risk and increases flexibility, making your application cheaper to maintain and easier to change.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.172.38