Created
April 23, 2016 15:48
-
-
Save fernandes/a23298a945bef6149bc31e3ff3ccbba1 to your computer and use it in GitHub Desktop.
My Annotations of POODR (Practical Object-Oriented Design in Ruby) Book by Sandi Metz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Chapter 2: Designing Classes with Single Responsability | |
Single Responsability | |
* Ask Each Method: 'Class whats your :method?' | |
* Hide Instance Variables | |
* Hide Data Structures | |
* Extract Extra Responsability From Methods | |
Dependencies | |
* Must known no Constant | |
* Must _not_ call methods on other objects (constants) | |
* Must do _not_ depedend on methods arguments (call methods knowing the arguments) | |
* Most not know the order | |
Solutions: | |
* Inject dependencies | |
* Isolate instance creation (on a method) | |
* Isolate vulnerable external messages | |
* Remove argument-order dependencies | |
* Explicity define defaults | |
# Chapter 3: Managing Dependencies | |
1. some classes are more likely than other to have changes in requirements | |
2. concrete classes are more likely to change than abstract classes | |
3. changing a class that has many dependents will result in widespread consequences | |
1. | |
ruby < framework mature < framework imature < your code | |
2. | |
prefer depend on duck typing instead of is_a?(Class) | |
3. | |
avoid classes that _the whole world_ depends on | |
Many | |
d | |--------------------------|------------------------------| | |
e | | Abstract Zone (A) | Danger Zone (D) | | |
p | | changes are unlikeli | these classes WILL change | | |
e | | but, it they occur, will | and the changes will cascade | | |
n | | have broad effects | into dependents | | |
d | |--------------------------|------------------------------| | |
a | | Neutral Zone (B) | Neutral Zone (C) | | |
n | | changes are unlikely | changes are likely but they | | |
t | | and have few side | have few side effects | | |
s | | effects | | | |
| |--------------------------|------------------------------| | |
Few | |
Less ------------------------------------------------- More | |
Likelihood of Requirements Change | |
# Chapter 4: Creating Flexible Interfaces | |
Finding the Public Interface | |
* Constructing an Intention | |
* Asking for "What" instead of telling "How" | |
* Seeking context indepence | |
* Trusting other objects _This blind trust is a keystone of OO design. It allows objects to collaborate without binding themselves to context and is necessary in any application that expects to grow and change_ | |
* Using messages to discover objects (TripFinder -> Operation) | |
Methods in the public interface should | |
* Be expecitly identified as such | |
* Be more about _what_ than _how_ | |
* Have names that, insofar as you can antecipate, will not change | |
* Take a hash as an options parameter | |
Demeter Law | |
* Dont use delegation | |
* Focus on what sender wants, not on receiver behaves (to make customer walk by bike, instead of `customer.bicycle.wheel.rotate` that tells the whell to behave rotating, I want the customer to ride, so `customer.ride` makes more sense.) | |
# Chapter 5: Reducing Costs with Duck Typing | |
Recognizing Hidden Ducks | |
* case statements that switch on class | |
* kind_of? and is_a? | |
* respond_to? | |
module Diameterizable | |
def test_responds_to_diameter | |
assert_respond_to @object, :diameter | |
end | |
end | |
class WheelTest < MiniTest::Test | |
include Diameterizable | |
def setup | |
@wheel = @object = Wheel.new(rim: 29, tyre: 1) | |
end | |
def test_diameter | |
assert_equal @wheel.diameter, 31 | |
end | |
end | |
sources: https://pooreffort.com/blog/testing-duck-types/ (check on how to test doubles!) | |
# Chapter 6: Acquiring Behavior Through Inheritance | |
Using The Template Method Pattern | |
class Bicycle | |
attr_reader :size, :chain, :tire_size | |
def initialize(args = {}) | |
@size = args[:size] | |
@chain = args[:chain] || default_chain | |
@tire_size = args[:tire_size] || default_tire_size | |
end | |
def default_chain | |
'10-speed' | |
end | |
end | |
class RoadBike < Bicycle | |
# ... | |
def default_tire_size | |
'23' | |
end | |
end | |
class MountainBike < Bicycle | |
# ... | |
def default_tire_size | |
'2.1' | |
end | |
end | |
Decoupling Subclasses Using Hook Messages | |
* Dont force a subclass to ask `super` to participate | |
class Abstract | |
def initialize(args = {}) | |
@size = args[:size] | |
@chain = args[:chain] || default_chain | |
@tire_size = args[:tire_size] || default_tire_size | |
post_initialize(args) | |
end | |
# force subclasses to define local_spares and do not override public interface | |
def spares | |
{ tire_size: tire_size, chain: chain }.merge(local_spares) | |
end | |
# dont break `spares` if subclass do not override | |
def local_spares | |
{} | |
end | |
# abstract has a default value for attribute | |
def default_chain | |
'10-speed' | |
end | |
# provice information about not implemented methods on subclasses | |
def default_tire_size | |
raise NotImplementedError.new('Subclass must implement: ') | |
end | |
# provide message hooks | |
def post_initialize(args) | |
nil | |
end | |
end | |
# Chapter 7: Sharing Role Behavior with Modules | |
# Chapter 8: Combining Objects with Composition | |
# Chapter 9: Designing Cost-Effective Tests | |
What should be tested? | |
* Incoming messages should be tested for the state they return | |
* Outgoing command messages should be tested to ensure they get sent | |
* Outgoing query messages should not be tested | |
Never inject concrete classes as dependencies, use abstract classes (and test them under injected class test, because if you change the abstract test will pass, but will get runtime errors!). | |
Use mocks to test outgoing messages | |
@observer = MiniTest::Mock.new | |
@observer.expect(:changed, true, [param1, param2]) | |
# ... | |
@observer.verify | |
Testing Inherited Code | |
* Specify the inherited interface | |
* Specify subclass responsabilities | |
* confirming subclass behavior | |
* confirming subclass enforcement | |
* Testing unique behavior | |
* testing concrete subclass behavior | |
* testing abstract superclass behavior | |
class Bicycle; end | |
class RoadBike < Bicycle; end | |
# Ensure all subclass of bicycle honor the contract (Liskov Principle) | |
module BicycleInterfaceTest | |
def test_responds_to_method | |
assert_respond_to(@object, :method) | |
end | |
end | |
class BicycleTest < MiniTest::Unit::TestCase | |
include BicycleInterfaceTest | |
def setup | |
@bike = @object = Bicycle.new({tire_size: 0}) | |
end | |
class RoadBikeTest < MiniTest::Unit::TestCase | |
include BicycleInterfaceTest | |
def setup | |
@bike = @object = RoadBike.new | |
end | |
end | |
Specifying Subclass Responsabilities | |
According to class and subclass interaction, subclasses are suposed to implement, some methods, so we need to create a test to ensure this interface | |
class BicycleSubclassTest | |
def test_responds_to_method_hook | |
assert_responds_to(@object, :method_hook) | |
end | |
end | |
class RoadBikeTest < MiniTest::Unit::TestCase | |
include BicycleSubclassTest | |
end | |
Confirming Superclass Enforcement | |
class Bicycle | |
def initialize(args = {}) | |
@tire_size = args[:tire_size] || default_tire_size | |
end | |
# Subclasses are supposed to implement | |
def default_tire_size | |
raise NotImplementedError | |
end | |
end | |
class BicycleTest < MiniTest::Unit::TestCase | |
def setup | |
@bike = @object = Bicycle.new({ tire_size: 0 }) | |
end | |
def test_forces_subclasses_to_implement_default_tire_size | |
assert_raises(NotImplementedError) { @bike.default_tire_size } | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In line 203 is an
end
missing.Thank you for publishing! ❤️