viphat / til

Today I Learned
http://notes.viphat.work
0 stars 1 forks source link

[Ruby] - Design Patterns in Ruby - 1. Building Better Programs with Patterns #146

Open viphat opened 7 years ago

viphat commented 7 years ago

Chapter 1 - Building Better Programs with Patterns

For Author of this book, those patterns (which is mentioned from GoF book) can boil down to four points:

  1. Separate out the things that change from those that stay the same.
  2. Program to an interface, not an implementation.
  3. Prefer composition over inheritance.
  4. Delegate, delegate, delegate.

And ... You ain't gonna need it.

Separate Out the Things That Change from Those That Stay the Same

Identify which aspects of your system design are likely to change and isolate those bits from the more stable parts.

The ideal system - All changes are local?

Program to an interface, not an implementation

Less tightly coupled

Programming to an interface

# bad solution
if is_car
  my_car = Car.new
  my_car.drive(200)
else
  my_plane = AirPlane.new
  my_plane.fly(200)
end
# better solution
my_vehicle = get_vehicle
my_vehicle.travel(200)

The idea here is to program to the most general type you can—not to call a car a car if you can get away with calling it a vehicle, regardless of whether Car and Vehicle are real classes or abstract interfaces. And if you can get away with calling your car something more general still, such as a movable object, so much the better.

viphat commented 7 years ago

Prefer Composition over Inheritance

Inheritance sometimes seems like the solution to every problem. Need to model a car? Just subclass Vehicle, which is a kind of MovableObject.

ch01fig01

But inheritance causes tightly coupled. Change the behavior of the superclass, and there is an excellent chance that you have also changed the behavior of the subclass.

So we should not rely on inheritance as much as we do.

Alternative to inheritance: composition - Instead of creating classes that inherit most of their talents from a superclass, we can assemble functionality from the bottom up. To do so, we equip our objects with references to other objects—namely, objects that supply the functionality that we need. Because the functionality is encapsulated in these other objects, we can call on it from whichever class needs that functionality.

In short, we try to avoid saying that an object is a kind of something and instead say that it has something.

class Vehicle
  # All sorts of vehicle-related code...

  def start_engine
    # Start the engine
  end

  def stop_engine
    # Stop the engine
  end
end

class Car < Vehicle
  def sunday_drive
    start_engine
    # Cruise out into the country and return
    stop_engine
  end
end

The thinking behind this code goes something like this: Our car needs to start and stop its engine, but so will a lot of other vehicles, so let’s abstract out the engine code and put it up in the common Vehicle base class

ch01fig02

That is great, but now all vehicles are required to have an engine. If we come across an engine-less vehicle (for example, a bicycle or a sailboat), we will need to perform some serious surgery on our classes. Further, unless we take extraordinary care in building the Vehicle class, the details of the engine are probably exposed to the Car class—after all, the engine is being managed by Vehicle and a Car is nothing but a flavor of a Vehicle. This is hardly the stuff of separating out the changeable from the static.

We can avoid all of these issues by putting the engine code into a class all of its own—a completely stand-alone class, not a superclass of Car:

class Engine
  # All sorts of engine-related code...

  def start
    # Start the engine
  end

  def stop
    # Stop the engine
  end
end
class Car
  def initialize
    @engine = Engine.new
  end

  def sunday_drive
    @engine.start
    # Cruise out into the country and return...
    @engine.stop
  end
end

The engine code is factored out into its own class, ready for reuse (via composition, of course!). As a bonus, by untangling the engine-related code from Vehicle, we have simplified the Vehicle class.

ch01fig03

We have also increased encapsulation. Separating out the engine-related code from Vehicle ensures that a firm wall of interface exists between the car and its engine.

viphat commented 7 years ago

Delegate, Delegate, Delegate

class Car
  def initialize
    @engine = GasolineEngine.new
  end

  def sunday_drive
    @engine.start
    # Cruise out into the country and return...
    @engine.stop
  end

  def switch_to_diesel
    @engine = DieselEngine.new
  end

  def start_engine
    @engine.start
  end
  def stop_engine
    @engine.stop
  end
end

This simple “pass the buck” technique goes by the somewhat pretentious name of delegation. Someone calls the start_engine method on our Car. The car object says, “Not my department,” and hands the whole problem off to the engine.

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—all of those boring delegating methods such as start_engine and stop_engine that do nothing except pass the buck on to the object that really knows what to do.

viphat commented 7 years ago

You Ain’t Gonna Need It (YAGNI)

The YAGNI principle says simply that you should not implement features, or design in flexibility, that you don’t need right now. Why? Because chances are, you ain’t gonna need it later, either.

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. If you are not sure that you need it right now, postpone implementing the functionality until you really do need it. If you do not need it now, do not implement it now; instead, spend your time and energy implementing the things that you definitely need right now.

At the heart of the YAGNI idea is the simple realization that we tend to be wrong when we try to anticipate exactly what we will need in the future. When you put in some feature or add some new bit of flexibility to your system before you really need it, you are making a two-pronged bet.

First, you are betting that you will eventually need this new feature. If you make your persistence layer database independent today, you are betting that someday you will need to port to a new database. If you internationalize your GUI now, you are betting that someday you will have users in some foreign land. But as Yogi Berra is supposed to have said, predictions are hard, especially about the future. If it turns out that you never need to work with another database, or if your application never makes it out of its homeland, then you have done all of that up-front work and lived with all of the additional complexity for naught.

The second prong of the bet that you make when you build something before you actually need it is perhaps even more risky. When you implement some new feature or add some new flexibility before its time, you are betting that you can get it right, that you know how to correctly solve a problem that you haven’t really encountered yet. You are betting that the database independence layer you so lovingly installed last year will be able to handle the database the system actually does move to: “What! Marketing wants us to support xyzDB? I’ve never heard of it!” You are betting that your GUI internationalization will be able to deal with the specific languages that you need to support: “Gee, I didn’t realize that we would need to support a language that reads from right to left . . .”

We are all learning, getting smarter every day. This is especially true in software projects: You can be sure that you will have a better grasp of the requirements, technology, and design of any software system that you work on at the end of the project than at the beginning. Whenever you put in a feature before you really need it, you are guilty of programming while stupid; if you wait until you really need the thing, you are likely to have a better understanding of what you need to do and how you should go about doing it.

Design patterns are all about making your systems more flexible, more able to roll smoothly with change. But 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. Patterns are useful techniques, rather than ends in themselves. They can help you construct a working system, but 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.

viphat commented 7 years ago

To give you a preview of what lies in store for you, here is a quick overview of the GoF patterns covered in this book: