POODR Qoutables

I just finished reading Practical Object Oriented Design Ruby(POODR) by Sandi Metz. This book is a must read. It’s essentially a rule book for writing clean, isolated and flexible Object Oriented Code. If you ever found yourself looking at your code and wondering where you should create a new object, if the code is “clean” or whether one of your classes is doing too much. This book works tirelessly to disspell misplaced or unorganized OO code. This digestion is all about me quoting Sandi, at points where I feel her work must be remembered.

Chapter 1

“Object Oriented design requires that you shift from thinking of the World as a collection of predesigned procedures and start modeling the world as a series of messages that pass through objects”.

“Every Application is a collection of code, the codes arrangement is design”

“Design is not an assembly line where similarly trained workers construct identical widgets ; It’s a studio where like minded artists sculpt custom applications.”

“Just as a suclptor has chisels and files, an object oriented designer has tools - Principles and Patterns”

Design Principles


SOLID
Single Reponsibility
Open Closed
Liskov Substitution
Interface Substitution
Dependency Inversion

Dont Repeat Yourself

Law of Demeter

There is a good amount of research to indicate the correlation of these principles and High Quality code.

Design Patterns


“Design Patterns are simple and elegant solutions to specific problems in Object Oriented software design that you can use to make your own designs more flexible, modular, reusable and understandeable.”

“The notion of design Patterns is incredibly powerful. To name common problems and to solve the problems in common ways brings the fuzzy into focus.”

Yet and still Designing…


Designing Object Oriented Software is hard. Simply knowing design principles and patterns is not enough. “If you think of software as custom furiniture, then principles and patterns are the woodworking tools. Knowing how software should look when its done does not cause it to build itself out. Applications come into existence because some programmer applied the tools. The end result being a beautiful cabinet or a rickety chair, reflects the programmers experience with the tools of design.”

Chapter 2

“The foundation of an object oriented system is the message, but the most visible organizational structure is the class.”

“Your goal is to model your classes such that it does what its supposed to do right now and is also easy to change later.”

“The Quality of easy changeability reveals the craft of programming. Acheiving it takes knowledge, skill and a bit of artistic creativity.”

Easy to change means:
1. “Changes have no unexpected side effects”
2. “Small changes in requirements require correspondingly small changes in code.”
3. “Existing code is easy to reuse. ”
4. “The easiest way to make a change is to add code that in itself is easy to change”

Transparent - The consequences of change should be obvious
Reasonable - The cost of change should not be dramatically felt elsewhere
Usable - It should be able to used in new and unexpected ways
Exemplary - It should encourage others to write the same

Classes should have a Single Responsibility

“In determining that a class has a single responsibility, pretend that its alive and ask it questions. and make sure the answers make Sense.” eg: “Please Mr.Gear, what is your Ratio?” vs “Please Mr.Person, what is the circumference of the earth?”

“Attempt to describe what your class is doing in one sentence. If the description, uses the words [and, or] then your class likely has more than one responsibility.”

Depend on Behavior not Data

“Behavior is captured in methods and is invoked by sending messages”
“Data is held in variables and objects”

Rule 1: Always wrap instance variables in Accessor methods.
- Instead of directly reffering to variables. Hide the variables from the class, by wrapping them. Use attr_reader as an easy way to create encapsulating methods.

Rule 2: Always Hide Data Structures.
-Do not depend on data structures. Because if the structure changes then other code will have to as well.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#In Ruby Its easy to seperate structure from meaning, with the Struct Class

class RevealingReference
  attr_reader :wheels

  def initialize(data) # This data is a multi dimensional array
  @wheels = wheelify(data)
  end

  def diameters
    @wheels.collect {|wheel| wheel.rim + (wheel.tire * 2)}
    #struct gives you wheel.rim instead of wheel[0]. Can you see the benefits there? :)
  end

  Wheel = Struct.new(:rim, :tire)
  def wheelify(data)
    data.collect {|cell| Wheel.new(cell[0], cell[1])}
  end
end

Rule 3: Extract extra responsibilities from methods.
- This will ensure that each method has a single responsibility.

“The Path to changeable and Maintanable object oriented software begins with classes that have a single responsibility. The isolation that occurs as a result of SRP allows change w/o consequence and reuse w/o duplication.”

Chapter 3

“All of the behavior is dispersed among the objects. Therefore any desired behavior, an object either knows it personally inherits it, or knows another object who knows it.”

An object has a dependency when it knows:
- “The name of another class. (Gear expects a class named Wheel to exist.)”
- “The name of a message that it intends to send to someone other than self. (Gear expects Wheel instance to respond to diameter.)”
- “The arguments that a message requires. (Gear knows that Wheel.new requires a rim and tire.)”
- “The order of those arguments. (Gear knows the first argument to Wheel.new should be rim)”

“The design challenge is to manage dependencies so that each class has the fewest possible.”

“Unmanaged dependencies can cause an entire application to become an entangled mess.”

“Reducing dependencies means recognizing and removing the ones you don’t neeed.” The following techniques reduce dependencies:

Technique 1: INJECT DEPENDENCIES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#instead of doing 
def gear_inches
  ratio * Wheel.new(x,y).diameter
end

#do this
def initialize(chain, cog, wheel)
  @chainring = chain
  @cog = cog
  @wheel = wheel
end

def gear_inches
  ratio * wheel.diameter
end

Gear.new(5, 2, Wheel.new(26, 1.5))

“You should isolate unneccessary dependencies and leave the code better than you found it.”

Technique 2: ISOLATE INSTANCE CREATION

If you are constrained in your code and cannot inject an object in the initializer. Then you should isolate the creation of a new object in method of its own in the class you need it to be in. Similar to wrapping an instance variable in an accessor method.

1
2
3
4
5
6
7
8
9
10
11
def initialize(chain, cog, rim, tire)
  @chainring = chain
  @cog = cog
  @rim = rim
  @tire = tire
end


def wheel
  @wheel ||= Wheel.new(@rim, @tire)
end

Technique 3: ISOLATE VULNERABLE EXTERNAL METHODS

Isolate messages that are sent to someone that is not self, by wrapping the message call in its own method and then referencing the method whenever you need it.

1
2
3
4
5
6
7
8
9
10
11
12
13
#Instead of this 
def gear_inches
 ration * wheel.diameter
end

#Do This
def gear_inches
 ratio * diameter
end

def diameter
  wheel.diameter
end

Technique 4: REMOVE ARGUMENT ORDER DEPENDENCIES

When you send a message that requires arguments, one main requirement becomes that you need to pass arguments in a specific fixed order. You can avoid fixed order arguments by passing in a hash instead of specific arguments.

1
2
3
4
5
6
7
def initialize(args)
@chainring = args[:chain]
@cog = args[:cog]
@rim = args[:rim]
end

#Now the order of arguments does not matter.

Technique 5: EXPLICITLY DEFINE DEFAULT VALUES

Its better to use the fetch method to set defaults than to be clever and use the || method. Just Obey this rule.

1
2
3
4
5
6
7
8
9
10
11
12
13
#Instead of this
def initialize(args)
@chainring = args[:chain] || 40
@cog = args[:cog] || 15
@rim = args[:rim] || 10
end

#Do This 
def initialize(args)
@chainring  = args.fetch(:chain, 40)
@cog = args.fetch(:cog, 15)
@rim = args.fetch(:rim, 10)
end

Chapter 4

“Design is concerned w/ messages that pass between objects.”

“It deals not only w/ what objects know and who they know, but how they talk to one another. Conversations take place using their interfaces.”

Public Interfaces Reveal its primary responsibility
Are expected to be invoked by others
Will not change on a whim
Are thoroughly documented in tests


Private Interfaces Handle implementation details
Are not expected to be sent by other objects
Can change for any reason whatsoever
Are unsafe for others to depend on

May not be reinforced in tests

“Design experts notice Domain Objects, w/o concentrating on them. They focus on messages that pass between objects”

“Domain Objects are easy to find, but they are not at the design center of your application. Instead, they are a trap for the unwary. If you fixate on domain objects you will tend to coerce behavior into them. Design experts notice D/O without concentrating on them. They focus on the messages that pass between the. These messages are guides that lead you to discover other objects, ones that are just as necessary but far less obvious.”

Finding Public Interfaces

A public interface is simply the way that objects talk to one another. Its similar to the idea of the API but its for objects passing messages to one another. ex:

1
2
3
4
5
6
7
8
9
10
#this sets up a public interface
args = {chain: 10, cog: 15, wheel: Wheel.new(10, 1.5)}
gear = Gear.new(args)

#this reveals the public nature 
gear.diameter  #====> remember diameter is (wheel.diameter) 

#here we used the gear object to find out what is the diameter of a wheel. 
#this reveals a class who is not a single responsibility class but the fact that it can do something 
#with a wheel object indirectly is very useful.

Technique 1: The Sequence Diagram

“They allow you to experiment w/ different object arrangements and message passing.”

“Message based design yields more flexible applications than does class based perspective.”

“Think to yourself: I need to send this message who should respond to it.”

“Often times, a Sequence Diagram can help you figure out if there is a missing Object.”

Technique 2: Ask For What instead of telling How

“A Customer should be able to ask a mechanic to fix a bike and the mechanic should know how to fix a bike. A Customer should not have the methods to know how to fix a bike.”


Technique 3: Keep Context to a minimum

“The context that an object expects has a direct effect on how difficult it is to reuse.” ex:

1
2
3
4
5
args = {chain: 10, cog: 5, Wheel.new(10, 1.5)}
Gear.new(args)

#The context of being able to use some public methods of Gear requires that you pass in a Wheel Object
#The more dependencies on an object. The harder it will be to use. Keep that in mind.

“An object that could collaborate with others w/o knowing who they are or what they could do, could be used in novel and unanticipated ways.”

“Switching your attention from objects to messages allows you to concentrate on designing an application built upon public interfaces.”

Law Of Demeter
LOD is a set of coding rules that Results in loosely coupled code. “Demeter restricts the set of objects to which a method may send messages. It prohibits routing a message to a third object via a second object of a different type. Demeter is often paraphrased as ‘only talk to your immediate neighbors or use only one dot.’”

1
2
3
4
5
6
7
#Blatant Violation of Demeter 
customer.bike.wheel.tire

#Alternative Solution
hash.keys.sort.join(', ')

#Check violations of demeter by evaluating the number of intemediary objects

The problem with sending methods(messages) in a chain of methods kind of fashion is that it indicates tightly coupled code. Tightly coupled code is a NONO becuase its hard to manage. If you was to change something in one place, you may have to change things in various other places. And this is not ideal. What you want is to be able to have isolated control over what your messages is doing in various places.

Solving Violations of Demeter is often done by, using delegation methods. If youve read chapters 2-4, each time you was directed to wrap an instance variable or create and isolate an object, you was actually creating delegation methods.

Remember demeter “prohibits routing a message to a third object via a second object of a different type.” If you are calling your own instance method, then type does not change.

You can also try to use the Delegate module in Ruby or the Forwardable Module. Check out this video: https://www.youtube.com/watch?v=jQXtSnUNfMY

SUMMARY

Following these rules leads to clean, flexible, loosely coupled public interfaces. Notice that I didnt say classes remember. The goal of OO Design is to “start modeling the world as a series of messages that pass through objects” We are no longer modeling classes, we are using SOLID, and techniques found in Books like POODR in order to construct flexible and loosely coupled public interfaces. ENJOY!